unique constraint violated in stored procedure oracle - sql

I have below stored procedure:
create or replace PROCEDURE CALCULATE_RECOVERY_HISTORY(p_month IN VARCHAR2) AS
l_id NUMBER;
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
l_id := SQ_AP_RECOVERY_HISTORY.NEXTVAL;
INSERT INTO t_ap_recovery_history (ID, RECOVERY_TARGET_MONTH, TARGET_INSTANCE, RECOVERY_PROGRESS, RECOVERY_TARGET, FAILED_TO_RECOVERY, FOCUS_AREA, IDENTIFIER_CLASS, CREATED_ON)
SELECT l_id,
a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END),
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END),
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END),
f.focus_area,
r.identifier_class,
SYSDATE
from t_ap_recovery_target t, t_ap_recovery_focusarea f, range r
where t.a_focus_area_id = f.id and t.a_range_id = r.id
and t.a_recovery_target_month = p_month
group by a_target_instance, a_recovery_target_month, f.focus_area, r.identifier_class;
COMMIT;
END CALCULATE_RECOVERY_HISTORY;
When I run the procedure, I get the error
ORA-00001: unique constraint violated.
I've also tried another way which is
SELECT SQ_AP_RECOVERY_HISTORY.NEXTVAL, a_recovery_target_month ...
But this also return another error which is
Sequence number not allowed here
What should I change in the code to solve this constraint issue?
Below is the table definition for T_AP_RECOVERY_HISTORY
CREATE TABLE "DIMSPST"."T_AP_RECOVERY_HISTORY"
( "ID" NUMBER(38,0),
"RECOVERY_TARGET_MONTH" VARCHAR2(6 BYTE) DEFAULT TO_CHAR(SYSTIMESTAMP, 'YYYYMM'),
"TARGET_INSTANCE" VARCHAR2(20 BYTE),
"RECOVERY_PROGRESS" NUMBER(38,0),
"RECOVERY_TARGET" NUMBER(38,0),
"FAILED_TO_RECOVERY" NUMBER(38,0),
"FOCUS_AREA" VARCHAR2(20 BYTE),
"IDENTIFIER_CLASS" VARCHAR2(42 BYTE),
"CREATED_ON" TIMESTAMP (6),
PRIMARY KEY ("ID")

Perform the aggregation in a sub-query and then apply the sequence value in an outer-query:
CREATE PROCEDURE CALCULATE_RECOVERY_HISTORY(
p_month IN VARCHAR2
)
AS
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
INSERT INTO t_ap_recovery_history (
ID,
RECOVERY_TARGET_MONTH,
TARGET_INSTANCE,
RECOVERY_PROGRESS,
RECOVERY_TARGET,
FAILED_TO_RECOVERY,
FOCUS_AREA,
IDENTIFIER_CLASS,
CREATED_ON
)
SELECT SQ_AP_RECOVERY_HISTORY.NEXTVAL,
a_recovery_target_month,
a_target_instance,
RECOVERY_PROGRESS,
RECOVERY_TARGET,
FAILED_TO_RECOVERY,
focus_area,
identifier_class,
SYSDATE
FROM (
SELECT a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END) AS RECOVERY_PROGRESS,
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END) AS RECOVERY_TARGET,
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END) AS FAILED_TO_RECOVERY,
f.focus_area,
r.identifier_class
FROM t_ap_recovery_target t
INNER JOIN t_ap_recovery_focusarea f
ON (t.a_focus_area_id = f.id)
INNER JOIN range r
ON (t.a_range_id = r.id)
WHERE t.a_recovery_target_month = p_month
GROUP BY
a_target_instance,
a_recovery_target_month,
f.focus_area,
r.identifier_class
);
END CALCULATE_RECOVERY_HISTORY;
/
Note: If you COMMIT in stored procedures then you cannot chain multiple procedures together and if one fails then ROLLBACK then all. Instead, you should COMMIT in the block that you call the procedures from.
fiddle

