views:

1388

answers:

11

In light of the "Hidden features of..." series of questions, what little-known features of PL/SQL have become useful to you?

Edit: Features specific to PL/SQL are preferred over features of Oracle's SQL syntax. However, because PL/SQL can use most of Oracle's SQL constructs, they may be included if they make programming in PL/SQL easier.

+5  A: 

One little-known feature I have had great success with is the ability to insert into a table using a variable declared as its %ROWTYPE. For example:

CREATE TABLE CUSTOMERS (
    id NUMBER,
    name VARCHAR2(100),
    birth DATE,
    death DATE
)

PROCEDURE insert_customer IS
    customer CUSTOMERS%ROWTYPE;
BEGIN
    customer.id := 45;
    customer.name := 'John Smith';
    customer.birth := TO_DATE('1978/04/03', 'YYYY/MM/DD');

    INSERT INTO CUSTOMERS VALUES customer;
END;

Although it chews up a bit more redo tablespace, it certainly makes inserting data (especially into larger tables) much clearer. It also avoids the multitude of variables needed to store each column's value you wish to insert.

Adam Paynter
%ROWTYPE saves on duplication.
RichardOD
+4  A: 

Maybe not hidden enough , but I love the Merge statement that allow make upserts (insert or update)

MERGE <hint> INTO <table_name>
USING <table_view_or_query>
ON (<condition>)
WHEN MATCHED THEN <update_clause>
DELETE <where_clause>
WHEN NOT MATCHED THEN <insert_clause>
[LOG ERRORS <log_errors_clause> <reject limit <integer | unlimited>];
Jonathan
MERGE is not PL/SQL (3GL) but an Oracle SQL statement.
Andrew from NZSG
+5  A: 

The truly hidden oracle function is the OVERLAPS function, but is properly not very wise to use any unsupported futures


select 'yes' from dual where (sysdate-5,sysdate) overlaps (sysdate-2,sysdate-1);
Arno Conradie
It's shame that it's undocumented. It is so clear and concise!
Adam Paynter
I wonder if it can be used directly in PL/SQL IF statements...
Adam Paynter
Paynter: yes - beginif (sysdate-5,sysdate) overlaps (sysdate-2,sysdate-1) thendbms_output.put_line('yes');end if;end;
Jeffrey Kemp
Although it's not too painful to just write:select 'yes' from dual where sysdate-2 BETWEEN sysdate-5 AND sysdate OR sysdate-1 BETWEEN sysdate-5 AND sysdate;
Steve Broberg
overlaps() looks exactly what I needed! Though it being unsupported makes me wonder whether I should use it or not.
Sathya
@steve: ah, but that's not how overlaps() works though. You need to do (1stRange_highval >= 2ndRange_lowval AND 1stRange_lowval <= 2ndRange_highval)
David Aldridge
Oracle in note 1056382.1 translates "where (s1, a1) overlaps (s2, a2);"as"where (s2 < a1 and a2 > s1) or (s1 < a2 and a1 > s2);"
Leigh Riffel
+5  A: 

You can override variables, you can name anonymous blocks, and you can still refer to the overridden variables by name:

PROCEDURE myproc IS
   n NUMBER;
BEGIN
   n := 1;
   <<anon>>
   DECLARE
      n NUMBER;
   BEGIN
      n := 2;
      dbms_output.put_line('n=' || n);
      dbms_output.put_line('anon.n=' || anon.n);
      dbms_output.put_line('myproc.n=' || myproc.n);
   END anon;
END myproc;
Jeffrey Kemp
I didn't even know blocks could have labels! Very useful!
Adam Paynter
True. Many don't know that we can label loops. Really good.
Guru
+3  A: 

This a PL/SQL procedural construct i use a lot (credits to Steven Feuerstein and Chen Shapira). An Associative array used for chaching, but it does not pre load all data but gets data from database if needed and puts it in the Associative array.

create or replace
PACKAGE justonce
IS
  FUNCTION hair (code_in IN hairstyles.code%TYPE)
    RETURN hairstyles%ROWTYPE;
  TYPE hair_t IS TABLE OF hairstyles%ROWTYPE
    INDEX BY BINARY_INTEGER;
  hairs          hair_t;
END justonce;

create or replace 
PACKAGE BODY justonce
IS
  FUNCTION hair (code_in IN hairstyles.code%TYPE) RETURN hairstyles%ROWTYPE
  IS
    return_value   hairstyles%ROWTYPE;
    FUNCTION hair_from_database RETURN hairstyles%ROWTYPE
    IS
      CURSOR hair_cur IS
      SELECT * FROM hairstyles WHERE code = code_in;
    BEGIN
      OPEN hair_cur;
      FETCH hair_cur INTO return_value;
      CLOSE hair_cur;
      RETURN return_value;
    END hair_from_database;
  BEGIN
    IF NOT (hairs.exists(code_in))
    THEN
      dbms_output.put_line('Get record from database');
      hairs (code_in) := hair_from_database;
    END IF;
    RETURN hairs (code_in);
  END hair;
