Use cursor in LOOP in new QUERY - plsql

"I missing the forest through the trees..."
I want to query each column of a table which I retrieve in a FOR LOOP, but the inner query doesn't return the right thing.
Seems that the inner query not use the current column_name.
DECLARE
v_max_TS TIMESTAMP;
BEGIN
FOR cols IN (SELECT column_name FROM all_tab_cols WHERE table_name = '<tablename>')
LOOP
SELECT
MAX(CURR_TIMESTAMP) INTO v_max_TS
FROM <tablename>
WHERE cols.column_name IS NOT NULL
ORDER BY TO_TIMESTAMP(CURR_TIMESTAMP,'MM/DD/YYYY HH24:MI:SS') DESC;
dbms_output.put_line(cols.column_name || ' ' || v_max_TS);
END LOOP;
END;

Apart from the fact that your query doesn't make much sense (as Boneist wrote as a comment), that won't work as you need to use dynamic SQL (execute immediate) for such a purpose.
Here's an example based on Scott's schema. Have a look, adjust it if necessary.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200); -- will hold the SELECT statement
3 v_max varchar2(30);
4 begin
5 for cols in (select column_name
6 from all_tab_cols
7 where table_name = 'DEPT'
8 )
9 loop
10 l_str := 'select max(' || cols.column_name ||') from dept';
11 execute immediate l_str into v_max;
12 dbms_output.put_line(cols.column_name ||': '|| v_max);
13 end loop;
14 end;
15 /
DEPTNO: 40
DNAME: SALES
LOC: NEW YORK
PL/SQL procedure successfully completed.
SQL>

Related

PL/Sql procedure: my cursor is ordered by date but the output that is written to a table is not ordered the same