One option is to let Oracle create ID. You didn't specify database version you use, so trigger certainly is what would work:
create or replace trigger trg_bi_rec_hist
before insert on t_ap_recovery_history
for each row
begin
:new.id := SQ_AP_RECOVERY_HISTORY.NEXTVAL;
end;
/
Procedure then wouldn't contain insert into the ID column, i.e.
INSERT INTO t_ap_recovery_history (RECOVERY_TARGET_MONTH, ...)
SELECT a_recovery_target_month, ...
Another option (if your database version supports it) is to create ID as identity column instead of a trigger, e.g.
SQL> create table test
2 (id number generated always as identity);
Table created.

Or, if you would not create the trigger like in the previous answer, the procedure should look like below:
create or replace PROCEDURE CALCULATE_RECOVERY_HISTORY(p_month IN VARCHAR2) AS
l_id NUMBER;
BEGIN
ADD_LOG_INFO('CALCULATE_RECOVERY_HISTORY', 'Procedure Started');
INSERT INTO t_ap_recovery_history (ID, RECOVERY_TARGET_MONTH,
TARGET_INSTANCE, RECOVERY_PROGRESS, RECOVERY_TARGET,
FAILED_TO_RECOVERY, FOCUS_AREA, IDENTIFIER_CLASS, CREATED_ON)
with tb as (
SELECT a_recovery_target_month,
a_target_instance,
COUNT(CASE WHEN A_IS_RECOVERED = 'Y' THEN 1 END) c1,
COUNT(CASE WHEN A_IS_RECOVERED IN ('Y', 'N') THEN 1 END) c2,
COUNT(CASE WHEN A_IS_RECOVERED = 'N' THEN 1 END) c3,
f.focus_area,
r.identifier_class
from t_ap_recovery_target t, t_ap_recovery_focusarea f, range r
where t.a_focus_area_id = f.id and t.a_range_id = r.id
and t.a_recovery_target_month = p_month
group by a_target_instance, a_recovery_target_month,
f.focus_area, r.identifier_class
)
select SQ_AP_RECOVERY_HISTORY.NEXTVAL,
a_recovery_target_month,
a_target_instance,
c1,
c2,
c3,
focus_area,
identifier_class,
sysdate
from tb;
COMMIT;
END CALCULATE_RECOVERY_HISTORY;
In general triggers are detrimental to performance in case you have insert select inserting large numbers of rows in one go, or massive updates or massive deletes or merge.
If you have only DML affecting a small number of rows, triggers may save complexity, although I'd rather do more in stored procedures and less in triggers.

Related

Conditionally insert into another table if id exists in another table else insert into both both tables in oracle

