Declare a cursor to use it after begin - oracle

I have a project where i have to check if how many products are left to sell from a company for certain clients. A client can have many companies, also i have to refuse when a client or a product don't exist.
I have a table where i have clients and companies, then i got another where i have companies and products. I'm trying to create cursors to manage on the "companie-product" table, just the companies that the client can buy from, but when i declare a cursor it says "it's not a cursor" i've tryed the %rowtype, also the %type, and neither work. Does anyone know how i can fix it?
CREATE OR REPLACE PROCEDURE Transaccion
IS
my_cantidaRechazada NUMBER;
CURSOR peti
IS
SELECT idCliente, idProducto, cantidadPedido
FROM TABLAS_COMP.peticiones
ORDER BY idCliente ASC;
CURSOR empresaCliente
IS
SELECT *
FROM TABLAS_CL.empresacliente
ORDER BY tablas_cl.empresacliente.empresa;
empC empresaCliente%ROWTYPE;
CURSOR empresaProducto (vempresa NUMBER)
IS
SELECT *
FROM TABLAS_PRO.empresaproducto
WHERE empresa = vempresa
ORDER BY tablas_pro.empresaproducto.empresa ASC;
empP empresaProducto%ROWTYPE;
BEGIN
FOR cont IN peti
LOOP
IF existecliente (cont.idCliente) = 0
THEN
my_cantidaRechazada := cont.cantidadPedido;
INSERT INTO TABLAS_COMP.rechazados
VALUES (cont.idCliente,
cont.idProducto,
cont.cantidadPedido,
my_cantidaRechazada);
ELSIF existeproducto (cont.idProducto) = 0
THEN
my_cantidaRechazada := cont.cantidadPedido;
INSERT INTO TABLAS_COMP.RECHAZADOS
VALUES (cont.idCliente,
cont.idProducto,
cont.cantidadPedido,
my_cantidaRechazada);
END IF;
SELECT *
INTO empC
FROM TABLAS_CL.empresacliente
WHERE Cliente = cont.idCliente
ORDER BY empresa ASC;
FOR empP IN empC
LOOP -- (here's where the error is showed, it says "empC it's not a cursor")
SELECT *
INTO empP
FROM TABLAS_PRO.empresaproducto
WHERE Empresa = empC.Empresa;
END LOOP;
END LOOP;
END Transaccion;
Thanks in advance, David.

It would help if you specified oracle error exactly, along with the ORA-XXXXX error code. Saying it in your words can be ambiguous.
Anyway: what you indicated is wrong, really. Should be
-- wrong: for empP in empC loop
for empP in empresaProducto loop
...
end loop;
because empP is declared as empresaProducto cursor variable.
Though, SELECT you put into the loop is meaningless - you already have empP, so - why would you insert into it again? Think it over.

Related

PL/SQL Cursor whose query is based on a variable

I've got a PL/SQL block that's basically
DECLARE
PIDM NUMBER(8);
CLM_TEST_SCORE NUMBER(5);
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = pidm;
BEGIN
OPEN C_STUDENT;
LOOP
CLM_TEST_SCORE := '';
FETCH c_Student INTO pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES;
FETCH C_CLM_SCORES INTO CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (CLM_TEST_SCORE);
END LOOP
END
As far as I'm aware, the pidm referred to in C_CLM_SCORES is the PIDM NUMBER(8) declared in line 2. That would mean that the query the cursor refers to mutates every iteration of the LOOP, depending on the current value of pidm. That doesn't jive with my understanding of cursors as a query-in-progress, as the underlying query changes every LOOP. Maybe it's the original author taking advantage of a clever DB algorithm?
This code works. I just have absolutely no idea why. What the heck is going on here?
You have an overly confusing block of code that is a nightmare to debug as you have:
SQL statements that refer to column name and local variables with the same identifier (PIDM and CLM_TEST_SCORE).
Cursors that change every iteration because they contain a bind variable referring to local variables (PIDM).
Highly inefficient use of loops.
If you want to make it clearer, you can rewrite the PL/SQL block so that you do not have duplicate identifiers and use a parameterised cursor:
DECLARE
v_PIDM SOSC.DW_ALL_COLLECTOR.PIDM%TYPE;
v_CLM_TEST_SCORE some_table.CLM_TEST_SCORE%TYPE;
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES(p_pidm NUMBER) IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = p_pidm;
BEGIN
OPEN C_STUDENT;
LOOP
FETCH c_Student INTO v_pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES(v_pidm);
FETCH C_CLM_SCORES INTO v_CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (v_CLM_TEST_SCORE);
END LOOP;
END;
/
However, that is still very inefficient as each iteration performs a SELECT and an INSERT and will generate log entries. You can make it much simpler and more efficient to rewrite the whole thing as a single SQL statement:
INSERT INTO some_table (clm_test_score)
SELECT ( select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST s
where s.SORTEST_PIDM = c.pidm )
FROM SOSC.DW_ALL_COLLECTOR c;
db<>fiddle here
The code in the question is an advertisement for "Why should implicit cursors be used?". If you rewrite your code as below it becomes much easier to understand:
BEGIN
FOR rowStudent IN (select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM)
LOOP
FOR rowScores IN (select max(to_number(SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
from SATURN.SORTEST
where SORTEST_PIDM = rowStudent.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowScores.CLM_TEST_SCORE);
END LOOP; -- rowScores
END LOOP; -- rowStudent
END;
This eliminates all of the variables and cursor definitions, and all the code is right in front of you where you can see it at a glance.
If you wanted to tighten it up a bit further you could use a join to get down to just one cursor:
BEGIN
FOR rowStudent_scores IN (SELECT d.PIDM, MAX(TO_NUMBER(s.SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
FROM SOSC.DW_ALL_COLLECTOR d
INNER JOIN SATURN.SORTEST s
ON s.SORTEST_PIDM = d.PIDM
GROUP BY d.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowStudent_scores.CLM_TEST_SCORE);
END LOOP; -- rowStudent_scores
END;

Getting the corresponding record of a Cursor/Select when in a Cursor LOOP statement

The question seems easy. I have built a package, where there is a quite massive cursor, let's say on all invoices of my company for the whole year.
CURSOR c_invoices(p_year IN INTEGER) IS
SELECT all_invoices.invoicenumber,
all_invoices.invoicedate,
all_invoices.customernumber
FROM all_invoices
WHERE all_invoices.year = p_year
;
After opening it and using a LOOP statement, I want to get some data from another table (forbidden_customers), but only if the customer is in this very last table.
What I'd like to do, is to open another cursor (or a SELECT ?) at the very beginning of my package, browsing the whole table(forbidden_customers), and then getting to the corresponding record when in my invoices LOOP.
So, something like :
CURSOR c_forbidden_customers IS
SELECT forbidden_customers.customernumber,
forbidden_customers.customeradress
FROM forbidden_customers
;
And then :
OPEN c_invoices(v_year);
LOOP FETCH c_invoices INTO invoices_cursor;
BEGIN
EXIT WHEN c_invoices%NOTFOUND;
*IF invoices_cursor.customernumber IS FOUND IN c_forbidden_customers ...
THEN ...*
This is what I do meanwhile (I know it is bad):
SELECT COUNT(*)
INTO v_exist /*INTEGER*/
FROM forbidden_customers
WHERE forbidden_customers.customernumber= p_customernumber
IF v_exist <> 0
THEN...
I tried to make it as clear as possible. Thank you for your time
Don't do it twice; join both tables in the same cursor and use it. Also, if you switch to a cursor FOR loop, you'll save yourself from some typing as Oracle will do most of boring stuff for you (declaring cursor variable, opening the cursor, closing it, exiting the loop ...):
create or replace procedure p_test (p_year in integer) is
begin
for c_invoices in
(select a.invoicenumber,
a.invoicedate,
a.customernumber,
c.customeraddress
from all_invoices a join forbidden_customers c on c.customernumber = a.customernumber
where a.year = p_year)
loop
-- do something
end loop;
end;
If the table forbidden_customers is not large and it will fit oracle's session memory, you can use a pl/sql table to store all id's from forbidden_customers and check it later. The check is done in memory only, so it is much faster than any regular select.
create table all_invoices
(id number,
year number,
customer_number number);
create table forbidden_customers
(customer_number number);
CREATE OR REPLACE TYPE t_number_table IS TABLE OF number
/
CREATE OR REPLACE PROCEDURE test23
IS
forbidden_customers_list t_number_table;
CURSOR c_invoices (p_year IN INTEGER)
IS
SELECT all_invoices.customer_number
FROM all_invoices
WHERE all_invoices.year = p_year;
BEGIN
SELECT customer_number
BULK COLLECT INTO forbidden_customers_list
FROM forbidden_customers;
FOR rec_invoices in c_invoices(2022) loop
if forbidden_customers_list.exists(rec_invoices.customer_number) then
null;
end if;
end loop;
end;
/

pl/sql loop records select oracle plsql

I have a select statement that I am trying to loop over and increment a variable based on the condition of the select statement, then return the variable as an out so I can do something with it in some front end code. I am using oracle 11g and I am seeing a few ways I can do this... but I am not sure which is the best way. I have some of what I am trying to do below, but again stopped because of confusion.
First I am setting my proc and 'in variable'
PROCEDURE SEEKER (pMonkeyID IN Number, vMarkCounter OUT Number)
AS
BEGIN
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc
OPEN seeker_cur;
begin
OPEN Seeker_cur;
vMarkCounter := 0;
Here is the part I am not sure about. Should I loop and then exit if the condition is not met or should I do an if statement and somehow determine if there is a record that could be greater than one? If so how would that work? Is there a benefit to doing one way over the other? So... I am going to sudo-code what I am trying to do (below):
FOR (however many records) in Seeker_cur
IF seeker_cur (not found) or (returns no records)
EXIT or (break for loop);
ELSE
LOOP
vMarkCounter := vMarkCounter + 1;
EXIT WHEN seeker_cur is out of records (somehow)
END IF;
END LOOP;
END;
END SEEKER;
I am sure there are a few ways to do this. What ways would you suggest?
why dont you use implicit cursor , it will open and close itself:
DECLARE
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc;
vMarkCounter number:=0;
BEGIN
FOR i IN seeker_cur
LOOP
vMarkCounter := vMarkCounter+1;
END LOOP;
dbms_output.put_line(vMarkCounter);
END;
It seems to me that the solution your problem might be as simple as this:
SELECT COUNT(*)
INTO l_some_local_variable
FROM monkey_book_apps
WHERE monkey_id = p_monkey_id
AND monkey_doc_type = 'BANANA';
RETURN l_some_local_variable;
Avoiding PL/SQL loops and using the simplest SQL possible is (almost always) the most efficient way. Tom Kyte calls the row-by-row execution of LOOPs "slow-by-slow".

Reasonable SELECT ... INTO Oracle solution for case of multiple OR no rows

I just want to SELECT values into variables from inside a procedure.
SELECT blah1,blah2 INTO var1_,var2_
FROM ...
Sometimes a large complex query will have no rows sometimes it will have more than one -- both cases lead to exceptions. I would love to replace the exception behavior with implicit behavior similiar to:
No rows = no value change, Multiple rows = use last
I can constrain the result set easily enough for the "multiple rows" case but "no rows" is much more difficult for situations where you can't use an aggregate function in the SELECT.
Is there any special workarounds or suggestions? Looking to avoid significantly rewriting queries or executing twice to get a rowcount before executing SELECT INTO.
Whats wrong with using an exception block?
create or replace
procedure p(v_job VARCHAR2) IS
v_ename VARCHAR2(255);
begin
select ename into v_ename
from (
select ename
from scott.emp
where job = v_job
order by v_ename desc )
where rownum = 1;
DBMS_OUTPUT.PUT_LINE('Found Rows Logic Here -> Found ' || v_ename);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No Rows found logic here');
end;
SQL> begin
p('FOO');
p('CLERK');
end; 2 3 4
5 /
No Rows found logic here
Found Rows Logic Here -> Found SMITH
PL/SQL procedure successfully completed.
SQL>
You could use a for loop. A for loop would do nothing for no rows returned and would be applied to every row returned if there where multiples. You could adjust your select so that it only returns the last row.
begin
for ARow in (select *
from tableA ta
Where ta.value = ???) loop
-- do something to ARow
end loop;
end;

SELECT COUNT(*) vs. fetching twice with an explicit cursor

I have read a book whose title is "Oracle PL SQL Programming" (2nd ed.) by Steven Feuerstein & Bill Pribyl. On page 99, there is a point suggested that
Do not "SELECT COUNT(*)" from a table unless you really need to know the total number of "hits." If you only need to know whether there is more than one match, simply fetch twice with an explicit cursor.
Could you anyone explain this point more to me by providing example? Thank you.
Update:
As Steven Feuerstein & Bill Pribyl recommends us not to use SELECT COUNT() to check whether records in a table exist or not, could anyone help me edit the code below in order to avoid using SELECT COUNT(*) by using explicit cursor instead? This code is written in the Oracle stored procedure.
I have a table emp(emp_id, emp_name, ...), so to check the provided employee ID corret or not:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
There are a number of reasons why developers might perform select COUNT(*) from a table in a PL/SQL program:
1) They genuinely need to know how many rows there are in the table.
In this case there is no choice: select COUNT(*) and wait for the result. This will be pretty fast on many tables, but could take a while on a big table.
2) They just need to know whether a row exists or not.
This doesn't warrant counting all the rows in the table. A number of techniques are possible:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
IF c%FOUND THEN
-- A row exists
...
ELSE
-- No row exists
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop fetching if 1 found
-- At least one row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop counting if 1 found
IF cnt = 0 THEN
-- No row found
ELSE
-- Row found
END IF;
END;
3) They need to know whether more than 1 row exists.
Variations on the techniques for (2) work:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
FETCH c INTO v;
IF c%FOUND THEN
-- 2 or more rows exists
...
ELSE
-- 1 or 0 rows exist
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ... ;
-- Exactly 1 row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
WHEN TOO_MANY_ROWS THEN
-- More than 1 row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM <= 2; -- Stop counting if 2 found
IF cnt = 0 THEN
-- No row found
IF cnt = 1 THEN
-- 1 row found
ELSE
-- More than 1 row found
END IF;
END;
Which method you use is largely a matter of preference (and some religious zealotry!) Steven Feuerstein has always favoured explicit cursors over implicit (SELECT INTO and cursor FOR loops); Tom Kyte favours implicit cursors (and I agree with him).
The important point is that to select COUNT(*) without restricting the ROWCOUNT is expensive and should therefore only be done when a count is trully needed.
As for your supplementary question about how to re-write this with an explicit cursor:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
That would be:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
CURSOR c IS SELECT 1
FROM emp
WHERE emp_id = emp_id_in;
v_dummy INTEGER;
BEGIN
...
OPEN c;
FETCH c INTO v_dummy;
IF c%FOUND > 0 THEN
/* do sth */
END;
CLOSE c;
/* more statements */
...
END do_sth;
But really, in your example it is no better or worse, since you are selecting the primary key and Oracle is clever enough to know that it only needs to fetch once.
If two is all you are interested in, try
SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
SELECT COUNT(*)
FROM TABLE
WHERE ROWNUM < 3
)
It will take less code than doing the manual cursor method,
and it is likely to be faster.
The rownum trick means to stop fetching rows once it has two of them.
If you don't put some sort of limit on the count(*), it could take a long while to finish, depending on the number of rows you have. In that case, using a cursor loop, to read 2 rows from the table manually, would be faster.
This comes from programmers writing code similar to the following (this is psuedo code!).
You want to check to see if the customer has more than one order:
if ((select count(*) from orders where customerid = :customerid) > 1)
{
....
}
That is a terribly inefficient way to do things. As Mark Brady would say, if you want to know if a jar contains pennies, would you count all the pennies in the jar, or just make sure there is 1 (or 2 in your example)?
This could be better written as:
if ((select 1 from (select 1 from orders where customerid = :customerid) where rownum = 2) == 1)
{
....
}
This prevents the "counting all of the coins" dilemma since Oracle will fetch 2 rows, then finish. The previous example would cause oracle to scan (an index or table) for ALL rows, then finish.
He means open a cursor and fetch not only the first record but the second, and then you will know there is more than one.
Since I never seem to need to know that SELECT COUNT(*) is >= 2, I have no idea why this is a useful idiom in any SQL variant. Either no records or at least one, sure, but not two or more. And anyway, there's always EXISTS.
That, and the fact that Oracle's optimizer seems to be pretty poor... - I would question the relevance of the technique.
To address TheSoftwareJedi's comments:
WITH CustomersWith2OrMoreOrders AS (
SELECT CustomerID
FROM Orders
GROUP BY CustomerID
HAVING COUNT(*) >= 2
)
SELECT Customer.*
FROM Customer
INNER JOIN CustomersWith2OrMoreOrders
ON Customer.CustomerID = CustomersWith2OrMoreOrders.CustomerID
Appropriately indexed, I've never had performance problems even with whole universe queries like this in SQL Server. However, I have consistently run into comments about Oracle optimizer problems here and on other sites.
My own experience with Oracle has not been good.
The comment from the OP appears to be saying that full COUNT(*) from tables are not well handled by the optimizer. i.e.:
IF EXISTS (SELECT COUNT(*) FROM table_name HAVING COUNT(*) >= 2)
BEGIN
END
(which, when a primary key exists, can be reduced to a simple index scan - in a case of extreme optimization, one can simply query the index metadata in sysindexes.rowcnt - to find the number of entries - all without a cursor) is to be generally avoided in favor of:
DECLARE CURSOR c IS SELECT something FROM table_name;
BEGIN
OPEN c
FETCH c INTO etc. x 2 and count rows and handle exceptions
END;
IF rc >= 2 THEN BEGIN
END
That, to me would result in less readable, less portable, and less maintainable code.
Before you take Steven Feuerstein's suggestions too serious, just do a little benchmark. Is count(*) noticeably slower than the explicit cursor in your case? No? Then better use the construct that allows for simple, readable code. Which, in most cases, would be "select count(*) into v_cnt ... if v_cnt>0 then ..."
PL/SQL allows for very readable programs. Don't waste that just to nano-optimize.
Depending on the DB, there may be a sys table which stores an approximate count and can be queried in constant time. Useful if you want to know whether the table has 20 rows or 20,000 or 20,000,000.
SQL Server:
if 2 = (
select count(*) from (
select top 2 * from (
select T = 1 union
select T = 2 union
select T = 3 ) t) t)
print 'At least two'
Also, don't ever use cursors. If you think you really really need them, beat yourself with a shovel until you change your mind. Let relics from an ancient past remain relics from an ancient past.
If you want to get number of rows in a table, please don't used count(*), I would suggest count(0) that 0 is the column index of your primary key column.

Resources