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

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>

Related

Use cursor in LOOP in new QUERY

"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>

PLSQL STORED PROCEDURE does not give result

select count(*)
INTO countExceed
from uid_emp_master k
where k.unique_id in (select k.reviewer_uid
from uid_rm_hierarchy k
where k.unique_id in ('||p_ID_list||'))
and k.band IN( 'A','B','C','D');
if (countExceed > 0) then
quer :='UPDATE UID_RM_HIERARCHY I
SET I.REVIEWER_UID in (SELECT L.REVIEWER_UID
FROM UID_RM_HIERARCHY L
WHERE L.UNIQUE_ID in ('||p_ID_list||') )
WHERE I.REVIEWER_UID in('||p_ID_list||')
and isdeleted=0';
EXECUTE IMMEDIATE quer ;
END IF;
the above stored procedure does not show any result the variable countExceed declared as a number please help me to correct the query.
The issue is in
where k.unique_id in ('||p_ID_list||'))
Here you are saying to look for records
where unique_id = '||p_ID_list||'
exactly as its typed, but what you need is to handle that variable as a list of values.
Say you have a table like this
create table tabTest(id) as (
select 'id1' from dual union all
select 'id2' from dual union all
select 'id3' from dual union all
select 'id4' from dual
)
and your input string is 'id1,id3,1d8';
I see two ways to do what you need; one is with dynamic SQL, for example:
declare
vResult number;
vList varchar2(199) := 'id1,id3,1d8';
vSQL varchar2(100);
begin
vSQL :=
'select count(*)
from tabTest
where id in (''' || replace (vList, ',', ''', ''') || ''')';
--
execute immediate vSQL into vResult;
--
dbms_output.put_line('Result: ' || vResult);
end;
Another way could be by splitting the string into a list of values and then simply using the resulting list in the IN.
For that, there are many answers about how to split a comma separated list string in Oracle.

Pl/sql dbms output

I'm very new to pl/sql and I cannot make this query run.
I want it to find differences between two tables and then output ID of those transactions.
Any help would be appreciated!
SET SERVEROUTPUT ON
DECLARE
diff_id varchar2(50);
diff_id2 varchar2(50);
BEGIN
FOR dcount IN
SELECT
O.transid ,
ABB.transid
into diff_id, diff_id2
FROM
(SELECT *
FROM O.transactions
AND abdate >= trunc(sysdate -3)
) O
FULL OUTER JOIN
(SELECT *
FROM ABB.transactions
AND abdate >= trunc(sysdate -3)
) ABB
ON O.transid = ABB.transid
LOOP
DBMS_OUTPUT.put_line (employee_rec.diff_id);
DBMS_OUTPUT.put_line (employee_rec.diff_id2);
END LOOP;
END;
my desired output would be id of transactions which are not in both
tables. Ie 375 and 480
Ah, yes, 375 and 480. What about 832?
Anyway: you don't need PL/SQL to do that. Would SET operators do any good? For example, if you want to fetch ID s from the first table that aren't contained in the second one, you'd use
select id from first_table
minus
select id from second_table;
Both ways?
select 'in 1st, not in 2nd' what, id
from (select id from first_table
minus
select id from second_table)
union all
select 'in 2nd, not in 1st', id
from (select id from second_table
minus
select id from first_table);
Apply additional conditions, if necessary (ABDATE column, for example).

Error PLSQL copying content of 2 tables in 1 with varray

I have this tables:
CREATE TABLE departments (
dep_na number(2) NOT NULL PRIMARY KEY,
dname VARCHAR2(15),
loc VARCHAR2(15)
);
INSERT INTO departments VALUES (20,'CONTABILITY','SEVILLA');
INSERT INTO departments VALUES (30,'INVEST','MADRID');
COMMIT;
CREATE TABLE employees (
emp_nu number(4) NOT NULL PRIMARY KEY,
surname VARCHAR2(10),
oficio VARCHAR2(10),
dir number(4),
date_a DATE ,
salar number(6,2),
comis number(6,2),
dep_na number(2) NOT NULL REFERENCES departments(dept_no)
);
ALTER SESSION SET NLS_DATE_FORMAT='DD/MM/YYYY';
INSERT INTO employees VALUES (7369,'SANCHEZ','EMPLEADO',7902,'17/12/1990',1040,NULL,20);
INSERT INTO employees VALUES (7499,'ARROYO','VENDEDOR',7698,'20/02/1990',1500,390,30);
COMMIT;
create or replace
TYPE TDEP AS OBJECT(
dep_na NUMBER(2),
dname VARCHAR2(15),
loc VARCHAR2(15)
);
CREATE OR REPLACE TYPE TEMPLE AS OBJECT(
emp_nu number(4),
surname VARCHAR2(10),
oficio VARCHAR2(10),
dir number(4),
date_a DATE,
salar number(6,2),
comision number(6,2),
dep_na TDEP
);
CREATE OR REPLACE TYPE VEMPLE AS VARRAY(20) OF TEMPLE;
I created the following table pack with varray and types, but I have the problem that when I insert the contents of the employees and departments tables in the table pack the computer gives me error.
CREATE TABLE pack(
array_employees VEMPLE,
departme TDEP
);
I have problems with this code:
DECLARE
T VEMPLE;
A TDEP
CURSOR C1 is select * from departamentos order by dep_na;
CURSOR C2(DEPAR NUMBER) is select * from empleados where dep_na = depar;
j integer := 1;
BEGIN
for i in C1 LOOP
DBMS_OUTPUT.PUT_LINE(i.dep_na);
T := NEW VEMPLE();
A := NEW TDEP();
j := 1;
for x in C2(i.dep_na) loop
if j < T.LIMIT THEN
DBMS_OUTPUT.PUT_LINE(x.apellido || ' - ' || i.dep_na);
T.extend;
T(j) := NEW TEMPLE(NULL, NULL, NULL, NULL);
T(j).departments := NEW TDEP(i.loc, NULL, NULL);
j := j + 1;
end if;
end loop;
INSERT INTO Grupos VALUES(i.A, T);
end loop;
end;
/
I need help with this query in Oracle. I have problems with the cursor.
The immediate problem is that you're just missing a semicolon after A TDEP in the declare section, which is causing your ORA-06550: line 4, column 1: PLS-00103: Encountered the symbol "CURSOR" when expecting ... error. Which you haven't shown, but you said it's a cursor error, so that seems to line up.
But you have lots of other errors, some from inconsistent naming which I think is from partially changing things for posting, but partly from how you're creating object instances. I think this is pretty much clean:
DECLARE
T VEMPLE;
A TDEP;
CURSOR C1 is select * from departments order by dep_na;
CURSOR C2(DEPAR NUMBER) is select * from employees where dep_na = depar;
j integer := 1;
BEGIN
for i in C1 LOOP
DBMS_OUTPUT.PUT_LINE(i.dep_na);
T := NEW VEMPLE();
-- supply values to constructor
A := NEW TDEP(i.dep_na, i.dname, i.loc);
j := 1;
for x in C2(i.dep_na) loop
if j < T.LIMIT THEN
DBMS_OUTPUT.PUT_LINE(x.surname|| ' - ' || i.dep_na);
T.extend;
-- supply values to constructor
T(j) := NEW TEMPLE(x.emp_nu, x.surname, x.oficio,
x.dir, x.date_a, x.salar, x.comis, A);
-- no idea what the next line is doing
-- T(j).departments := NEW TDEP(i.loc, NULL, NULL);
j := j + 1;
end if;
end loop;
-- elements were wrong way round, which wouldn't matter if you
-- included the column names - which is good practice anyway
INSERT INTO pack VALUES(T, A);
end loop;
end;
/
SQL Fiddle. (Don't try to select from the table in the Fiddle though, it struggles with object types; this just shows it doesn't error...)
I assume this is an exercise. There are probably better and more efficient ways to convert from a relational to an object schema.