If customer id exists in table A, insert order in table B.
if customer id does not exist in table A, insert customer id in table A and then order in table B.
I have been trying to achieve this with if/else and merge but keep running into
invalid sql statement.
IF EXISTS (SELECT CustomerID FROM Customer_T WHERE CustomerID = 18)
Insert into Order_T
values(79,18,to_date('09/28/2021','mm/dd/yyyy'),to_date('10/01/2021','mm/dd/yyyy'),1,3)
ELSE
insert INTO Customer_T VALUES (18,'Capitol Industries Ltd', '999 Fifth Avenue', 'New York', 'NY','10015')
insert into Order_T values (79,18,to_date('09/28/2021','mm/dd/yyyy'),to_date('10/01/2021','mm/dd/yyyy'),1,3)
END IF;
The IF THEN ELSE logic is not needed for this case. Instead make use of the database built-in functionality. customerid should be your primary key, so if you try to insert and it already exists that will raise the DUP_VAL_ON_INDEX exception.
Check the following example:
-- create tables
create table customers (
id number generated by default on null as identity
constraint customers_id_pk primary key,
name varchar2(255 char)
)
;
create table orders (
id number generated by default on null as identity
constraint orders_id_pk primary key,
customer_id number
constraint orders_customer_id_fk
references customers on delete cascade,
product varchar2(100 char)
)
;
BEGIN
BEGIN
insert INTO customers VALUES (2,'Capitol Industries Ltd');
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
NULL;
END;
insert into orders (customer_id,product) values (2,'a book');
END;
/
run the above block a couple of times. Only the first time it will insert a customer.
Suppose you have 2 very simple tables.
Tables
create table T1( num_ )
as
select 1 from dual ;
create table T2( num_ )
as
select 200 from dual ;
An anonymous block, containing IF .. ELSE .. END IF and EXISTS() similar to the code in your question, causes an error:
function or pseudo-column 'EXISTS' may be used inside a SQL statement
only
begin
if exists( select num_ from T1 where num_ = 2 ) then
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'if' ) ;
else
insert into T1( num_ ) values( 2 ) ;
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'else' ) ;
end if ;
end ;
/
-- error:
... function or pseudo-column 'EXISTS' may be used inside a SQL statement only
One solution may be to do the following (see asktom.oracle.com - Equivalent for EXISTS() in an IF statement)
begin
for x in ( select count(*) cnt
from dual
where exists ( select num_ from T1 where num_ = 2 )
) loop
if ( x.cnt = 1 ) then -- found
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'if' ) ;
else -- not found
insert into T1( num_ ) values( 2 ) ;
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'else' ) ;
end if;
end loop;
end;
/
-- output:
1 rows affected
dbms_output:
else
After the first execution of the anonymous block, the tables contain the following rows:
select num_, '<- T1' as table_ from T1
union all
select num_, '<- T2' from T2 ;
-- result
NUM_ TABLE_
1 <- T1
2 <- T1
200 <- T2
2 <- T2
Execute the anonymous block again, and you get ...
1 rows affected
dbms_output:
if
-- tables
NUM_ TABLE_
1 <- T1
2 <- T1
200 <- T2
2 <- T2
2 <- T2
DBfiddle here.
You may use multitable insert and use the fact that aggregate function without group by always returns a row with null in case of absent (= not satisfying where condition) row.
The code is below:
insert into customers(id, name, company, state)
values (1, 'Some name', 'Some company', 'NY')
1 rows affected
insert all
when cust_exists = 0
then
into customers (id, name, company, state)
values (cust_id, cust_name, company, state)
when 1 = 1
then
into orders (id, customer_id, order_date, due_date, some_id)
values(order_id, cust_id, order_date, due_date, some_id)
select
1 as order_id,
1 as cust_id,
'Some other name' as cust_name,
'Company' as company,
'NY' as state,
date '2021-09-28' as order_date,
date '2021-10-03' as due_date,
100 as some_id,
nvl(max(1), 0) as cust_exists
from customers
where id = 1
1 rows affected
insert all
when cust_exists = 0
then
into customers (id, name, company, state)
values (cust_id, cust_name, company, state)
when 1 = 1
then
into orders (id, customer_id, order_date, due_date, some_id)
values(order_id, cust_id, order_date, due_date, some_id)
select
2 as order_id,
2 as cust_id,
'Some other name' as cust_name,
'Company' as company,
'NY' as state,
date '2021-09-28' as order_date,
date '2021-10-03' as due_date,
100 as some_id,
nvl(max(1), 0) as cust_exists
from customers
where id = 2
2 rows affected
You may also use two inserts with documented ignore_row_on_dupkey_index hint, which does what its name implies.
insert /*+
ignore_row_on_dupkey_index(customers(id))
*/
into customers (id, name, company, state)
values (2, 'Name', 'Comp', 'NY')
βœ“
insert into orders (id, customer_id, order_date, due_date, some_id)
values (3, 2, date '2021-09-30', date '2021-10-07', 5)
1 rows affected
select *
from customers
ID
NAME
COMPANY
STATE
1
Some name
Some company
NY
2
Some other name
Company
NY
db<>fiddle here

Oracle - Procedure to merge with insert, update and delete

