I have a function:
CREATE OR REPLACE FUNCTION numOfOrders(name VARCHAR2) RETURN sys_refcursor AS
test_cur sys_refcursor;
BEGIN
OPEN test_cur for
SELECT C_CustKey, C_Name, COUNT(*) AS Num_Of_Orders
FROM Customer c
INNER JOIN Orders o
ON o.O_CustKey = c.C_CustKey
WHERE C_Name = name
GROUP BY C_CustKey, C_Name;
RETURN test_cur;
END;
/
the screenshot of the code :
and i called it by typing
SELECT numOfOrders('john') FROM customer;
I get my intended output:
C_CustKey C_Name Num_Of_Orders
0001 john 10
but it gets repeated for the number of rows in my customer table, i can fix this by adding fetch first 1 rows only; but how do I prevent it from happening.
Link to output (I can assure you all 1214 rows are the same) :
Tables
Customer :
Orders :
To cut a long story short: this is wrong:
select numOfOrders('john') from customer;
--------
this
because you're selecting the same dataset for all rows in customer table. What would be better? This:
select numOfOrders('john') from dual;
----
this
Why dual? Because it contains only one row.
A simple example based on Scott's schema (I don't have your data): there are 14 employees in the whole emp table, some of them working in department 10.
Function, similar to yours:
SQL> create or replace function num_of_employees (p_deptno in dept.deptno%type)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select d.dname,
8 count(*) as num_of_employees
9 from dept d join emp e on e.deptno = d.deptno
10 where d.deptno = p_deptno
11 group by d.dname;
12 return l_rc;
13 end;
14 /
Function created.
This is what you should do - select from dual; the result is just a single line:
SQL> select num_of_employees(10) from dual;
NUM_OF_EMPLOYEES(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3 --> this is the result
SQL>
But, if you select from emp (which has 14 rows), you'll get 14 duplicate rows as the result:
SQL> select num_of_employees(10) from emp;
NUM_OF_EMPLOYEES(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
<snip>
14 rows selected.
SQL>
You asked whether it'll work (the dual table) if function returns more than a single row: it will.
I modified the function so that - if no department is passed to the function - it returns number of employees in all departments (i.e. multiple rows):
SQL> create or replace function num_of_employees (p_deptno in dept.deptno%type)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select d.dname,
8 count(*) as num_of_employees
9 from dept d join emp e on e.deptno = d.deptno
10 where d.deptno = p_deptno or p_deptno is null --> modified WHERE clause
11 group by d.dname;
12 return l_rc;
13 end;
14 /
Function created.
Calling the function:
SQL> select num_of_employees(null) from dual;
NUM_OF_EMPLOYEES(NUL
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
RESEARCH 5
SALES 6
SQL>
Related
I've created a variable IDS TABLECLIENT.ID&type; and i fill this var. with:
OPEN V_ID;
LOOP
FETCH V_ID INTO IDS;
EXIT WHEN V_ID%NOTFOUND;
END LOOP;
CLOSE V_ID;
This works fine. it stores 5 id clients but when i use this in a select statement, i'm waiting 5 registers but i only get 1:
SELECT *
FROM TABLECLIENT
WHERE ID IN IDS;
Maybe i have to loop ids inside the statement? please help oracle friends
IDS - at a moment - contains only one row fetched by the cursor.
For example, this is table of departments in Scott's sample schema:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
This code simulates what you have (though, it would have been better if you posted that info); cursor selects department numbers and - later - uses that value in another query.
SQL> set serveroutput on
SQL> declare
2 cursor v_id is select deptno from dept;
3 ids dept.deptno%type;
4 l_cnt number;
5 begin
6 open v_id;
7 loop
8 fetch v_id into ids;
9 exit when v_id%notfound;
10
11 -- display IDS's contents:
12 dbms_output.put_line('Department ' || ids);
13
14 -- you can do "something" with that value; for example,
15 -- count employees who work in that department
16 select count(*)
17 into l_cnt
18 from emp
19 where deptno = ids;
20 dbms_output.put_line('...Number of employees in DEPT ' || ids ||
21 ' = ' || l_cnt);
22 end loop;
23 close v_id;
24 end;
25 /
Result is:
Department 10 --> this is value fetched in the 1st loop round
...Number of employees in DEPT 10 = 3
Department 20 --> fetched in the 2nd round
...Number of employees in DEPT 20 = 5
Department 30
...Number of employees in DEPT 30 = 6
Department 40
...Number of employees in DEPT 40 = 0
PL/SQL procedure successfully completed.
SQL>
So I have a pl/sql function and I want to select and update table inside open for statement
It looks like this:
table_a
id | status | document_id |
create or replace function a(p_document_id in number)
return sys_refcursor
is
result sys_refcursor;
begin
open result for
select id
from table_a t
where t.document_id = p_document_id;
update table_a t
set t.status = 1
where id in (select id
from table_a t
where t.document_id = p_document_id);
return result;
end;
/
But it is not working. Is there some method to do this? thanks in advance
This is a sample table:
SQL> select * from table_a;
ID DOCUMENT_ID STATUS
---------- ----------- ----------
1 100 0
Function you wrote compiles, but doesn't work because functions - normally - don't do DML:
SQL> select a(100) from dual;
select a(100) from dual
*
ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "SCOTT.A", line 11
SQL>
It, though, would work from PL/SQL, e.g.
SQL> declare
2 l_rc sys_refcursor;
3 begin
4 l_rc := a(100);
5 end;
6 /
PL/SQL procedure successfully completed.
SQL> select * from table_a;
ID DOCUMENT_ID STATUS
---------- ----------- ----------
1 100 1
SQL>
But, if you want to be able to call the function from PL/SQL, it has to be an autonomous transaction, which also means that you have to commit within:
SQL> rollback;
Rollback complete.
SQL> create or replace function a(p_document_id in number)
2 return sys_refcursor
3 is
4 result sys_refcursor;
5 pragma autonomous_transaction; --> this
6 begin
7 open result for
8 select id
9 from table_a t
10 where t.document_id = p_document_id;
11
12 update table_a t
13 set t.status = 1
14 where id in (select id
15 from table_a t
16 where t.document_id = p_document_id);
17
18 commit; --> this
19
20 return result;
21 end;
22 /
Function created.
Let's try it:
SQL> select a(100) from dual;
A(100)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ID
----------
1
SQL> select * from table_a;
ID DOCUMENT_ID STATUS
---------- ----------- ----------
1 100 1
SQL>
This might, or might not be OK. Autonomous transactions can be tricky and we usually use them for logging purposes, so that their commit doesn't affect the main transaction.
Need help understanding how to use a cursor variable in a procedure. And use the anonymous block to call the procedure 6 times to run a set of queries. Trying to figure this out have been giving me a headache, thank you in advance for any help.
CREATE OR REPLACE PROCEDURE City_Jail_SP
(lv_query IN NUMBER,
lv_out out VARCHAR2)
AS
cursor qry_lvl IS
BEGIN
OPEN qry_lvl;
LOOP
FETCH INTO
IF chioce = 1 THEN SELECT AVG(COUNT(*))
FROM crime_officers
GROUP BY officer_id;
ELSIF chioce = 2 THEN SELECT MIN(Fine_amount)
FROM Crime_charges;
ELSIF chioce = 3 THEN COLUMN (hearing_date-date_charged) HEADING DAYS
SELECT crime_id, Classification, date_charged, hearing_date,
( hearing_date-date_charged)
FROM crimes
WHERE hearing_date-date_charged >14;
ELSIF choice = 4 THEN select cl.criminal_id, cl.last, cl.first, cc.Crime_code, cc.Fine_amount
FROM criminals cl
JOIN crimes cr
ON cr.criminal_id = cl.criminal_id
JOIN crime_charges cc
ON cc.crime_id = cr.crime_id;
ELSIF chioce = 5 THEN SELECT LAST, FIRST
FROM officers JOIN crime_officers USING (officer_id)
JOIN crimes USING (crime_id)
GROUP BY (LAST, FIRST)
HAVING COUNT(crime_id)>(SELECT AVG(COUNT(crime_id))
FROM crimes JOIN crime_officers using (crime_id)
GROUP BY officer_id);
ELSIF choice = 6 THEN SELECT DISTINCT FIRST, LAST
FROM criminals JOIN crimes USING (criminal_id)
JOIN crime_charges USING (crime_id)
GROUP BY (FIRST, LAST)
HAVING COUNT(Criminal_ID)<(SELECT AVG(COUNT(Criminal_ID))
FROM crimes JOIN criminals USING (Criminal_ID)
GROUP BY Criminal_ID)
ORDER BY FIRST, LAST;
END IF;
close qry_lvl;
END;
/
It looks as if code you wrote should be shift-deleted so that you could start over.
you started to declare a cursor, but never finished it
you didn't declare any cursor variable to store cursor's values into it
anyway, you'd rather switch to a cursor FOR loop as it is simpler to use
you're fetching into ... what?
there's no end loop
you declared IN and OUT parameters, but never used any of them
there's bunch of select statements in IF, almost all of them returning different data set. Also, there's no into clause in any of them. PL/SQL requires it, so you'll have to declare quite a few local variables
typos, typos, typos ... is it chioce or choice?
what is that column ... heading days thing supposed to do? Sounds like something related to SQL*Plus
Maybe you'd actually want to use refcursors. Here's a simple example based on Scott's schema. Depending on department number, I'm returning different data set.
Sample data:
SQL> SELECT * FROM dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Procedure (kind of simulates what you tried to do):
SQL> CREATE OR REPLACE PROCEDURE p_test (par_deptno IN NUMBER,
2 par_rc OUT SYS_REFCURSOR)
3 IS
4 l_dname dept.dname%TYPE;
5 BEGIN
6 SELECT dname
7 INTO l_dname
8 FROM dept
9 WHERE deptno = par_deptno;
10
11 IF l_dname = 'ACCOUNTING'
12 THEN
13 OPEN par_rc FOR SELECT d.dname, e.ename, e.job
14 FROM dept d
15 JOIN emp e
16 ON e.deptno = d.deptno
17 AND d.deptno = par_deptno;
18 ELSIF l_dname = 'SALES'
19 THEN
20 OPEN par_rc FOR SELECT e.ename, e.job, e.sal
21 FROM emp e
22 WHERE e.job = 'SALESMAN';
23 END IF;
24 END;
25 /
Procedure created.
Testing:
SQL> var l_rc refcursor
SQL> exec p_test(10, :l_rc);
PL/SQL procedure successfully completed.
SQL> print :l_rc
DNAME ENAME JOB
-------------- ---------- ---------
ACCOUNTING CLARK MANAGER
ACCOUNTING KING PRESIDENT
ACCOUNTING MILLER CLERK
SQL> exec p_test(30, :l_rc);
PL/SQL procedure successfully completed.
SQL> print :l_rc
ENAME JOB SAL
---------- --------- ----------
ALLEN SALESMAN 1600
WARD SALESMAN 1250
MARTIN SALESMAN 1250
TURNER SALESMAN 1500
SQL>
Please, read some basic PL/SQL documentation, you can't start coding and making up your own syntax. This is Oracle 12c PL/SQL Language Reference. It will take you some time to read it, but - at least - you'll know what you're doing (I hope so).
I am trying to run some SQL queries on Oracle database, but before running the query I need to check if both table and column exists. If table exists and column does not exist, then run another query:
if table `testtable` exists and if table has column `testcolumn`
Run a SQL which returns the result
else if table `testtable` exists but column `testcolumn` not present
Run a different sql which also returns the result
else
print some defined string
You can use:
DECLARE
nCount NUMBER;
BEGIN
SELECT COUNT(*)
INTO nCount
FROM USER_TAB_COLS
WHERE TABLE_NAME = 'TESTTABLE' AND
COLUMN_NAME = 'TESTCOLUMN';
IF nCount > 0 THEN
-- Run a SQL which returns the result
ELSE
SELECT COUNT(*)
FROM USER_TABLES
WHERE TABLE_NAME = 'TESTTABLE';
IF nCount > 0 THEN
Run a different sql which also returns the result
ELSE
print some defined string
END;
You'll have to add code to run whatever SQL you're trying to run, and to print whatever message you need.
Best of luck.
Here's one option - check contents of USER_TAB_COLUMNS and - depending on what you find - use refcursor in order to return the result.
SQL> create or replace function f_test
2 return sys_refcursor
3 is
4 l_cnt number;
5 cur_r sys_refcursor;
6 begin
7 -- 1st test - this one fails
8 select count(*)
9 into l_cnt
10 from user_tab_columns
11 where table_name = 'EMP'
12 and column_name = 'DOES_NOT_EXIST';
13
14 if l_cnt > 0 then
15 open cur_r for select ename, job, sal from emp;
16 end if;
17
18 -- 2nd test - this one is OK
19 select count(*)
20 into l_cnt
21 from user_tab_columns
22 where table_name = 'DEPT'
23 and column_name = 'DEPTNO';
24
25 if l_cnt > 0 then
26 open cur_r for select dname, loc from dept;
27 end if;
28
29 return cur_r;
30 end;
31 /
Function created.
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME LOC
-------------- -------------
ACCOUNTING NEW YORK
RESEARCH DALLAS
SALES CHICAGO
OPERATIONS BOSTON
SQL>
It has to be some kind of a dynamic code because you can't just write a static SELECT statement that selects non-existent columns as you'd get ORA-00904: "DOES_NOT_EXIST": invalid identifier error.
I have the following trigger for update action.
CREATE OR REPLACE TRIGGER oferta_trigger
BEFORE UPDATE ON oferty
FOR EACH ROW
DECLARE
id_oferta number (10);
id_komis number (10);
id_test_komis number (10);
suma decimal(10,2) :=0;
CURSOR c1 IS
SELECT cena_aktualna, id_test_komis
FROM oferty
WHERE status = 'A';
BEGIN
id_oferta := :NEW.idk;
dbms_output.put_line(id_oferta);
SELECT komis_id INTO id_komis FROM oferty WHERE idk = id_oferta;
FOR i in c1 LOOP
IF i.id_test_komis = id_komis THEN
suma := suma + i.cena_aktualna;
END IF;
END LOOP;
UPDATE komisy SET wartosc_samochodow = suma WHERE idk = id_komis;
END;
During update operation.
UPDATE oferty SET status ='Z' WHERE idk =1;
I get the following error:
SQL Error: ORA-04091: table OFERTY is mutating, trigger/function may
not see it.
How to solve the problem. I think this is problem of getting id.
Here's an example based on Scott's schema. I altered the DEPT table, adding a new column (SUM_SAL) which is supposed to contain sum of all salaries in that department.
First, the good, old mutating table way.
SQL> create or replace trigger trg_sumsal
2 after update on emp
3 for each row
4 declare
5 l_sum number;
6 begin
7 select sum(sal) into l_sum
8 from emp
9 where empno = :new.empno;
10
11 update dept set sum_sal = l_sum
12 where deptno = :new.deptno;
13 end;
14 /
Trigger created.
SQL> update emp set sal = 5000 where ename = 'KING';
update emp set sal = 5000 where ename = 'KING'
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_SUMSAL", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_SUMSAL'
SQL>
As we already knew, that won't work.
Now, the compound trigger:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 l_deptno emp.deptno%type;
6
7 after each row is
8 begin
9 l_deptno := :new.deptno;
10 end after each row;
11
12 after statement is
13 l_sum number;
14 begin
15 select sum(sal) into l_Sum
16 from emp
17 where deptno = l_deptno;
18
19 update dept set sum_sal = l_sum
20 where deptno = l_deptno;
21 end after statement;
22 end;
23 /
Trigger created.
SQL> update emp set sal = 10000 where ename = 'KING';
1 row updated.
SQL> select * From dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Nice; that works!
[EDIT, after reading William's comment]
If several departments are affected within a single UPDATE statement, the above code won't work properly. Slightly adjusted, it looks like this & fixes that issue:
SQL> create or replace trigger trg_sumsal
2 for update or insert on emp
3 compound trigger
4
5 type t_tab is table of number;
6 l_tab t_tab := t_tab();
7
8 after each row is
9 begin
10 l_tab.extend;
11 l_tab(l_tab.last) := :new.deptno;
12 end after each row;
13
14 after statement is
15 l_sum number;
16 begin
17 for i in l_tab.first .. l_tab.last loop
18 select sum(sal) into l_Sum
19 from emp
20 where deptno = l_tab(i);
21
22 update dept set sum_sal = l_sum
23 where deptno = l_tab(i);
24 end loop;
25 end after statement;
26 end;
27 /
Trigger created.
Testing:
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> update emp set sal = 10000 where ename in ('SMITH', 'KING');
2 rows updated.
SQL> select * from dept;
DEPTNO DNAME LOC SUM_SAL
---------- -------------- ------------- ----------
10 ACCOUNTING NEW YORK 13750
20 RESEARCH DALLAS 15975
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>