Fetch multiple rows into a variable - sql

I am attempting to fetch multiple unique account ID's (transactions.ID) and store in Variable v_Trxn. The problem is that the query count returns incorrect. Expected result is 300485 rows.
DECLARE
l_Partition transactions.Partitionkey%TYPE;
v_Trxn transactions.ID%TYPE;
l_Count number;
CURSOR v_Trxn_cur IS
SELECT ID
FROM transactions
WHERE Partitionkey > l_Partition;
BEGIN
SELECT dl_common.Get_Partitionkey(aInstitutionId => DL_COMMON.Get_InstitutionId , aDate => add_months(sysdate,-4)) INTO l_Partition FROM Dual;
OPEN v_Trxn_Cur;
LOOP
FETCH v_Trxn_Cur INTO v_Trxn;
EXIT WHEN v_Trxn_Cur%NOTFOUND;
END LOOP;
SELECT COUNT(UNIQUE(ID)) INTO l_Count FROM transactions WHERE ID In v_Trxn;
DBMS_OUTPUT.PUT_LINE(l_Count || ' Number of Unique Sernos');
CLOSE v_Trxn_Cur;
End;
Output: 1 Number of Unique Sernos
If I put the partitionkey directly into the WHERE clause I get the expected number of rows.
SELECT
dl_common.Get_Partitionkey(aInstitutionId => DL_COMMON.Get_InstitutionId,
aDate => add_months(sysdate, -4)) PARTITION
FROM Dual;
Returns: PARTITION 2914365
SELECT
COUNT(UNIQUE(ID)) C_COUNT
FROM transactions C
WHERE C.Partitionkey > 2914365
Returns: C_COUNT 300485
The expected number of rows is fetched.
Please explain what I am doing wrong.

You're repeatedly selecting a single ID into your scalar variable. It can never hold more than a single value at a time. After the first iteration of your loop your variable holds the first ID returned by your cursor query (which is indeterminate because you don't have an order-by clause). After the second iteration your variable holds the second ID returned by the cursor query. It does not, and cannot, hold both values simultaneously.
To hold multiple values you would need to bulk select into a collection type, which needs to be declared at schema level (i.e. an SQL type, not a PL/SQL type) if you really want to use it as part of a later SQL query.
But storing hundreds of thousands of values in a collection is going to consume a significant amount of memory. Without knowing what you're really going to do with the values once you have them it isn't clear if that is just a price you'll have to pay; if you can do your work in batches (of, say, 1000 IDs at a time; if row-by-row processing is appropriate; or if you really want a join as part of a larger query, without holding them as a PL/SQL variable at all.

You need to use VARRAY OR NESTED TABLES FOR THIS PURPOSE.
You can hold multiple values like this-
Type var_dnames IS VARRAY(1000) transactions.ID%TYPE;
v_Trxn var_dnames;
Now "v_Trxn" can hold multiple values from fetch statement.

Related

Cursor in procedure returning more values than query