I need to create a procedure to treat this case in the most performative way possible (is an extremely large amount of data).
I have a table called ORDER_A that every day receives a full load (its truncated, and all records are inserted again).
I have a table called ORDER_B which is a copy of ORDER_A, containing the same data and some additional control dates.
I also have a table MANAGER to save start and finish date, and if the procedure is running.
After all inserts are done in ORDER_A, i want to execute a procedure that, for each record on ORDER_A, must looks for a record with the same identifier (primary key: order_id) in table B.
If a record exists with the same order_id, and any of the other columns have changed, an update must be performed on table B
If a record exists with the same order_id, and no values ​​in the other columns have been modified, nothing should be performed, the record must remain the same in table B.
If there is no record with the same order_id, it must be inserted in table B.
If there is a record on ORDER_B that no longer exists on ORDER_A (it was deleted), the column "flag_deleted" must be updated to "1".
My tables are like this
CREATE TABLE ORDER_A
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6),
CHECKSUM_CODE VARCHAR2(40),
PRIMARY KEY (ORDER_ID)
);
CREATE TABLE ORDER_B
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6)
INSERT_AT TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
UPDATED_AT TIMESTAMP(6),
CHECKSUM_CODE VARCHAR2(40),
FLAG_DELETED NUMBER(1),
PRIMARY KEY (ORDER_ID)
);
-- index on checksum column for both tables
CREATE INDEX idx_cksum on ORDER_A (CHECKSUM_CODE ASC);
CREATE INDEX idx_cksum on ORDER_B (CHECKSUM_CODE ASC);
-- Manager table
CREATE TABLE MANAGER
(
TABLE_NAME VARCHAR2(40),
PROCEDURE_NAME VARCHAR2(50),
START_TS TIMESTAMP(6),
FINISH_TS TIMESTAMP(6),
IS_RUNNING NUMBER(1)
);
I'm thinking in something like this procedure below, but I'm not sure if it's the best way and how to deal with the delete case
create or replace procedure MERGE_DATA_ORDER
DECLARE
is_running number;
ex_running EXCEPTION;
BEGIN
SELECT IS_RUNNING INTO is_running FROM MANAGER WHERE PROCEDURE_NAME = 'MERGE_DATA_ORDER';
IF is_running = 1
then RAISE ex_running
ELSE
-- Update the flag on manager table
UPDATE MANAGER SET IS_RUNNING = 1, START_TS = SYSTIMESTAMP WHERE PROCEDURE_NAME = 'MERGE_DATA_ORDER';
COMMIT;
-- update all records with a checksum using STANDARD_HASH with MD5
UPDATE ORDER_A
SET CHECKSUM_CODE =
STANDARD_HASH
(
ORDER_ID ||
ORDER_CODE ||
ORDER_STATUS ||
ORDER_USER_ID ||
ORDER_DATE,
'MD5'
);
COMMIT;
-- then, I do a MERGE operation, using the checksum as a comparator
merge into ORDER_B b
using (select a.* from ORDER_A a) m
on (m.ORDER_ID = b.ORDER_ID)
when matched then
update
set
b.ORDER_ID = m.ORDER_ID,
b.ORDER_CODE = m.ORDER_CODE,
b.ORDER_STATUS = m.ORDER_STATUS,
b.ORDER_USER_ID = m.ORDER_USER_ID,
b.ORDER_DATE = m.ORDER_DATE,
b.COD_CHECKSUM = m.COD_CHECKSUM,
b.DAT_UPDATE = SYSTIMESTAMP
where b.CHECKSUM_CODE <> m.CHECKSUM_CODE
when not matched then
insert (
b.ORDER_ID,
b.ORDER_CODE,
b.ORDER_STATUS,
b.ORDER_USER_ID,
b.ORDER_DATE,
b.COD_CHECKSUM
)
values (
m.ORDER_ID,
m.ORDER_CODE,
m.ORDER_STATUS,
m.ORDER_USER_ID,
m.ORDER_DATE,
m.COD_CHECKSUM
);
END IF;
-- set the flag to 0
UPDATE MANAGER SET IS_RUNNING = 0, FINISH_TS = SYSTIMESTAMP WHERE PROCEDURE_NAME = 'MERGE_DATA_ORDER';
COMMIT;
END;
/
I need some help to complete this code, performance tips and deal with the delete issue;
I think you can do this as a single statement as part of the data load. Let's assume that ORDER_A has been loaded (but I will comment on that later). Then you can define the result of the insert/update by doing a full outer join between ORDER_A and ORDER_B, and the use a CASE statement to project the "correct" value from ORDER_A or ORDER_B. Similarly you can project the FLAG_DELTED. It would look something like this. In this example, I am skipping the MD5, but this could be added if really needed - more on that later too
select
case
when ( b.order_id is null ) then a.order_id
else case when (
b.ORDER_ID != m.ORDER_ID or
b.ORDER_CODE != m.ORDER_CODE or
b.ORDER_STATUS != m.ORDER_STATUS or
b.ORDER_USER_ID != m.ORDER_USER_ID or
b.ORDER_DATE != m.ORDER_DATE or
b.DAT_UPDATE != SYSTIMESTAMP ) then b.order_id else a.order_id end
end as newOrder_id
, case when ( b.order_id is null ) then a.order_code
else case when (
b.ORDER_ID != m.ORDER_ID or
b.ORDER_CODE != m.ORDER_CODE or
b.ORDER_STATUS != m.ORDER_STATUS or
b.ORDER_USER_ID != m.ORDER_USER_ID or
b.ORDER_DATE != m.ORDER_DATE or
b.DAT_UPDATE != SYSTIMESTAMP ) then b.order_code else a.order_code end
end as newOrder_code
, case when ( b.order_id is null ) then a.order_status
else case when (
b.ORDER_ID != m.ORDER_ID or
b.ORDER_CODE != m.ORDER_CODE or
b.ORDER_STATUS != m.ORDER_STATUS or
b.ORDER_USER_ID != m.ORDER_USER_ID or
b.ORDER_DATE != m.ORDER_DATE or
b.DAT_UPDATE != SYSTIMESTAMP ) then b.order_status else a.order_status end
end as newOrder_status
/* etc... ( Repeat for all projected columns )
Then for the flag_deleted column */
, case when ( a.order_id is null ) then 1
when ( b.order_id is null ) then 0
else b.flag_deleted
end as newFlag_deleted
from Order_b b
full outer join Order_a a
on b.order_id = a.order_id
It may be possible that ORDER_A could be an external table, so then you would just need to prepend this with a
CREATE TABLE NEW_ORDER_A as select....
And then you have the results you need.
Where you a hemorrhaging performance in your example, is the update of ORDER_A. You are generating redo, undo and losing any compression benefits. You are also maintaining indexes, but indexes are not needed.
Assuming you have resources, you cab now use DIRECT PATH and parallelism, and this would scale pretty well.
Lastly, if you really do need the MD5, you need to add a special character between each column, otherwise is will be ambiguous. For example, the following woukd have the same MD5
COL1 COL2
AA BBB
AAB BB

