Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reuse a large query without repeating it?

Tags:

sql

plsql

dry

If I have two queries, which I will call horrible_query_1 and ugly_query_2, and I want to perform the following two minus operations on them:

(horrible_query_1) minus (ugly_query_2)
(ugly_query_2) minus (horrible_query_1)

Or maybe I have a terribly_large_and_useful_query, and the result set it produces I want to use as part of several future queries.

How can I avoid copying and pasting the same queries in multiple places? How can I "not repeat myself," and follow DRY principles. Is this possible in SQL?

I'm using Oracle SQL. Portable SQL solutions are preferable, but if I have to use an Oracle specific feature (including PL/SQL) that's OK.

like image 409
Buttons840 Avatar asked Feb 02 '12 20:02

Buttons840


People also ask

How do I reuse a MySQL query?

The workaround in MySQL is to create multiple temporary tables. You can use a temporary table as a source to populate another one. This is the approach we use. (Yes, it would be more efficient to use a single temporary table, but MySQL doesn't allow the same temporary table to be referenced more than once.)


3 Answers

If you want to reuse the SQL text of the queries, then defining views is the best way, as described earlier.

If you want to reuse the result of the queries, then you should consider global temporary tables. These temporary tables store data for the duration of session or transaction (whichever you choose). These are really useful in case you need to reuse calculated data many times over, especially if your queries are indeed "ugly" and "horrible" (meaning long running). See Temporary tables for more information.

If you need to keep the data longer than a session, you can consider materialized views.

like image 23
Szilard Barany Avatar answered Sep 29 '22 21:09

Szilard Barany


create view horrible_query_1_VIEW as 
 select .. ...
  from .. .. ..

create view ugly_query_2_VIEW as 
 select .. ...
  from .. .. ..

Then

(horrible_query_1_VIEW) minus (ugly_query_2_VIEW)

(ugly_query_2_VIEW) minus (horrible_query_1_VIEW)

Or, maybe, with a with clause:

with horrible_query_1 as (
  select .. .. ..
    from .. .. ..
) ,
ugly_query_2 as (
  select .. .. ..
     .. .. ..
)
(select * from horrible_query_1 minus select * from ugly_query_2    ) union all
(select * from ugly_query_2     minus select * from horrible_query_1)
like image 117
René Nyffenegger Avatar answered Sep 29 '22 22:09

René Nyffenegger


Since you're using Oracle, I'd create Pipelined TABLE functions. The function takes parameters and returns an object (which you have to create) and then you SELECT * or even specific columns from it using the TABLE() function and can use it with a WHERE clause or with JOINs. If you want a unit of reuse (a function) you're not restricted to just returning values (i.e a scalar function) you can write a function that returns rows or recordsets. something like this:

FUNCTION RETURN_MY_ROWS(Param1 IN type...ParamX IN Type)
            RETURN PARENT_OBJECT PIPELINED
            IS
            local_curs cursor_alias; --you need a cursor alias if this function is in a Package
            out_rec ROW_RECORD_OF_CUSTOM_OBJECT:=ROW_RECORD_OF_CUSTOM_OBJECT(NULL, NULL,NULL) --one NULL for each field in the record sub-object
        BEGIN
         OPEN local_curs FOR
          --the SELECT query that you're trying to encapsulate goes here
          -- and it can be very detailed/complex and even have WITH () etc..
        SELECT * FROM baseTable WHERE col1 = x;

   -- now that you have captured the SELECT into a Cursor
   -- here you put a LOOP to take what's in the cursor and put it in the 
   -- child object (that holds the individual records)
          LOOP
         FETCH local_curs --opening the ref-cursor
          INTO out_rec.COL1, 
               out_rec.COL2,
               out_rec.COL3;
        EXIT WHEN local_curs%NOTFOUND;
         PIPE ROW(out_rec); --piping out the Object
        END LOOP;
        CLOSE local_curs;  -- always do this
        RETURN;  -- we're now done
  END RETURN_MY_ROWS;

after you've done that, you can use it like so

SELECT * FROM TABLE(RETURN_MY_ROWS(val1, val2)); 

you can INSERT SELECT or even CREATE TABLE out of it , you can have it in joins.

two more things to mention:

--ROW_RECORD_OF_CUSTOM_OBJECT is something along these lines
CREATE or REPLACE TYPE ROW_RECORD_OF_CUSTOM_OBJECT AS OBJECT
(
     col1 type;
     col2 type;
      ...
     colx type;
 );

and PARENT_OBJECT is a table of the other object (with the field definitions) we just made

create or replace TYPE PARENT_OBJECT IS TABLE OF ROW_RECORD_OF_CUSTOM_OBJECT;

so this function needs two OBJECTs to support it, but one is a record, the other is a table of that record (you have to create the record first).

In a nutshell, the function is easy to write, you need a child object (with fields), and a parent object that will house that child object that is of type TABLE of the child object, and you open the original base-table fetching SQL into a SYS_REFCURSOR (which you may need to alias) if you're in a package and you read from that cursor from a loop into the individual records. The function returns a type of PARENT_OBJECT but inside it packs the records sub-object with values from the cursor.

I hope this works for you (there may be permissioning issues with your DBA if you want to create OBJECTs and Table functions)*/

like image 26
MereMortal Avatar answered Sep 29 '22 22:09

MereMortal