Skip to content

Oracle Tips and Tricks

November 23, 2012

While I generally prefer to build solutions that are database agnostic, sometimes it’s necessary to move some code into stored procedures or triggers in the interests of performance. This reduces the interaction between the application and the database but also gives the database more control over optimizations. This blog is about some small tricks that you can do in Oracle. I will follow up with a later one on SQL Server tips.

Joining onto or querying Cursors

Cursors are both a blessing and a curse. They provide a lot of flexibility but one part where they really fall short is when you want to join against them with a query or extract a single row out of the cursor using a query. They don’t work that way so you end up scratching your head. Well, there are a few ways around it. The quickest (not so quick from Oracle’s perspective but rather from a development standpoint) and dirtiest way to get this working is to use the Oracle XML operations to convert it to an xml table.

Let’s assume that you have a cursor with the following fields:

Client varchar2(100)
Invoice_Number Number(16)
Amount Number(16,2)

The idea is to get all of the data into a “table” that we can use for joins or queries. The trick is to do something like the following:

select I.*
  from xmltable('/ROWSET/ROW' passing xmltype(MyCursor) columns
    Client varchar2(100) path 'Client',
    Invoice_Number Number(16) path 'Invoice_Number',
    Amount Number(16,2) path 'Amount') I;

I wouldn’t use this for large cursors or for operations that require really high performance but it’s really useful to use on the odd occasion. Here is a full set of code to test it out:

-- Create the test table
CREATE TABLE TestInvoiceTable
(
Client varchar2(100),
Invoice_Number Number(16),
Amount Number(16,2)
);

-- Add some test data
INSERT INTO TestInvoiceTable (Client, Invoice_Number, Amount) VALUES ('Client A', 1231, 55.50);
INSERT INTO TestInvoiceTable (Client, Invoice_Number, Amount) VALUES ('Client B', 1232, 122.96);
INSERT INTO TestInvoiceTable (Client, Invoice_Number, Amount) VALUES ('Client A', 1233, 232.00);
INSERT INTO TestInvoiceTable (Client, Invoice_Number, Amount) VALUES ('Client C', 1234, 85.14);

-- A Block of code to test our query
declare
  example_cursor SYS_REFCURSOR;
  ExtractInvNo Number(16);
Begin
  -- Build a dummy cursor
  OPEN example_cursor FOR
  SELECT * FROM TestInvoiceTable;

  -- Use the cursor in a traditional query
  SELECT I.Invoice_Number INTO ExtractInvNo
  FROM XMLTABLE('/ROWSET/ROW' PASSING XMLTYPE(example_cursor) COLUMNS
    Client varchar2(100) path 'CLIENT',
    Invoice_Number Number(16) path 'INVOICE_NUMBER',
    Amount Number(16,2) path 'AMOUNT') I WHERE I.Amount > 100 AND ROWNUM = 1;

  -- Output the value
  DBMS_OUTPUT.PUT_LINE(ExtractInvNo);
end;

This will output 1232 to the output window.

Finding Blockers

This can be done really quickly by querying the v$session_blockers table. It will quickly return which sessions are being blocked and by who. Not very difficult to find but it certainly helps if you find your oracle session just hanging.

select * from v$session_blockers

Running Totals in a Query

Getting a running total can be really useful in queries. We may want to find out how many line items on an invoice are still outstanding if we have only received part of the payment on that invoice or we may just want to have a carrying value for a report. Oracle has a way to do this. We can even have a running total for a particular subset of our data. So, using the table that I added earlier, we could do something like this:

SELECT Client, Invoice_Number, Amount,
  SUM(Amount) OVER 
    (ORDER BY Client, Invoice_Number) AS Running_Total,
  SUM(Amount) OVER 
    (PARTITION BY Client ORDER BY Client, Invoice_Number) AS Client_Total
FROM TestInvoiceTable ORDER BY Client, Invoice_Number

This will return the following data:

CLIENT INV_NUMBER AMOUNT RUNNING_TOTAL CLIENT_TOTAL
Client A 1231 55.50 55.5 55.5
Client A 1233 232.00 287.5 287.5
Client B 1232 122.96 410.46 122.96
Client C 1234 85.14 495.6 85.14

As you can see, we have two running totals. The running total for the whole query; and the running total for each client courtesy of PARTITION BY.

Conclusion

Databases might not be your cup of tea but it helps to know what you can do with them. A few tricks can go a long way to making your life easier and ultimately improving the performance and robustness of your solution.

From → Coding Tips

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: