I have a select statement with a group by statement let's call it <SGB> (there are more elements in the select, but the error message refers only to the group by).
The following works without error:
<SGB>;
but when I use this statement with an explicit cursor as such:
DECLARE CURSOR MMM IS
SGB;
BEGIN
FOR MCR in MMM
LOOP
DBMS_OUTPUT.PUT_LINE('blah');
END LOOP;
END;
I get the error message ORA-00979: not a GROUP expression
I could not find it, but is there some limitation for GROUP BY when in a cursor definition?
EDIT: here is a simplified SGB:
select
A, to_date(B, 'format') BDATE,
C, D, E, F
from TB_001
pivot
(
MAX(VAL)
for NAM
in ('C' C, 'D' D, 'E' E, 'F' F)
)
group by
A, to_date(B, 'format'), C, D, E, F
having
to_date(B, 'format') = select( max(to_date(B, 'format')) from from TB_001 )
and D=1
;
Another way to say it is that I have a vertical table of parameters, and parameters values for procedures, together with a date. I need to put it in a horizontal way and keep only a subset of the relevant procedures. I wanted to have a cursor so that I can in the loop exec the procedure with the relevant parameters, and potentially save an execution status in another table.
Not really a nice solution, but I got around it by saving the select in a temporary table and making the cursor on the temporary table.
create table TEMP_T as SGB;
declare cursor MMM is
select * from TEMP_T;
begin
for MCR in MMM
loop
DBMS_OUTPUT.PUT_LINE('blah');
end loop;
EXECUTE IMMEDIATE 'drop table TEMP_T';
end;
Related
I'm trying to enter data from table Towns(column place) to table F (column b).
and I would like to use (select * from Towns) in cursor but it is throwing an error.
Instead, if I use a (select place from towns) - my program works.
I want to incorporate (select * from Towns).
DECLARE
z_towns towns.place%TYPE;
CURSOR abc IS
(SELECT place FROM towns);
BEGIN
OPEN abc;
LOOP
FETCH abc INTO z_towns;
EXIT WHEN abc%NOTFOUND;
INSERT INTO F
(b)
VALUES(z_towns);
END LOOP;
CLOSE abc;
END;
You'll have to declare a different "target" variable - not just for the place column, but all columns in the towns table. Or, even better, a cursor variable which inherits type of the whole cursor return value (abc%rowtype):
DECLARE
CURSOR abc IS
SELECT * FROM towns;
abc_r abc%rowtype;
BEGIN
OPEN abc;
LOOP
FETCH abc INTO abc_r;
EXIT WHEN abc%NOTFOUND;
INSERT INTO F (b) VALUES(abc_r.place);
END LOOP;
CLOSE abc;
END;
That can be shortened if you use a cursor FOR loop as Oracle in that case does most of the job for you:
BEGIN
FOR abc IN (SELECT * FROM towns) LOOP
INSERT INTO F (b) VALUES(abc.place);
END LOOP;
END;
Certainly, the simplest (and fastest, because cursor's row-by-row is slow-by-slow and performance will suffer if there's a lot of data you're dealing with) option is to directly insert rows into the target table:
INSERT INTO F (b) SELECT place FROM towns;
I am new to Oracle programming (started a month ago).
I've created a cursor to retrieve a value from a table 'CDF_LU' and then use the cursor to insert into another table 'test_1'. However there is an error when I run it.
Here is my code:
DECLARE
c_cdf_table CDF_LU.PROD_COLUMN_NAME%type;
-- create cursor.
CURSOR c_CDF_Table_Name IS
SELECT PROD_COLUMN_NAME
FROM CDF_LU
ORDER BY CDF;
-- create record.
c_cdf_table c_CDF_Table_Name%ROWTYPE;
BEGIN
OPEN c_CDF_Table_Name;
LOOP
FETCH c_CDF_Table_Name INTO c_cdf_table;
EXIT WHEN c_CDF_Table_Name%NOTFOUND;
-- insert to table_1.
INSERT into test_1
select A,B,C from table_1 where some_conditions
END LOOP;
CLOSE c_CDF_Table_Name;
END;
When I run this code, there are following errors:
In line 'FETCH c_CDF_Table_Name INTO c_cdf_table;', SQL statement ignored.
In line 'FETCH c_CDF_Table_Name INTO c_cdf_table;', at most one declaration for "C_CDF_TABLE" is permitted.
In line 'INSERT into test_1', SQL statement ignored.
I wrote the SQL codes above by strictly following the syntax of cursors, so I'm not sure where the problem is.
Could you please advise? Thank you!
The way you wanted to do it is possible (of course) when errors are fixed; something like this:
declare
-- cursor
cursor c_cdf_table_name is
select prod_column_name
from cdf_lu
order by cdf;
-- cursor variable
c_cdf_table c_cdf_table_name%rowtype;
begin
open c_cdf_table_name;
loop
fetch c_cdf_table_name into c_cdf_table;
exit when c_cdf_table_name%notfound;
insert into test1 (col1, col2, co3)
select a, b, c from table1
where d = c_cdf_table.prod_column_name;
end loop;
close c_cdf_table_name;
end;
/
However, there's a way shorter & simpler option - a cursor FOR loop. As you can see, you don't have to declare a cursor variable, open the cursor, fetch from it, take care about exiting the loop nor closing the cursor - Oracle does all that for you:
begin
for cur_r in (select prod_column_name
from cdf_lu
order by cdf)
loop
insert into test1 (col1, col2, co3)
select a, b, c from table1
where d = c_cdf_table.prod_column_name;
end loop;
end;
/
In my PL/SQL script, how do I declare JUSTIFIC_REC when it represents a join?
SELECT *
INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
All I want is to insert into a TABLE3 a concatenated row:
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
How should the declaration of JUSTIFIC_REC be like in the beginning of my script?
If it wasn't for the INNER JOIN , I would write something like:
JUSTIFIC_rec TABLE1%ROWTYPE;
If I understood correctly you can try with cursor rowtype like this (not sure if that is what you meant by declaring your variable type for the select with joins):
set serveroutput on;
declare
cursor cur is
SELECT ENTRY_COMMENTS, DESCRIPTION
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec cur%ROWTYPE;
begin
open cur;
loop
fetch cur into justific_rec;
exit when cur%notfound;
dbms_output.put_line(justific_rec.entry_comments || ' ' || justific_rec.description);
end loop;
close cur;
end;
Answer to your question is itself in your question. You have to use the %row type
tow type attribute can be any of the below type:
rowtype_attribute :=
{cursor_name | cursor_variable_name | table_name} % ROWTYPE
cursor_name:-
An explicit cursor previously declared within the current scope.
cursor_variable_name:-
A PL/SQL strongly typed cursor variable, previously declared within the current scope.
table_name:-
A database table or view that must be accessible when the declaration is elaborated.
So code will looks like
DECLARE
CURSOR c1 IS
SELECT * FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec c1%ROWTYPE;
BEGIN
open c1;
loop
fetch c1 into justific_rec;
exit when c1%notfound;
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
end loop;
close c1;
END;
/
You need to use BULK COLLECT INTO
SELECT *
BULK COLLECT INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
The JUSTIFIC_REC is needed to be TABLE type with appropriate columns
If all you're wanting to do is insert into a table based on a select statement, then there's no need for PL/SQL (by which I mean there's no need to open a cursor, fetch a row, process the row and then move on to the next row) - that's row-by-row aka slow-by-slow processing.
Instead, you can do this all in a single insert statement, e.g.:
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
If you wanted to embed this insert statement in PL/SQL, all you need to do is add a begin/end around it, like so:
begin
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
end;
/
This is the preferred solution when working with databases - think in sets (where possible) not procedurally (aka row-by-row processing). It is easier to maintain, the code is simpler to read and write, and it'll be more performant since you're not having to switch between PL/SQL and SQL several times with each row.
Context switching is bad for performance - think in terms of a bath full of water - is it quicker to empty the bath with a spoon (row-by-row processing), with a jug (batched rows - by - batched rows) or by pulling the plug (set-based)?
When I try to run procedure printshipment I get an error
PLS-00341: declaration of cursor 'C' is incomplete or malformed
What is wrong with my cursor declaration and how to fix it?
CREATE OR REPLACE PROCEDURE printshipment(onmbr IN shipment.onum%TYPE,
shnmbr IN shipment.snum%TYPE)
IS
CURSOR c IS
SELECT
shcontent.inum ino,
item.descr description,
item.qtyshipped q,
item.unitprice u,
u * q cost
FROM shcontent, item
WHERE shcontent.snum = shnmbr
AND shcontent.onum = onmbr
AND shcontent.inum = item.inum;
rec c%ROWTYPE;
BEGIN
OPEN c;
FETCH c INTO rec;
IF c%NOTFOUND THEN
dbms_output.put_line('No Shipment');
END IF;
CLOSE c;
END;
/
I think if you want to include variable parameters in the cursor's query you have to declare it as a parameterized cursor, like this:
CURSOR C (c_onmbr IN Shipment.onum%type, c_shnmbr IN Shipment.snum%type)
IS
SELECT ShContent.inum Ino, Item.descr description, Item.Qtyshipped Q,
Item.UnitPrice U, U * Q COST
FROM ShContent, Item
WHERE ShContent.snum = c_shnmbr
AND ShContent.onum = c_onmbr
AND ShContent.inum = Item.inum;
and then
OPEN C(onmbr, shnmbr);
I may be mistaken, though; my Oracle certs are a bit out-of-date.
Column aliases cannot be reference in the same SELECT list. Unless "q" and "u" are also columns in the tables, this part of the statement is invalid:
item.qtyshipped q,
item.unitprice u,
u * q cost
Either replace u * q with item.qtyshipped * item.unitprice or wrap the SELECT statement in another inline view and then reference the aliases. Also, there is probably some more information in the error message(s). Always post the entire error message when there's a problem.
SQL Server is able to return the results of multiple queries in a single round-trip, e.g:
select a, b, c from y;
select d, e, f from z;
Oracle doesn't like this syntax. It is possible to use reference cursors, like this:
begin
open :1 for select count(*) from a;
open :2 for select count(*) from b;
end;
However, you incur a penalty in opening/closing cursors and you can hold database locks for an extended period. What I'd like to do is retrieve the results for these two queries in one shot, using Odp.net. Is it possible?
In Oracle, reference cursor is a pointer to data, rather than data itself.
So if a procedure returns two reference cursors, the the client still has to go and fetch the rows from those cursors (and incur the network hits).
As such, if the data volumes are small, you probably want to call a procedure that just returns the values.
If the data volumes are large (thousands of rows) then it won't be a single network trip anyway, so an extra one or two as you switch between cursors isn't going to make much difference.
Another choice is have a single select return all the rows. That might be a simple UNION ALL
select a, b, c from y union all select d, e, f from z;
It could be a pipelined table function
create or replace package test_pkg is
type rec_two_cols is record
(col_a varchar2(100),
col_b varchar2(100));
type tab_two_cols is table of rec_two_cols;
function ret_two_cols return tab_two_cols pipelined;
end;
/
create or replace package body test_pkg is
function ret_two_cols return tab_two_cols pipelined
is
cursor c_1 is select 'type 1' col_a, object_name col_b from user_objects;
cursor c_2 is select 'type 2' col_a, object_name col_b from user_objects;
r_two_cols rec_two_cols;
begin
for c_rec in c_1 loop
r_two_cols.col_a := c_rec.col_a;
r_two_cols.col_b := c_rec.col_b;
pipe row (r_two_cols);
end loop;
for c_rec in c_2 loop
r_two_cols.col_a := c_rec.col_a;
r_two_cols.col_b := c_rec.col_b;
pipe row (r_two_cols);
end loop;
return;
end;
end;
/
select * from table(test_pkg.ret_two_cols);
I believe the most recent versions of ODP for 11g allow user-defined types which may help.