I'm a newbie in SQL and I was wondering if you can print the contents of a cursor%rowtype dynamically ?
For example:
cursor cur is select * from ...;
current_row cur%rowtype;
begin
open cur;
loop
fetch cur into current_row;
-- dbms_output.put_line( current_row ); Would that be possible ?
exit when cur%notfound;
end loop;
Or do I have to do the boring part myself, that is specifying each member I need to print, which is quite lousy and lame, e.g.:
dbms_output.get_line( current_row.first || current_row.second || .... || current_row.last );
Is there a simple way to achieve dynamic printing to some extent in PL/SQL? Im using PL/SQL developer.
Thanks
Use the DBMS_SQL package to archive that. Of course it takes more lines (than the simple %rowtype cursor loop in your example) to create and loop through the cursor, but once this is done, you can process any SELECT query and output the results, no matter how many columns the query returns of what types the columns have.
Related
I have a table TestTable with columns of col_test1, col_test2, col_test3 ...
and I want to create a loop that accesses each of these columns individually and find the max value and place it in the variable made in the declare block and simply dbms.out.put it.
Declare
my_array sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('col_test1','col_test2','col_test2');
v_test number(8,0);
Begin
for r in my_array.first..my_array.last
loop
select max(my_array(r)) into v_test from TestTable;
dbms_output.put_line(v_test);
end loop;
End;
/
The output I get is just the string 'col_test1'which should be 50.
This is done through oracle SQL. Is there any way to achieve this?
You could use dynamic SQL for this
Declare
my_array sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('col_test1','col_test2','col_test2');
v_test number(8,0);
Begin
for r in my_array.first..my_array.last
loop
execute immediate 'select max(' || my_array(r) || ') from TestTable'
into v_test;
dbms_output.put_line(v_test);
end loop;
End;
If you're going to resort to dynamic SQL, however, it would generally make more sense to build a single SQL statement that took that max of all three columns in one pass rather than potentially doing three separate table scans on the same table.
I am trying to use open cursor as a substitute for execute immediate because my SQL statement could return multiple records.
open cur1 for rule_sql;
loop
dbms_output.put_line(cur1.rule_id);
end loop;
close cur1;
It throws an error saying: "PLS-00487: Invalid reference to variable 'CUR1'"
Has anyone had similar issues? Any help is much appreciated :)
The cursor is just a pointer to a result set. To reference its contents you need to fetch it into a variable. Note that the variable must be a record type which matches the projection of the query. This may be hard if you're using dynamic SQL to implement a fluid set of columns.
Anyway, something like this:
declare
cur1 sys_refcursor;
Type cur_rec is record (
rule_id number,
rule_desc varchar2(32));
row1 cur_rec;
....
Begin
...
open cur1 for stmt;
for row1 in cur1 loop
Dbms_output.put_line(row1.rule_id);
End loop;
....
End;
"If I do not know the type of columns in the result then I cannot create a variable to capture the cursor values."
Life is more complicated when you don't know the projection of your query at compile time. You can't use Native Dynamic SQL anymore, you need to go full DBMS_SQL.
In 11g Oracle introduced the so-called Method 4 Dynamic SQL. This allows us to handle variable projections at the cost of a lot more code. Adrian Billington wrote an excellent introduction to this on his Oracle-developer.net site. Check it out
You have missed the fetch statement - see Example 7.4 in docs
open cur1 for rule_sql;
loop
fetch cur1 into my_row_variable;
exit when cur1%notfound;
dbms_output.put_line(cur1.rule_id);
end loop;
close cur1;
I have an existing Customers table and want to output each row from this table using a stored procedure. There is no input criteria, just need to output all of the records.
The stored procedure should basically be equivalent to:
"SELECT C_ID, LAST, FIRST, DOB, DPHONE, EMAIL FROM customers;"
This seems simple but I can't figure it out. All my searches haven't worked out for this case.
How would one accomplish this?
EDIT: Answered my question below. Very simple.
In Oracle your options are:
1. Use a function and return a REF CURSOR
2. Use a procedure and use a REF CURSOR as an OUT parameter
3. Use a PIPELINED function
4. Use a function and return a collection.
read this documentation
Overview of Table Functions
see similar question here Return collection from packaged function for use in select
simple sample of such function
CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet
PIPELINED IS
out_rec TickerType := TickerType(NULL,NULL,NULL);
in_rec p%ROWTYPE;
BEGIN
LOOP
FETCH p INTO in_rec;
EXIT WHEN p%NOTFOUND;
-- first row
out_rec.ticker := in_rec.Ticker;
out_rec.PriceType := 'O';
out_rec.price := in_rec.OpenPrice;
PIPE ROW(out_rec);
-- second row
out_rec.PriceType := 'C';
out_rec.Price := in_rec.ClosePrice;
PIPE ROW(out_rec);
END LOOP;
CLOSE p;
RETURN;
END;
/
Nevermind! Just got it. It is pretty simple. I just did this:
CREATE OR REPLACE PROCEDURE CustomerReport AS
BEGIN
FOR cus IN (SELECT C_ID, LAST, FIRST, DOB, DPHONE, EMAIL FROM customers)
LOOP
dbms_output.put_line(cus.C_ID||' '||cus.FIRST||' '||cus.LAST||' '||cus.DOB||' '||cus.DPHONE||' '||cus.EMAIL);
END LOOP;
END;
I am reading a lot of stuff about repeating Select statements within a loop but I am having some difficulties as I have not found something clear till now. I want to execute some queries (Select queries) several times, like in a FOR loop. Can anyone help with some example please?
The basic structure of what you are asking can be seen below.
Please provide more information for a more specific code sample.
DECLARE
l_output NUMBER;
BEGIN
FOR i IN 1..10 LOOP
SELECT 1
INTO l_output
FROM dual;
DBMS_OUTPUT.PUT_LINE('Result: ' || l_output);
END LOOP;
END;
PS: If you need to enable output in SQL*Plus, you may need to run the command
SET SERVEROUTPUT ON
UPDATE
To insert your results in another table:
DECLARE
-- Store the SELECT query in a cursor
CURSOR l_cur IS SELECT SYSDATE DT FROM DUAL;
--Create a variable that will hold each result from the cursor
l_cur_rec l_cur%ROWTYPE;
BEGIN
-- Open the Cursor so that we may retrieve results
OPEN l_cur;
LOOP
-- Get a result from the SELECT query and store it in the variable
FETCH l_cur INTO l_cur_rec;
-- EXIT the loop if there are no more results
EXIT WHEN l_cur%NOTFOUND;
-- INSERT INTO another table that has the same structure as your results
INSERT INTO a_table VALUES l_cur_rec;
END LOOP;
-- Close the cursor to release the memory
CLOSE l_cur;
END;
To create a View of your results, see the example below:
CREATE VIEW scott.my_view AS
SELECT * FROM scott.emp;
To view your results using the view:
SELECT * FROM scott.my_view;
I have the following function used to retrieve a batch of Ids from a table. This function is being used as the OFFSET and LIMIT clauses seem to offer poor performance.
CREATE OR REPLACE FUNCTION getBatch(_workloadId VARCHAR, _offSet INT, _limit INT)
RETURNS SETOF NUMERIC AS $$
DECLARE
c SCROLL CURSOR FOR
SELECT id FROM workload WHERE workload_id = $1 ORDER BY id ASC;
BEGIN
OPEN c;
MOVE FORWARD $2 IN c;
RETURN QUERY FETCH FORWARD 10 FROM c;
END;
$$ LANGUAGE plpgsql;
I want the FETCH FORWARDcount to be passed in as a parameter, but I'm unable to find a way to do this. Referencing $3 does not work, I've also tried the following:
EXECUTE 'RETURN QUERY FETCH FORWARD ' || $3 || ' FROM c;';
Any help would be greatly appreciated.
You are confusing SQL cursors with PL/pgSQL cursors, which are similar but not the same. In particular, there is no FETCH FORWARD count in PL/pgSQL:
The direction clause can be any of the variants allowed in the
SQL FETCH command except the ones that can fetch more than one
row; namely, it can be NEXT, PRIOR, FIRST, LAST, ABSOLUTE
count, RELATIVE count, FORWARD, or BACKWARD.
In PL/pgSQL you can only fetch one row at a time and process (or return) it.
There is also a disclaimer in the manual:
Note: This page describes usage of cursors at the SQL command level. If you are trying to use cursors inside a PL/pgSQL function,
the rules are different.
You could open that cursor in PL/pgSQL and loop to return rows. But that's only relevant if you want to fetch multiple distinct pieces from a big cursor to save overhead. Else a plain FOR loop (with automatic cursor) or a simple SELECT with OFFSET and LIMIT are certainly faster. Cursors are primarily meant to be returned to and used by the client:
CREATE OR REPLACE FUNCTION getbatch_ref(_cursor refcursor, _workload_id text)
RETURNS refcursor
LANGUAGE plpgsql AS
$func$
BEGIN
OPEN $1 SCROLL FOR
SELECT id
FROM workload
WHERE workload_id = $2
ORDER BY id;
RETURN $1;
END
$func$;
You can use this function in SQL:
BEGIN;
SELECT getbatch_ref('c', 'foo');
MOVE FORWARD 10 IN c;
FETCH FORWARD 10 FROM c;
ROLLBACK; -- or COMMIT;
You could also just use plain SQL:
BEGIN;
DECLARE c SCROLL CURSOR FOR
SELECT id
FROM workload
WHERE workload_id = 'foo'
ORDER BY id;
-- OPEN c; -- only relevant in plpgsql
-- The PostgreSQL server does not implement an OPEN statement for cursors;
-- a cursor is considered to be open when it is declared.
MOVE FORWARD 10 IN c;
FETCH FORWARD 10 FROM c;
ROLLBACK; -- or COMMIT;