Beruflich Dokumente
Kultur Dokumente
The best way to calculate the amount of time between two dates is to take advantage of the
INTERVAL and TIMESTAMP datatypes, introduced in the Oracle9i Database. The following
function takes advantage of these datatypes to accept two dates and return the interval of time
between them:
CREATE OR REPLACE FUNCTION date_diff (
start_date_in IN DATE
, end_date_in IN DATE
)
RETURN INTERVAL DAY TO SECOND
IS
BEGIN
RETURN CAST ( end_date_in AS TIMESTAMP WITH TIME ZONE )
- CAST ( start_date_in AS TIMESTAMP WITH TIME ZONE );
END date_diff;
/
January's Tip of the Month
This is a way to create a Java class in an Oracle database using JDBC and calling the class
through a function created in PL/SQL. This java class, "dbTableRowCount", uses a default or
currently open connection to the database. To connect to the database from an external
application, the connection part of the code has to be enhanced providing a username and
password. This code can be implemented on an Oracle database version 8i and higher. The
classes can be created to perform DDL and DML tasks as needed per application requirements.
SELECT dbtabrowcount('xxxxx')
FROM DUAL;
If you are running Oracle9i Database Release 2 or above, you can advantage of a very, very cool
feature: associative arrays that are indexed by strings, rather than integer. I have found this
technique handy in a number of situations, most recently when I wanted to cache the contents of
a relational table in a collection and access rows by a GUID (globally unique identifier) primary
key value. These GUIDs are strings, not integers, so the typical INDEX BY BINARY_INTEGER
won't help me out much. String-based indexing is also very helpful when you need to index
information by more than one value, as is the case with a concatenated index.
In the package definition below, I am loading the contents of my books table (code to define this
table is available in this newsletter's download) into a series of associative arrays that emulate
the primary key and unique indexes on the table. The unique index arrays rely on string-based
indexes. By caching the table contents in collections, I can greatly speed up the performance of
querying against this static copy of the table. Very handy when running batch processes against
large tables!
For some additional background on this technique, check out my Brave New World seminar.
CREATE OR REPLACE PACKAGE summer_reading
IS
SUBTYPE author_title_t IS VARCHAR2 (32767);
FUNCTION onebook (
book_id_in IN books.book_id%TYPE
) RETURN books%ROWTYPE;
FUNCTION onebook (
isbn_in IN books.isbn%TYPE
)
RETURN books%ROWTYPE;
FUNCTION onebook (
author_in books.author%TYPE
,title_in books.title%TYPE
)
RETURN books%ROWTYPE;
PROCEDURE set_reload_interval (
interval_in IN NUMBER
);
PROCEDURE set_reload_interval (
interval_in IN INTERVAL DAY TO SECOND
);
END summer_reading;
/
books_aa book_id_aat;
by_isbn_aa isbn_aat;
by_author_title_aa author_title_aat;
FUNCTION author_title (
author_in books.author%TYPE
,title_in books.title%TYPE
,delim_in IN VARCHAR2 := '^'
) RETURN author_title_t
IS
BEGIN
RETURN UPPER (author_in) || delim_in || UPPER (title_in);
END;
PROCEDURE load_arrays
IS
BEGIN
DBMS_OUTPUT.put_line ( 'Reloading books arrays at '
|| TO_CHAR (SYSDATE, 'HH24:MI:SS')
);
g_last_load := SYSDATE;
END load_arrays;
PROCEDURE set_reload_interval (
interval_in IN INTERVAL DAY TO SECOND
)
IS
BEGIN
g_reload_interval := interval_in;
END;
PROCEDURE set_reload_interval (
interval_in IN NUMBER
)
IS
BEGIN
g_reload_interval :=
NUMTODSINTERVAL (interval_in, 'SECOND');
END;
FUNCTION reload_needed
RETURN BOOLEAN
IS
retval BOOLEAN := g_reload_interval IS NOT NULL;
l_date DATE := SYSDATE;
BEGIN
IF retval
THEN
retval :=
NUMTODSINTERVAL (l_date - g_last_load, 'DAY') >
g_reload_interval;
END IF;
RETURN retval;
END;
FUNCTION onebook (
book_id_in IN books.book_id%TYPE
)
RETURN books%ROWTYPE
IS
BEGIN
IF reload_needed
THEN
load_arrays;
END IF;
FUNCTION onebook (
isbn_in IN books.isbn%TYPE
)
RETURN books%ROWTYPE
IS
l_book_id books.book_id%TYPE :=
by_isbn_aa (isbn_in);
BEGIN
RETURN onebook (l_book_id);
END;
FUNCTION onebook (
author_in books.author%TYPE
,title_in books.title%TYPE
)
RETURN books%ROWTYPE
IS
BEGIN
RETURN onebook (
by_author_title_aa (
author_title (author_in, title_in)));
END;
BEGIN
load_arrays;
END summer_reading;
/
This is a small package to generate database documentation in HTML format. It sends an email
with 3 attachments (HTML files) containing a nicely formatted database object description.
Supported objects:
• Function
• Procedure
• Trigger
• User Type
• Table
• View
The following will prepare a document for the object EMP in the current schema.
documentator.generateDocInfo('EMP', 'emp.htm', 'email@your_domain.com');
Example 2:
documentator.generateDocInfoByType(IN_OBJECT_TYPE, IN_FILE_NAME, IN_EMAIL)
Where IN_OBJECT_TYPE is the object type (PACKAGE, etc..). The package will generate
documentation for all objects for the given type. IN_FILE_NAME is the output file name that will
be used as the base for output file names and IN_EMAIL is the email recipient address.
The following will prepare document for all tables in the current schema.
documentator.generateDocInfoByType('TABLE','table.htm',
'email@your_domain.com');
Example 3:
documentator.generateDocInfoByTypes(<TYPES>, IN_FILE_NAME, IN_EMAIL)
Where IN_OBJECT_TYPE specifies objects types (PACKAGE, PROCEDURE, etc..) separated
by commas, IN_FILE_NAME is the output file name that will be used as the base for output file
names and IN_EMAIL is the email recipient address.
The following will prepare a document for all passed object types in the current schema.
documentator.generateDocInfoByTypes('FUNCTION, PROCEDURE, PACKAGE, TRIGGER,
TYPE, TABLE, VIEW', 'objects.htm',
'email@your_domain.com');
To view the documentation, save attachments in any folder and open file with name passed as
parameter IN_FILE_NAME. This package is easy enough to extend for all other database objects
not included in it.
Note: Even if this specific functionality is of no use to you, the code is worth looking at as it does
contain some interesting components including emailing from within PL/SQL, string manipulation,
etc.
Sadly, Oracle does not yet support bi-directional access to cursor result sets (a.k.a, "scrollable
cursors") through a PL/SQL interface. You might well find, however, that you can achieve the
desired effect with a combination of the following:
• Multiple queries (each with different ORDER BY clauses that correspond to the different
ways you need to traverse the result set)
For tables with relatively small numbers of rows, the use of multiple queries might yield a
satisfactory implementation. If, however, your result set is very large, you might run into some
performance issues. In addition, you may still not be able to reference arbitrary rows within the
result set as desired.
Fortunately, you can achieve the desired effect of a bi-directional cursor rather easily by caching
the result in a PL/SQL collection. Once the data has been moved into the cache, you can move
back and forth through the result set, compare rows, etc., with complete freedom and a high
degree of efficiency.
Altering Types
Reprinted from OracleDBA.co.uk
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=20605
Recent versions of Oracle allow you to alter a type on the fly without having to recreate it. Here's
a simple example of how to do it, because as you can see from the first demo below, it's not quite
as intuitive as CREATE OR REPLACE.
create type T1 as object ( x number, y number ) not final;
/
Type created.
Type created.
Type altered.
Oddly In
Compliments of Steven Feuerstein, from the March issue of OPP/News
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=20606
I recently received an email from a puzzled developer. So...what's wrong with the following
scenario?
SQL> SELECT 'SQL IN trimmed trailing blanks!'
2 FROM DUAL
3 WHERE 'CA ' IN ( 'CA', 'US' )
4 /'TRIMMEDTRAILINGBLANKS!'
------------------------
SQL IN trimmed trailing blanks!
Isn't that odd? I asked if the string "CA " (CA followed by three spaces) was in the list of 'CA' and
'US' -- and Oracle said yes!
I checked the documentation but could not find anything about automatically trimming blanks
when you use the IN clause. So I experimented some more:
DBMS_OUTPUT.put_line ( message_in );
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ( message_in || ' did not happen.' );
END;
Ah, just kidding. You should continue to choose the best tool for the challenge at hand. But in this
case, avoid funkiness with IN by executing your query within a PL/SQL block!
The SYS_TYPEID function (in PL/SQL 9i/10g) can be used in a query to return the typeid of the
most specific type of the object instance passed to the function as an argument.
But, is there any way to get the text name of an object's type (or must I create my own
get_type_name function and implement it in every type)?
/
CREATE TYPE PartTimeStudent_typ UNDER Student_typ (
numhours NUMBER
)
/
When querying multiple rows of data from Oracle, don't use the cursor FOR loop. Instead,
assuming you are running at least Oracle8i, start using the wonderful, amazing BULK COLLECT
query, which improves query response time very dramatically. The following statement, for
example, retrieves all the rows in the employee table and deposits them directly into a collection
of records:
DECLARE
TYPE employee_aat IS TABLE OF employee%ROWTYPE
INDEX BY BINARY_INTEGER;
l_employees employee_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
FROM employee;
END;
Of course, if your table has 1,000,000 rows in it, the above block of code will consume enormous
amounts of memory. In this case, you will want to take advantage of the LIMIT clause of BULK
COLLECT as follows:
DECLARE
TYPE employee_aat IS TABLE OF employee%ROWTYPE
INDEX BY BINARY_INTEGER;
l_employees employee_aat;
For more complete coverage of this topic, check out my 21st Century PL/SQL seminar materials.
--
-- We need a target table, object type, collection type and a sequence to
-- replicate the issue described above...
--
CREATE TABLE t
( x INT
, y CHAR(1)
, z DATE );
CREATE SEQUENCE s;
--
-- Using the PLS-00436 workaround (see my 10g demo pages), we can now
emulate
-- what we would expect INSERT..SELECT..RETURNING to do, but using
-- FORALL..INSERT..VALUES instead. The variable "nt_passed_in" represents
the
-- collection parameter coming in from the middle-tier...
--
DECLARE
nt_passed_in ntt;
nt_to_return ntt;
BEGIN
nt_passed_in := pretend_parameter();
END;
/
spool off
--
-- Cleanup...
--
DROP TABLE t PURGE;
DROP SEQUENCE s;
DROP TYPE ntt;
DROP TYPE ot;
The output is shown below:
102> CREATE TABLE t
2 ( x INT
3 , y CHAR(1)
4 , z DATE );
Table created.
102>
102> CREATE TYPE ot AS OBJECT
2 ( x INT
3 , y CHAR(1)
4 , z DATE );
5 /
Type created.
102>
102> CREATE TYPE ntt AS TABLE OF ot;
2 /
Type created.
102>
102> CREATE SEQUENCE s;
Sequence created.
102>
102>
102> --
102> -- Using the PLS-00436 workaround (see my 10g demo pages), we can now
emulate
102> -- what we would expect INSERT..SELECT..RETURNING to do, but using
102> -- FORALL..INSERT..VALUES instead. The variable "nt_passed_in"
represents the
102> -- collection parameter coming in from the middle-tier...
102> --
102>
102> DECLARE
2
3 nt_passed_in ntt;
4 nt_to_return ntt;
5
6 FUNCTION pretend_parameter RETURN ntt IS
7 nt ntt;
8 BEGIN
9 SELECT ot(NULL, 'X', SYSDATE) BULK COLLECT INTO nt
10 FROM dual
11 CONNECT BY ROWNUM <= 5;
12 RETURN nt;
13 END pretend_parameter;
14
15 BEGIN
16
17 nt_passed_in := pretend_parameter();
18
19 FORALL i IN nt_passed_in.FIRST .. nt_passed_in.LAST
20 INSERT INTO t ( x, y, z )
21 VALUES
22 ( s.NEXTVAL
23 , TREAT( nt_passed_in(i) AS ot ).y
24 , TREAT( nt_passed_in(i) AS ot ).z
25 )
26 RETURNING ot( x, y, z )
27 BULK COLLECT INTO nt_to_return;
28
29 FOR i IN nt_to_return.FIRST .. nt_to_return.LAST LOOP
30 DBMS_OUTPUT.PUT_LINE(
31 'Sequence value = [' || TO_CHAR( nt_to_return(i).x ) || ']'
32 );
33 END LOOP;
34
35 END;
36 /
Sequence value = [1]
Sequence value = [2]
Sequence value = [3]
Sequence value = [4]
Sequence value = [5]
102>
102> spool off
The Template Design Pattern is perhaps one of the most widely used and useful OO (Object
Oriented) design patterns. It is used to set up the outline or skeleton of an algorithm, leaving the
details to specific implementations later. This way, subclasses can override parts of the algorithm
without changing its overall structure.
This is particularly useful for separating the variant and the invariant behaviour, minimizing the
amount of code to be written. The invariant behaviour is placed in the abstract class (template)
and then any subclasses that inherits it can override the abstract methods and implement the
specifics needed in that context.
How do we implement the Template Design Pattern in PL/SQL, using PL/SQL 9i/10g object
types? We must use MEMBER (non-STATIC) methods (procedures/functions):
CREATE OR REPLACE TYPE templ_method_abstract AS OBJECT (
dummy VARCHAR2(10),
MEMBER PROCEDURE template_method,
NOT INSTANTIABLE MEMBER PROCEDURE operation1,
NOT INSTANTIABLE MEMBER PROCEDURE operation2
)
NOT FINAL
NOT INSTANTIABLE
/
CREATE OR REPLACE TYPE BODY templ_method_abstract IS
MEMBER PROCEDURE template_method IS
BEGIN
operation1;
operation2;
END;
END;
/
DECLARE
l_templ_method_object templ_method_abstract;
BEGIN
l_templ_method_object := NEW templ_method_concrete_a ('DUMMY');
l_templ_method_object.template_method;
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/38 PLS-00169: modifier 'STATIC' conflicts with prior 'NOT
INSTANTIABLE' specification