END justonce;

Test it :

declare
    h hairstyles%ROWTYPE;
begin
   for i in 1000..1004
   loop
      h := justonce.hair(i);
      dbms_output.put_line(h.description);
   end loop;
   for i in 1000..1004
   loop
      h := justonce.hair(i);
      dbms_output.put_line(h.description||' '||h.price);
   end loop;

end;
/

Get record from database
CREWCUT
Get record from database
BOB
Get record from database
SHAG
Get record from database
BOUFFANT
Get record from database
PAGEBOY
CREWCUT 10
BOB 20
SHAG 21
BOUFFANT 11
PAGEBOY 44
Robert Merkwürdigeliebe
Those table types are very useful! I like being able to use arbitrary indexes (in your case, hair codes) to reference its rows. Much nicer than traditional arrays!
Adam Paynter
+3  A: 
  1. An undocumented function: dbms_system.ksdwrt (writes to alert/trace files)
  2. DBMS_SQL package (as an example of its use see this question
  3. AUTHID CURRENT_USER clause
  4. Conditional compilation
Andrew from NZSG
Be careful about using dbms_system.ksdwrt (). Programs which use it can serve as vectors in a DoS attack.
APC
Hidden feature : Conditional Compilation can be enabled in Oracle 9.2.0.6 (it was back-ported to support a patch release)
JulesLt
+5  A: 

You can index pl/sql tables by other types besides integers. This way you can create "dictionary" like structures, which can make your code much easier to read:

Example:

DECLARE
  TYPE dictionary IS TABLE OF VARCHAR2(200) INDEX BY VARCHAR2(100);
  dict dictionary;
BEGIN
  dict('NAME') := 'John Doe';
  dict('CITY') := 'New York';

  dbms_output.put_line('Name:' || dict('NAME'));
END;
Diederik Hoogenboom
Again, another thing I didn't know you could do! Very useful!
Adam Paynter
+1  A: 

Dynamic PL/SQL is ugly, but can do some interesting stuff. For example, names can be treated as variables, which I've used earlier to traverse %rowtype variables like arrays, and to create a function which will, for a given table name, return a cursor which selects a single row with the default values of each column. Both are useful workarounds for denormalized tables.

l0b0
+5  A: 

My answer to Hidden Features in Oracle is relevant here:

Since Apex is now part of every Oracle database, these Apex utility functions are useful even if you aren't using Apex:

SQL> declare
  2    v_array apex_application_global.vc_arr2;
  3    v_string varchar2(2000);
  4  begin
  5  
  6    -- Convert delimited string to array
  7    v_array := apex_util.string_to_table('alpha,beta,gamma,delta', ',');
  8    for i in 1..v_array.count
  9    loop
 10      dbms_output.put_line(v_array(i));
 11    end loop;
 12  
 13    -- Convert array to delimited string
 14    v_string := apex_util.table_to_string(v_array,'|');
 15    dbms_output.put_line(v_string);
 16  end;
 17  /
alpha
beta
gamma
delta
alpha|beta|gamma|delta

PL/SQL procedure successfully completed.
Tony Andrews
nope.. apex isnt 'standard'..on Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64biPL/SQL Release 10.2.0.4.0 - Productiona "desc apex_util" givesERROR:ORA-04043: object apex_util does not exist
ShoeLace
@ShoeLace, actually I believe it has been standard since 10.2 at least. However (a) the package may have still been called htmldb_util then, and (b) it may require configuration by the DBA to make it available. I don't know the definitive answer to either of these questions I'm afraid.
Tony Andrews
+4  A: 

Procedures and functions may be defined within DECLARE blocks:

DECLARE

    PROCEDURE print(text VARCHAR2) IS
    BEGIN
        DBMS_OUTPUT.put_line(text);
    END;

BEGIN

    print('Yay!');
    print('Woo hoo!');

END;

This is handy for creating stand-alone scripts.

Adam Paynter
You can also nest procedures and functions, which is especially useful when you need to break down large procedures into several smaller ones, without passing tons of parameters.
ammoQ
+2  A: 

Do you know that with the option SAMPLE (K) you can SELECT only a sample composed by up to K percent of an Oracle table?

SELECT *
  FROM MASSIVE_TABLE SAMPLE (5);

The previous statement retrieves a randomic set composed by up to 5% of the records stored in the massive table called MASSIVE_TABLE.

The chicken in the kitchen
This is not PL/SQL.
Janek Bogucki