PL/SQL: How to delete records in a specific manner, for example if records of specific type X exist, delete all but one record

I'm trying to create a PL/SQL procedure where by I delete records that are grouped and selected by cursor but I only want one record remaining. I want to delete first by Xcomment, if there are multiple entries with id_number, activity_code, start_dt, activity_participation_code exist, then delete all but ONE entry with blank/null xcomment. If there are multiple entries with blank xcomment, then delete all but one with blank table_nmb. If multiple entries with blank table_nmb then delete highest sequence until only one is left. Essentially, I only want one record per all these fields. I'm having trouble thinking of how to do this so any help would be appreciated.
Here is my code so far:
Create Or Replace Function Y_Cleanup_Cursor
Return Sys_Refcursor
As
My_Cursor Sys_Refcursor;
Begin
Open My_Cursor For
Select Q.Id_Number, Q.Activity_Code, Q.Start_Dt, Q.Activity_Participation_Code, Q.Rec_Count, A.Xcomment, A.Table_Nmb, A.Xsequence
From (Select Id_Number, Activity_Code, Start_Dt, Activity_Participation_Code, Count(0) As Rec_Count
From Activity A
Group By Id_Number, Activity_Code, Start_Dt, Activity_Participation_Code
Having Count(0) > 1) Q,
Activity A
Where
Q.Id_Number = A.Id_Number And
Q.Activity_Code = A.Activity_Code And
Q.Start_Dt = A.Start_Dt And
Q.Activity_Participation_Code = A.Activity_Participation_Code;
Return My_Cursor;
End Y_Cleanup_Cursor;
Create Or Replace Procedure Help_Me_Please(Code In Varchar2)
Is
-- Declare Variables
-- I Stands For Internal Variable
L_Cursor Sys_Refcursor;
I_Id_Number Varchar2(10 Byte);
I_Xsequence Number (6);
I_Activity_Code Varchar2(05 Byte);
I_Start_Dt Varchar2(08 Byte);
I_Activity_Participation_Code Varchar2(02 Byte);
I_Table_Nmb Varchar2(15 Byte);
I_Xcomment Varchar2(255 Byte);
I_Rec_Count Number (6);
L_Counter Integer;
Begin
L_Cursor := Y_Cleanup_Cursor;
Loop
Fetch L_Cursor Into
I_Id_Number, I_Activity_Code, I_Start_Dt, I_Activity_Participation_Code, I_Rec_Count, I_Xcomment, I_Table_Nmb, I_Xsequence;
Select Count (Id_Number)
Into L_Counter
From Activity Where
Id_Number = I_Id_Number
And Activity_Code = I_Activity_Code
And Start_Dt = I_Start_Dt
And Activity_Participation_Code = I_Activity_Participation_Code
And Trim(Xcomment) Is Null;
If L_Counter <> I_Rec_Count Then
Begin
Delete From Activity
Where
Id_Number = I_Id_Number
And Activity_Code = I_Activity_Code
And Start_Dt = I_Start_Dt
And Activity_Participation_Code = I_Activity_Participation_Code
And Trim(Xcomment) Is Null;
end;
End If;
Exit When L_Cursor%Notfound;
End Loop;
Close L_Cursor;
End Help_Me_Please;
From what I gather you want to delete all rows except 1 where there are repeating columns
first make sure to backup your table:
create table [backup_table] as select * from [table];
Try This:
DELETE FROM backup_table
WHERE rowid not in
(SELECT MIN(rowid)
FROM backup_table
GROUP BY [col1], [col2]);
Col1 and col2, etc are the columns that should be identical

Resources