Only getting my head around cursor loops and the likes lately, so might be something very simple with my code that's causing the problem
I am using a cursor to spool through customer data to create an xml file. It needs to be sorted by date so that the most recent data is at the bottom of the xml file.
when I run the sql for the cursor, i can see the data is ordered by date. But when I run the entire procedure and check the output, it seems to be ordered by date but on closer inspection some of the records are not in the correct order.
here is the code I'm running. I've omitted a lot of the query as its just xml padding, but I don't think that should make a difference.
the output is written to a table, which i then copy and paste into notepad++. When checking the output table I can see that the order is wrong
drop table recs_xml_output;
create table recs_xml_output (XML_STRING VARCHAR2 (4000 char));
declare
PROCEDURE p_generate_ohmpi_record
IS
lv_string VARCHAR2(10000 CHAR) := NULL;
lv_date_format VARCHAR2(20 CHAR) := 'YYYY-MM-DD';
lv_time_format VARCHAR2(20 CHAR) := 'HH24:MI:SS';
n_id PLS_INTEGER := NULL;
CURSOR c_patient_xml IS
select *
from sbyn_transaction T
where timestamp >= '07-JAN-22 11.58.02.139977000'
and timestamp <= '07-JAN-22 17.51.26.054240000'
ORDER BY TIMESTAMP;
begin
for v_patient_xml in c_patient_xml
loop
lv_string := n_id||'<Person><SourceID>';
lv_string := lv_string||v_patient_xml.lid||'</SourceID><PPSN>'||v_patient_xml.lid||'</PPSN>';
lv_string := lv_string||'<PPSNLastUpdated>';
lv_string := lv_string||TO_CHAR( v_patient_xml.pps_number_updated,lv_date_format )||'T'||TO_CHAR( v_patient_xml.pps_number_updated,lv_time_format)||'</PPSNLastUpdated>';
lv_string := lv_string||'<Birth>';
IF v_patient_xml.date_of_birth IS NOT NULL THEN
lv_string := lv_string||'<DateOfBirth>'||TO_CHAR( v_patient_xml.date_of_birth,lv_date_format )||'T'||TO_CHAR( v_patient_xml.date_of_birth,lv_time_format)||'</DateOfBirth>';
else lv_string := lv_string||'<DateOfBirth></DateOfBirth>';
END IF;
...
insert into recs_xml_output VALUES (lv_string);
END LOOP;
COMMIT;
end p_generate_ohmpi_record;
begin
p_generate_ohmpi_record;
end;
/
The main issue with your code is that you aren't storing the ordering column in your output table, and you're relying on the rows being returned from that table in the order they were inserted.
Unfortunately, as it's a heap table, the order of insertion is not necessarily going to be the same as the order you retrieve them. In order to guarantee a specific ordering of the rows when selecting from a table, you need to have an order by clause.
Therefore you could do something like:
create table recs_xml_output (tstamp timestamp, XML_STRING VARCHAR2 (4000 char));
PROCEDURE p_generate_ohmpi_record
IS
...
CURSOR c_patient_xml IS
select *
from sbyn_transaction T
where timestamp >= '07-JAN-22 11.58.02.139977000'
and timestamp <= '07-JAN-22 17.51.26.054240000'
ORDER BY TIMESTAMP;
begin
for v_patient_xml in c_patient_xml
loop
...
insert into recs_xml_output (tstamp, xml_string)
VALUES (v_patient_xml.timestamp, lv_string);
END LOOP;
COMMIT;
end p_generate_ohmpi_record;
select *
from recs_xml_output
order by tstamp;
However, if your ultimate goal is simply to take your rows and output them as XML, you can do it in a single SQL statement:
WITH sbyn_transaction AS (SELECT 1 lid,
to_timestamp('11/01/2022 11:25:57.136468', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('01/01/2000', 'dd/mm/yyyy') date_of_birth,
'info 1' info_column
FROM dual
UNION ALL
SELECT 2 lid,
to_timestamp('11/01/2022 11:23:46.115329', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('06/10/1979', 'dd/mm/yyyy') date_of_birth,
'info 2' info_column
FROM dual
UNION ALL
SELECT 3 lid,
to_timestamp('11/01/2022 11:24:08.951232', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
NULL date_of_birth,
'info 3' info_column
FROM dual
UNION ALL
SELECT 4 lid,
to_timestamp('11/01/2022 11:23:17.468329', 'dd/mm/yyyy hh24:mi:ss.ff6') pps_number_updated,
to_date('29/03/1957', 'dd/mm/yyyy') date_of_birth,
'info 4' info_column
FROM dual)
-- end of mimicking your table with data in it; main query below:
SELECT st.*,
XMLELEMENT("Person",
XMLFOREST(lid AS "SourceID",
lid AS "PPSN",
to_char(pps_number_updated, 'yyyy-mm-dd"T"hh24:mi:ss') AS "PPSNLastUpdated"),
XMLELEMENT("Birth",
XMLFOREST(to_char(date_of_birth, 'yyyy-mm-dd"T"hh24:mi:ss') AS "DateOfBirth") AS "Birth"),
XMLFOREST(info_column AS "SomeData")).getclobval() xml_record
FROM sbyn_transaction st
ORDER BY pps_number_updated;
LID PPS_NUMBER_UPDATED DATE_OF_BIRTH INFO_COLUMN XML_RECORD
---------- ------------------------------------------------- ------------- ----------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4 11-JAN-22 11.23.17.468329000 29/03/1957 info 4 <Person><SourceID>4</SourceID><PPSN>4</PPSN><PPSNLastUpdated>2022-01-11T11:23:17</PPSNLastUpdated><Birth><DateOfBirth>1957-03-29T00:00:00</DateOfBirth></Birth><SomeData>info 4</SomeData></Person>
2 11-JAN-22 11.23.46.115329000 06/10/1979 info 2 <Person><SourceID>2</SourceID><PPSN>2</PPSN><PPSNLastUpdated>2022-01-11T11:23:46</PPSNLastUpdated><Birth><DateOfBirth>1979-10-06T00:00:00</DateOfBirth></Birth><SomeData>info 2</SomeData></Person>
3 11-JAN-22 11.24.08.951232000 info 3 <Person><SourceID>3</SourceID><PPSN>3</PPSN><PPSNLastUpdated>2022-01-11T11:24:08</PPSNLastUpdated><Birth></Birth><SomeData>info 3</SomeData></Person>
1 11-JAN-22 11.25.57.136468000 01/01/2000 info 1 <Person><SourceID>1</SourceID><PPSN>1</PPSN><PPSNLastUpdated>2022-01-11T11:25:57</PPSNLastUpdated><Birth><DateOfBirth>2000-01-01T00:00:00</DateOfBirth></Birth><SomeData>info 1</SomeData></Person>

Need to delete all tables based on their references i.e FK and PK

I need to create a script to clean up all the objects in the schema 'myschema'.
Order will be like
All tables(FK PK order)
How do I achieve this in one PLSQL block.
set serveroutput on;
declare
lv_str varchar2(1000);
begin
for c in(select distinct a.table_name as table_name, b.table_name as parent_table_name
from
all_constraints a
left outer join all_constraints b on a.r_constraint_name = b.constraint_name and a.owner = b.owner) loop
lv_str :='DROP TABLE '||c.table_name;
--lv_str :='DROP TABLE '||c.parent_table_name;
dbms_output.put_line(lv_str);
end loop;
end;
This is a script I use to clear Scott's schema. Why? I use it for testing purposes and, as time goes by, it turns into a mess. I don't pay much attention about what I remove and in which order; I simply run it 2-3 times and everything's gone. See if you can use it, improve it if you want.
SET SERVEROUTPUT ON;
DECLARE
l_user VARCHAR2 (30) := 'SCOTT';
l_str VARCHAR2 (200);
BEGIN
IF USER = l_user
THEN
FOR cur_r IN (SELECT object_name, object_type
FROM user_objects
WHERE object_name NOT IN ('EMP',
'DEPT',
'BONUS',
'SALGRADE'))
LOOP
BEGIN
l_str :=
'drop '
|| cur_r.object_type
|| ' "'
|| cur_r.object_name
|| '"';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
END;
/
PURGE RECYCLEBIN;
SELECT * FROM user_objects;
How to drop foreign key constraints first, and then drop tables:
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200);
3 begin
4 for cur_r in (select table_name, constraint_name
5 from user_constraints
6 where constraint_type = 'R')
7 loop
8 l_str := 'alter table ' || cur_r.table_name ||
9 ' drop constraint ' || cur_r.constraint_name;
10 dbms_output.put_line(l_str);
11
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select table_name from user_tables where table_name not in ('EMP', 'DEPT'))
18 loop
19 l_str := 'drop table ' || cur_r.table_name;
20
21 dbms_output.put_line(l_str);
22
23 execute immediate l_str;
24 end loop;
25 end;
26 /
alter table TDET drop constraint SYS_C00105662
drop table BONUS
drop table SALGRADE
drop table TEST
drop table MYTABLE
drop table TABLEB
drop table TABLEA
drop table EMPLOYEES
drop table T_PRINT
drop table TMAS
drop table TDET
PL/SQL procedure successfully completed.
SQL>

PL/SQL: I get expression 'I' cannot be used as an assignment target

My code:
create table info(str varchar2(30));
declare
cursor c(job emp_ast.job_id%type, dep emp_ast.department_id%type) is select employee_id
from emp_ast
where job_id=job and department_id=dep;
type t_job is table of emp_ast.job_id%type;
t t_job:=t_job();
emp emp_ast.employee_id%type;
i number(3);
begin
select job_id
bulk collect into t
from emp_ast;
for i in 10..270 loop
for j in 1..t.count loop
open c(i, t(j));
loop
fetch c into emp;
insert into info
values (i||' '||t(j)||' '||emp);
exit when c%notfound;
end loop;
i:=i+10;
end loop;
end loop;
end;
/
I get "expression 'I' cannot be used as an assignment target", reffering to the line where I increment i by 10. I am trying to save the department_id, employee_id and job_id as a string in a table for each department and each job.
At the point where you get that message, i refers to the loop control variable i defined in the line for i in 10..270 loop, not the int(3) variable defined earlier. In PL/SQL a loop definition defines a variable which is only accessible inside the loop, and which you cannot alter. I suggest you change the name of one or the other to make them unique.
EDIT
PL/SQL doesn't provide a way to step by more than 1 in a computed FOR loop. Instead, you will need to compute the desired department number value within the loop:
DECLARE
CURSOR c(job EMP_AST.JOB_ID%TYPE,
dep EMP_AST.DEPARTMENT_ID%TYPE)
IS SELECT EMPLOYEE_ID
FROM EMP_AST
WHERE JOB_ID = job AND
DEPARTMENT_ID = dep;
TYPE t_job IS TABLE OF EMP_AST.JOB_ID%TYPE;
t t_job := t_job();
emp EMP_AST.EMPLOYEE_ID%TYPE;
nDepartment NUMBER;
BEGIN
SELECT job_id
BULK COLLECT INTO t
FROM EMP_AST;
FOR i IN 1..27 LOOP
nDepartment := i * 10;
FOR j IN 1..t.COUNT LOOP
OPEN c(t(j), nDepartment);
LOOP
FETCH c INTO emp;
INSERT INTO info
VALUES (nDepartment || ' ' || t(j) || ' ' || emp);
EXIT WHEN c%notfound;
END LOOP; -- cursor c
CLOSE c;
END LOOP; -- j
END LOOP; -- i
END;
/
Note that in the code above the nDepartment value is computed within the i loop, which now increments from 1 to 27 instead of going from 10 to 270.

PL/SQL: Cursor ::Retrieve a list of employees for each department

How to retrieve list of employees for each department from table EMP into a comma-delimited new table
something like:
[column x:ie deptno] [column y:ie ename]
--------------------------
7 Jesus, María, José
5 Staz, Przemek, Tomek
6 John, Jane, Bob
below table is where I want to put my result from Function concatenate_list compilation
CREATE TABLE Z
(
x NUMBER(2) NOT NULL,
y VARCHAR2 (4000) NOT NULL
);
SET SERVEROUTPUT ON
CREATE OR REPLACE FUNCTION concatenate_list (xy_cursor IN SYS_REFCURSOR)
RETURN VARCHAR2
IS
lret VARCHAR2(30000);
ltemp VARCHAR2(30000);
BEGIN
LOOP
FETCH xy_cursor
INTO ltemp;
EXIT WHEN xy_cursor%notfound;
lret := lret || ',' || ltemp;
END LOOP;
RETURN LTRIM(lret, ',');
END;
/
SHOW ERRORS
how to insert the results from "Function concatenate_lit compile" and get a result as mentioned above.
Maybe using something like this:
INSERT INTO Z( x, y) SELECT e1.x,
concatenate_list(CURSOR(SELECT e2.y FROM EMP e2 WHERE e2.x= e1.x));
but how to set it up form inside the PL/SQL block
This may help you.
declare
type cur_name is ref cursor;
emp_name cur_name;
v_ename emp.ename%type;
v_all_ename varchar2(1000);
v_deptno emp.deptno%type;
cursor c is select deptno,cursor(select ename from emp e where e.deptno=f.deptno) from emp f group by deptno;
begin
open c;
loop
fetch c into v_deptno,emp_name;
exit when c%notfound;
loop
fetch emp_name into v_ename;
exit when emp_name%notfound;
v_all_ename:=v_all_ename||v_ename;
v_all_ename:=v_all_ename||',';
end loop;
dbms_output.put_line(v_deptno||' '||v_all_ename);
v_all_ename:='';
end loop;
close c;
end;

Pl/SQL - oracle 9i - Manual Pivoting

We have a table which has three columns in it:
Customer_name, Age_range, Number_of_people.
1 1-5 10
1 5-10 15
We need to return all the number of people in different age ranges as rows of a single query. If we search for customer #1, the query should just return one row:
Header- Age Range (1-5) Age Range (5-10)
10 15
We needed to get all the results in a single row; When I query for customer 1, the result should be only number of people in a single row group by age_range.
What would be the best way to approach this?
You need to manually perform a pivot:
SELECT SUM(CASE WHEN age_range = '5-10'
THEN number_of_people
ELSE NULL END) AS nop5,
SUM(CASE WHEN age_range = '10-15'
THEN number_of_people
ELSE NULL END) AS nop10
FROM customers
WHERE customer_name = 1;
There are easy solutions with 10g and 11g using LISTGAGG, COLLECT, or other capabilities added after 9i but I believe that the following will work in 9i.
Source (http://www.williamrobertson.net/documents/one-row.html)
You will just need to replace deptno with customer_name and ename with Number_of_people
SELECT deptno,
LTRIM(SYS_CONNECT_BY_PATH(ename,','))
FROM ( SELECT deptno,
ename,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS seq
FROM emp )
WHERE connect_by_isleaf = 1
CONNECT BY seq = PRIOR seq +1 AND deptno = PRIOR deptno
START WITH seq = 1;
DEPTNO CONCATENATED
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
This will create a stored FUNCTION which means you can access it at any time.
CREATE OR REPLACE FUNCTION number_of_people(p_customer_name VARCHAR2)
RETURN VARCHAR2
IS
v_number_of_people NUMBER;
v_result VARCHAR2(500);
CURSOR c1
IS
SELECT Number_of_people FROM the_table WHERE Customer_name = p_customer_name;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_number_of_people;
EXIT WHEN c1%NOTFOUND;
v_result := v_result || v_number_of_people || ' ' || CHR(13);
END;
END;
To run it, use:
SELECT number_of_people(1) INTO dual;
Hope this helps, and please let me know if there are any errors, I didn't testrun the function myself.
Just do
select Number_of_people
from table
where Customer_name = 1
Are we missing some detail?

Resources