I am using a simple cursor in a procedure that receives a couple of parameters.
I then make a cursor on a select query with a where clause with multiple conditions, which are equal to the received parameters. This cursor should only return 1 row, instead it returns multiple rows. I found this out because I'm using a for loop to go through this cursor and insert something into another table based on the values of this cursor.
When I run the query on the database statically(as in without pl/sql) I get what I expect, but when I do it from a cursor which should return only one row, and run it in a for loop, the loop does multiple iterations. How is this possible?
Thank you!
EDIT:
ID kind kolo kolo1 mjt salesman money date done
1 001 001 002 00013 00056 100,00 21-feb-12 N
I run a cursor like this:
Cursor linija IS
SELECT *
FROM table_x X
where x.mjt = mjt
and x.salesman = salesman
and x.kind = kind
and x.kolo1 = kolo1
and x.done = 'N';
This should return only one row, but instead my cursor returns %rowcount is 10.
You have a name conflict. You have called your local variables the same as your column names, and the column names are taking precedence, as noted in the documentation:
If a SQL statement references a name that belongs to both a column and either a local variable or formal parameter, then the column name takes precedence.
Caution:
When a variable or parameter name is interpreted as a column name, data can be deleted, changed, or inserted unintentionally.
The first four checks are always going to be true (unless you have null values), so you'll get every row that has done = 'N'.
Change your local variable names to something else; it's fairly common to use a prefix to distinguish between local variables, parameters, and columns, something like:
Cursor linija IS
SELECT *
FROM table_x X
where x.mjt = l_mjt
and x.salesman = l_salesman
and x.kind = l_kind
and x.kolo1 = l_kolo1
and x.done = 'N';
If this is in a stored procedure, rather than an anonymous block, you could use the procedure/function name as a prefix, which some people prefer. If your procedure was called myproc, for example, you could do:
Cursor linija IS
SELECT *
FROM table_x X
where x.mjt = myproc.mjt
and x.salesman = myproc.salesman
and x.kind = myproc.kind
and x.kolo1 = myproc.kolo1
and x.done = 'N';
In addition to what Alex has said (and I can't second his advice to distinguish variable names from column names enough!), why are you using a cursor for loop to do the insert?
You could just do your insert in one SQL statement, eg:
insert into your_table (col1, col2, ...)
select col1, col2, ...
from your_table
where ...
That will perform much better than going through the whole dataset and inserting each row one at a time. When it comes to databases, think set-based as much as you can, not procedurally!

Convert select into stored procedure best approach

I use this SQL to get count for every group of type.
select
mytype, count(mytype)
from types1
group by 1
The result is 5 records with count for each type. I need to convert this to a stored procedure; should I write the above SQL using For...Select or should I return single value using Select...Where...Into 5 times for each type?
I will use the return counts to update a master table and types may increase in the future.
That depends on what you want out of the procedure:
If you want the same output as your select with five rows, use a FOR SELECT. You will get one row for each type and an associated count. This is probably the "standard" approach.
If however you want five output variables, one for each count of each type, you can use five queries of the form SELECT COUNT(1) FROM types1 WHERE mytype = 'type1' INTO :type1. Realize though that this will be five queries and you may be better off doing a single FOR SELECT query and looping through the returned rows in the procedure. Also note that if you at some point add a sixth type you will have to change this procedure to add the additional type.
If you want to query a single type, you can also do something like the following, which will return a single row with a single count for the type in the input parameter:
CREATE PROCEDURE GetTypeCount(
TypeName VARCHAR(256)
)
RETURNS (
TypeCount INTEGER
)
AS
BEGIN
SELECT COUNT(1)
FROM types1
WHERE mytype = :TypeName
INTO :TypeCount;
SUSPEND
END

SQL on-demand cache table (possibly using SQL MERGE)

I am working on implementing an on-demand SQL cache table for an application so I have
CacheTable with columns Type, Number, Value
Then I have a function called GetValue( Type, Number )
So I want to have a function that does the following
If (CacheTable contains Type, Number) then return value
Else call GetValue( Type, Number) and put that value into CacheTable and return the Value
Does anyone know the most elegant way to do this?
I was thinking of using a SQL merge.
Not sure how elegant one can get, but we might do it just the way you describe. Query the database
select Value from Tab1 where Type=#type and Number=#num
and if no rows are returned, compute the value, then store it in the database for next time.
However, if the "compute the value" requires the database itself, and we can compute it in the database, then we can do the whole cycle with one database round trip -- more 'elegant' perhaps but faster at least than 3 round trips (lookup, compute, store).
declare #val int
select #val=Value from Tab1 where Type=#type and Number=#num
if ##ROWCOUNT=0 BEGIN
exec compute_val #type,#num,#val OUTPUT
insert into Tab1 values (#type,#num,#val)
END
SELECT #val[Value]--return
The only use for SQL Merge is if you think there may be concurrent users and the number is inserted between above select and insert, giving an error on the insert. I'd just catch the error and skip the insert (as we can assume the value won't be different by definition).

How to reuse a large query without repeating it?

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.
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)
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.
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)*/
If you operate with values, you could write functions.
Here you find infos on how to do it. It basically works like writing a function in any language. You can define parameters and return values.
Which gives you the cool possibility to write code just once. Here is how you do it:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5009.htm
Have you tried using RESULT_CACHE hint in your queries? Also, you could
ALTER SESSION SET RESULT_CACHE_MODE=FORCE
and see if it helps.

Complex Cursors for returning multiple rows

Each row in a cursor should be joined with another table and the whole result should be returned a one cursor
Say cursor1 return 5 rows.
Each of these 5 rows should be joined with table1.
and the final result for all the 5 rows should be returned a 1 row.
Plz help
It is rather tricky to combine the fetched records from a ref cursor with the result set of another query. A much better idea would be to open just the one ref cursor with a SELECT which joins all the relevant tables.
The question isn't clear, but it sounds like what you need is something like this
a) Define an object type with the shape of your result row
b) Define a collection of that object type
c) Create a function with a return type of the collection type - this could take in cursor A as a parameter (SYS_REFCURSOR), join each row in cursor A to table B, and then use PIPE ROW for each result row.
d) If the final result is needed as a cursor, then another function along the lines of
FUNCTION complex_query(in_cursor SYS_REFCURSOR)
RETURN SYS_REFCURSOR
IS
lreturn SYS_REFCURSOR;
BEGIN
OPEN lreturn FOR
(SELECT * FROM TABLE(convert_to_collection(in_cursor)));
RETURN lreturn;
END;
Alternatively, you could just do the SELECT * FROM TABLE(convert_to_collection(in_cursor)) directly.
What I don't understand is the requirement that everything is returned as 1 row.