How to store fetched bulk values into single variable and insert all values it on one variable using pl/sql

Note
Somebody Tell me how to store Multiples rows value for a single column store into a single variable and behalf of that variable returned values store in table on one single click how to do such scenario using pl/sql.
Like Such Query Return single column with Multi rows i want to store it into some var and insert all values on single click process.It will generate error image of error has attached
DECLARE
l_respondent_id VARCHAR(100);
BEGIN
FOR c IN (
SELECT
s.gr_number
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
) LOOP
l_respondent_id := c.gr_number;
BEGIN
SELECT
s.gr_number
INTO l_respondent_id
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
AND s.gr_number = c.gr_number;
EXCEPTION
WHEN no_data_found THEN
l_respondent_id := NULL;
END;
IF l_respondent_id IS NULL THEN
INSERT INTO student_class_attend ( gr_number ) VALUES ( c.gr_number ) RETURNING gr_number INTO l_respondent_id;
END IF;
END LOOP;
END;
You can probably use a nested table for this, together with a BULK COLLECT and a FORALL. Simplified example:
Tables and data
create table students( id primary key, fname, lname )
as
select 1, 'fname_1', 'lname_1' from dual union all
select 2, 'fname_2', 'lname_2' from dual union all
select 3, 'fname_3', 'lname_3' from dual union all
select 4, 'fname_4', 'lname_4' from dual union all
select 5, 'fname_5', 'lname_5' from dual union all
select 6, 'fname_6', 'lname_6' from dual ;
-- empty
create table attendance( studentid number references students, when_ date ) ;
Anonymous block
declare
-- This will allow you to store several studentIDs:
type studentid_t is table of students.id%type index by pls_integer ;
-- The current attendance list. Notice the TYPE.
attendancelist studentid_t ;
begin
-- Your query (including all the necessary joins and conditions) here.
-- In this case, we will pick up 3 of the 6 IDs.
select id bulk collect into attendancelist
from students
where mod( id, 2 ) = 0 ;
-- Write a _single_ DML statement after FORALL (which is not a loop - see docs)
forall i in attendancelist.first .. attendancelist.last
insert into attendance( studentid, when_ )
values( attendancelist( i ) , sysdate ) ; -- use whatever you need here
end ;
/
Result (after executing the anonymous block)
SQL> select * from attendance ;
STUDENTID WHEN_
---------- ---------
2 07-JUN-20
4 07-JUN-20
6 07-JUN-20
DBfiddle here.

Row compare and insert into log table only changed data

I'm trying to compare a global temporary table to another table and want to insert into a log table but can not seem to find the best/most efficient way to accomplish this.
Log Table
CREATE TABLE LogTable
(
Date_Time DATETIME,
Name VARCHAR2(10 CHAR),
old VARCHAR2(20 CHAR),
new VARCHAR2(20 CHAR),
)
Object Type
CREATE OR REPLACE type dbo.P_REC AS OBJECT
(
ATTR1 VARCHAR2(10 CHAR),
ATTR2 VARCHAR2(20 CHAR),
ATTR3 VARCHAR2(20 CHAR),
ATTR4 VARCHAR2(20 CHAR)
);
Collection Type
CREATE OR REPLACE type dbo.P_REC_LIST IS TABLE OF P_REC;
Stored Procedure
PROCEDURE PASSPEOPLETOORACLE(tmpCollection IN P_REC_LIST , resultCursor out sys_refcursor)
IS
BEGIN
IF tmpCollection .count > 0 THEN
INSERT INTO tmpPTbl SELECT * FROM table1; <--tmpPTbl is a copy of table1 before the merge statement.
MERGE INTO table1 MKTP
USING (
WITH tmpTBL AS
(
SELECT ADCOLL.ATTR1,
ADCOLL.ATTR2,
MV.ATTR3,
MV.ATTR4
FROM TABLE(tmpCollection) ADCOLL
LEFT JOIN materializedView MV
ON ADCOLL.ATTR1 = MV.ATTR1
)
SELECT DISTINCT COALESCE(tmpTBL.ATTR1,MKtmpTBL.ATTR1) AS ATTR1,
tmpTBL.ATTR2,
tmpTBL.ATTR3,
tmpTBL.ATTR4,
CASE WHEN tmpTBL.ATTR1 IS NULL
THEN 'Y' ELSE 'N' END
match_flag FROM tmpTBL
FULL JOIN table1 MKtmpTBL
ON MKtmpTBL.ATTR1 = tmpTBL.ATTR1
) usingTBL
ON (MKTP.ATTR1 = usingTBL.ATTR1)
WHEN MATCHED THEN
UPDATE SET MKTP.ATTR2 = usingTBL.ATTR2,
MKTP.ATTR3 = usingTBL.ATTR3,
MKTP.ATTR4 = usingTBL.ATTR4,
DELETE WHERE match_flag = 'Y'
WHEN NOT MATCHED THEN
INSERT (ATTR1)
VALUES (usingTBL.ATTR1);
END IF;
END;
Id like a way to compare the newly update records in table1 to the prior records in tmpPTbl and where the old and new values differ, insert a new row into the log table.
2019-02-14 23:59:59,jdoe,abcd,efgh would be an example of a record inserted into the log table.
tmpPTbl & table1 both have 50 columns in them & about 16k rows on average.
The best solution for you would be to create a Trigger on table Table1. So that any operation occurs on Table1 it can be logged to Logtable. See below demo:
CREATE TABLE table1
(col1 VARCHAR2(10),
col2 VARCHAR2(10),
col3 VARCHAR2(10) );
/
--Trigger
CREATE OR REPLACE TRIGGER Log_Entry before
INSERT OR
UPDATE ON table1 FOR EACH row
BEGIN
IF INSERTING THEN
INSERT INTO LogTable VALUES
(sysdate, :new.col1, :new.col2, :new.col3
);
ELSIF UPDATING THEN
INSERT INTO LogTable VALUES
(sysdate, :old.col1, :old.col2, :old.col3
);
END IF;
END;
/
Execution:
SQL> Insert into table1 values ('A','B','C');
SQL>Update table1
set col1 ='Z'
where col1 = 'A';
SQL> Merge INTO table1 tb1 USING
(SELECT 'Z' col1 , 'D' col2, 'K' col3 FROM dual
) tb2 ON (tb1.col1 = tb2.col1)
WHEN matched THEN
UPDATE SET tb1.col2=tb2.col2 WHEN NOT matched THEN
INSERT VALUES
(tb2.col1,tb2.col2,tb2.col3
);
SQL>Commit;
SQL> Select * from logtable;
DATE_TIME NAME OLD NEW
--------- ---------- -------------------- --------------------
15-FEB-19 A B C
15-FEB-19 Z B C
15-FEB-19 Z B C
Note there is no need to copy data to tmpPTbl table as well.

How to let PL/SQL know not to process this data again if the stored procedure is run

So I am running a PL/SQL script in oracle to load data from a cursor into a table. I thought of adding a column called 'Processed' to the cursor, and having the code check whether its been processed using that method next time the stored procedure is run; however, i am getting an error. What would be a better approach? I am trying to prevent duplicate entries in the event that i run the stored procedure multiple times. Below is my code:
CREATE OR replace PROCEDURE Test_proc
IS
CURSOR c1 IS
SELECT SUM(v.value_tx) AS sum_of_values ,
e.entity_id AS entity_id ,
e.entity_name_tx AS entity_name ,
e.ei_id_tx AS ei_id ,
v.create_dt AS create_dt ,
v.hr_num AS hr_num ,
v.utc_offset ,
v.data_date ,
v.hr_utc ,
v.hr ,
v.data_code ,
'N' AS processed
FROM value v
join submission_value sv
ON v.value_id = sv.value_id
join form_field ff
ON sv.form_field_id = ff.form_field_id
join submission s
ON sv.submission_id = s.submission_id
join survey_respondent sr
ON s.survey_respondent_id = s.survey_respondent_id
join entity e
ON sr.entity_id = e.entity_id
WHERE ei_id_tx IN ('ABC',
'AECD',
'AVA')
AND ff.form_field_id IN ('77421',
'77471')
GROUP BY e.entity_id,
e.entity_name_tx,
e.ei_id_tx,
v.create_dt,
v.hr_num,
v.utc_offset,
v.data_date,
v.hr_utc,
v.hr,
v.data_code;
l_var c1%ROWTYPE;
BEGIN
OPEN c1;
LOOP
FETCH c1
INTO l_var;
EXIT
WHEN c1%NOTFOUND;
IF l_var.processed = 'n' THEN
INSERT INTO value
(
energy_product_id,
data_source_id,
unit_cd,
value_tx,
utc_offset,
data_date,
hr_utc,
hr,
hr_num,
data_code,
create_dt,
create_user_id,
modify_dt,
modify_user_id,
effective_dt,
inactive_dt
)
VALUES
(
'333',
'747',
'NA',
l_var.sum_of_values,
l_var.utc_offset,
l_var.data_date,
l_var.hr_utc,
l_var.hr,
l_var.hr_num,
l_var.data_code,
SYSDATE,
'1',
NULL,
NULL,
NULL,
NULL
);
SET l_var.processed = 'n';
END IF;
END LOOP;
CLOSE c1;
END test_proc;
Thanks in advance and please let me know if there is anything further I need to add and/or clarify.
Adding processed = N to a cursor won't help much; actually, won't help at all. You'd need to update table you fetch data from, instead. For example,
update value set processed = 'Y'
where id = l_var.value_table_id
and then remove those rows from cursor with
and value.processed <> 'Y'
Though, a more common option is to include yet another condition into the WHERE clause which won't take rows that have already been inserted. It can be done with IN or EXISTS. Something like this:
cursor c1 is select
...
where <your current conditions here>
and not exists (select null from values
where <condition that joins VALUES table with one (or more)
tables from the FROM clause>
This "condition" is usually some ID, for example
where values.id = value.id
and ...