I am trying to write cursor that would insert into table, but I am receiving error, need help with this. Error that I am receiving is ORA-06550.
DECLARE
CURSOR cur_rating IS
SELECT bc.name, bc.title, bc.checkoutdate, bc.returneddate,
b.categoryname,b.publisher, ba.authorname
FROM bookshelf_checkout bc INNER JOIN bookshelf b
ON bc.title = b.title
INNER JOIN bookshelf_author ba
ON bc.title = ba.title
FOR UPDATE NOWAIT;
lv_totdays_num NUMBER(4) := 0;
lv_rating_txt VARCHAR2(2);
BEGIN
FOR rec_rating IN cur_rating LOOP
lv_totdays_num := rec_rating.returneddate -
rec_rating.checkoutdate;
IF lv_totdays_num <= 10 THEN lv_rating_txt := 'DR';
ELSIF lv_totdays_num <= 25 THEN lv_rating_txt := 'CR';
ELSIF lv_totdays_num <= 35 THEN lv_rating_txt := 'BR';
ELSE lv_rating_txt := 'A';
END IF;
INSERT INTO bookshelf_audit (title, publisher, categoryname,
new_rating, auditdate)
VALUES (rec_rating.title, rec_rating.publisher,
rec_rating.categoryname, lv_rating_txt, sysdate)
WHERE CURRENT OF cur_rating;
END LOOP;
COMMIT;
END;
You need to remove the where clause from your insert ... values statement:
INSERT INTO bookshelf_audit
(title, publisher, categoryname,
new_rating, auditdate)
VALUES
(rec_rating.title, rec_rating.publisher,
rec_rating.categoryname, lv_rating_txt, sysdate)
WHERE CURRENT OF cur_rating;
should be
INSERT INTO bookshelf_audit
(title, publisher, categoryname,
new_rating, auditdate)
VALUES
(rec_rating.title, rec_rating.publisher,
rec_rating.categoryname, lv_rating_txt, sysdate);
The WHERE CURRENT OF clause in an UPDATE or DELETE statement states that the most recent row fetched from the table should be updated or deleted :
UPDATE table_name
SET set_clause
WHERE CURRENT OF cursor_name;
OR
DELETE FROM table_name
WHERE CURRENT OF cursor_name;
but not applicable for an INSERT statement.
So, remove WHERE CURRENT OF cur_rating part only, your code will run. That's make your INSERT statement as :
INSERT INTO bookshelf_audit (title, publisher, categoryname,
new_rating, auditdate)
VALUES (rec_rating.title, rec_rating.publisher,
rec_rating.categoryname, lv_rating_txt, sysdate);
Related
I need to insert rows in a Table_A, with values from Table_B on UPDATE of Table_B.
table_A (
station int,
state varchar(20),
CONSTRAINT pk_table_a PRIMARY KEY(station, state),
CONSTRAINT fk_table_A FOREIGN KEY (station, state)
REFERENCES table_B (station, state)
)
table_B (
station int,
state varchar(20),
player int,
date_sent Date DEFAULT NULL
CONSTRAINT pk_table_b PRIMARY KEY(station, state, player)
)
Now, my triggers needs to add a row to table_A (station, state), when all the dates for these (station, state) become NOT NULL in table_B.
Here's my actual which causes mutating table error:
CREATE OR REPLACE TRIGGER add_stations_sent
AFTER INSERT OR UPDATE ON "TABLE_B"
FOR EACH ROW
WHEN (NEW.DATE_SENT IS NOT NULL)
DECLARE
nb_stations_null number;
BEGIN
SELECT COUNT(1)
INTO nbr_stations_null
FROM "TABLE_B"
WHERE "TABLE_B".STATE = :NEW.STATE AND
"TABLE_B".STATION <> :NEW.STATION AND
"TABLE_B".DATE_SENT IS NULL;
IF (nb_stations_null = 0) THEN
INSERT INTO "TABLE_A" VALUES (:NEW.STATION, :NEW.STATE);
END IF;
END;
Something like this will defer the processing to AFTER STATEMENT level and thus allow to you run queries
create or replace
trigger avoid_mutate
for insert or update on table_b
compound trigger
l_station sys.odcivarchar2list := sys.odcivarchar2list();
l_state sys.odcivarchar2list := sys.odcivarchar2list();
before each row is
begin
l_station.extend;
l_state.extend;
l_station(l_station.count) := :new.station;
l_state(l_state.count) := :new.state;
end before each row;
after statement is
declare
nb_stations_null number;
begin
for i in 1 .. l_station.count loop
SELECT COUNT(1)
INTO nbr_stations_null
FROM "TABLE_B"
WHERE "TABLE_B".STATE = l_state(i) AND
"TABLE_B".STATION <> l_station(i) AND
"TABLE_B".DATE_SENT IS NULL;
IF (nb_stations_null = 0) THEN
INSERT INTO "TABLE_A" VALUES (l_station(i), l_state(i));
END IF;
end after statement;
end;
/
Is there equivalent to this T-SQL query in PL/SQL (Oracle 12c)?
UPDATE A SET A.columnA = 10 WHERE A.columnB < 30 OUTPUT INSERTED.*, DELETED.*
The query updates table A and at the same time returns the status of the record before the update and after the update.
Trigger is not a solution for me as well as SELECT records before and SELECT records after updating.
Not a direct one, but using RETURNING INTO you will be able to achieve the same effect:
CREATE TABLE A(columnA VARCHAR2(10), columnB INT);
INSERT INTO A(columnA, columnB) VALUES ('Test', 10);
INSERT INTO A(columnA, columnB) VALUES ('Row 2', 20);
CREATE TABLE audit_table(col_new VARCHAR2(10),col_old VARCHAR2(10));
DECLARE
TYPE rec IS RECORD (actual A.columnA%TYPE, old A.columnA%TYPE);
TYPE col_a_t IS TABLE OF rec;
v_a col_a_t;
BEGIN
UPDATE (SELECT A.*, (SELECT A.columnA FROM dual) AS old_columnA FROM A)
SET columnA = 'XYZ'
WHERE columnB < 30
RETURNING columnA, old_columnA BULK COLLECT INTO v_a;
COMMIT;
-- printing for debug
FOR i IN v_a.first .. v_a.last LOOP
dbms_output.put_line('Old =>' || v_a(i).old || ' new => ' || v_a(i).actual);
END LOOP;
-- additional
FORALL i IN v_a.first .. v_a.last
INSERT INTO audit_table VALUES v_a(i);
COMMIT;
END;
/
SELECT * FROM A;
SELECT * FROM audit_table;
DBFiddle Demo
Idea taken from: Returning Old value during update
I have below requirement-
I have 2 tables Main Table A ,Staging table B
I have written a before insert trigger on main table A to check some wrong data. If the data is wrong then it will insert into staging table B so that user can view the errors and correct then and then again upload the data.
My issue is -Data is not getting inserted into staging table as I am using raise_form_trigger_failure just after insert statement.Is there any other way to do this?
TRIGGER CODE-
create or replace trigger CCM_TEST
before insert OR UPDATE ON "CCM_MANAGER"
for each row
declare
l_user varchar2(500);
v_user number;
v_cost_centre number;
v_company_code number;
v_show_error varchar2(100);
begin
l_user := NVL(v('APP_USER'), user);
if inserting then
begin
select count(1)
into v_cost_centre
From gl_code_combinations
where segment2=:new.cost_center
and enabled_flag ='Y';
if (v_cost_centre= 0) then
v_show_error:='Cost Centre does not exists!! '||'Cost Centre -'||:new.cost_center;
:new.CHECK_INSERT_FLAG:='Y';
Insert into CCM_MANAGER_STG(COMPANY_CODE,
COST_CENTER,
USER_NAME,
EFFECTIVE_START_DATE,
EFFECTIVE_END_DATE,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE,
LINE_ID,
CHECK_INSERT_FLAG,
SHOW_ERRORS) values
( :new.COMPANY_CODE,
:new.COST_CENTER,
:new.USER_NAME,
:new.EFFECTIVE_START_DATE,
:new.EFFECTIVE_END_DATE,
:new.CREATED_BY,
:new.CREATION_DATE,
:new.LAST_UPDATED_BY,
:new.LAST_UPDATE_DATE,
:new.LINE_ID,
:new.CHECK_INSERT_FLAG,
v_show_error);
end if;
raise_application_error (-20001, 'Cost Centre does not exists!! '||'Cost Centre -'||:new.cost_center||' ');
exception
when others then
--raise_application_error (-20001, sqlerrm);
null;
end;
begin
select count(1)
into v_company_code
From gl_code_combinations
where segment1=:new.company_code
and enabled_flag ='Y';
if (v_company_code= 0) then
v_show_error:='Company Code does not exists!!';
:new.CHECK_INSERT_FLAG:='Y';
Insert into CCM_MANAGER_STG(COMPANY_CODE,
COST_CENTER,
USER_NAME,
EFFECTIVE_START_DATE,
EFFECTIVE_END_DATE,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE,
LINE_ID,
CHECK_INSERT_FLAG,
SHOW_ERRORS) values
( :new.COMPANY_CODE,
:new.COST_CENTER,
:new.USER_NAME,
:new.EFFECTIVE_START_DATE,
:new.EFFECTIVE_END_DATE,
:new.CREATED_BY,
:new.CREATION_DATE,
:new.LAST_UPDATED_BY,
:new.LAST_UPDATE_DATE,
:new.LINE_ID,
:new.CHECK_INSERT_FLAG,
v_show_error);
end if;
raise_application_error (-20001, 'Company Code does not exists!! '||'Company code -'||:new.company_code||' ');
-- Rollback;
exception
when others then
raise_application_error (-20001, sqlerrm);
null;
end;
end if;
end;
First way is autonomous transaction.
Second way is compound trigger.
I have contact details data in Oracle table something like below...
I want to insert new column to assign same ID to the contacts having matching information , i.e. based on group of lastname, firstname and (phone and/or email)
Output should look like below
I'm new to this forum, so having formatting issues while posting question, please see attached images for easy understanding of my requirement
Looking for PL/SQL procedure to get this done in our huge database
Due to the nature of the data you presented I see no possible way to do it in one SQL or for it to be better tuned.
Script to create the table and populate with your example:
create table TAB_TEST(LAST_NAME VARCHAR2(200),
FIRST_NAME VARCHAR2(200),
PHONE NUMBER,
EMAIL VARCHAR2(200))
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 2058371579, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 4479940000, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', null, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
ALTER TABLE TAB_TEST ADD ID NUMBER
/
And the script to update
DECLARE
lncount NUMBER := 1;
-- had to create this record in order to add the rowid
TYPE lttab_test IS RECORD(
ROWID VARCHAR2(200),
last_name VARCHAR2(200),
first_name VARCHAR2(200),
phone NUMBER,
email VARCHAR2(200),
id NUMBER);
TYPE lttype
IS TABLE OF LTTAB_TEST;
larecords LTTYPE;
BEGIN
SELECT ROWID,
last_name,
first_name,
phone,
email,
id
bulk collect INTO larecords
FROM tab_test a;
FOR i IN 1..larecords.count LOOP
IF Larecords(i).id IS NULL THEN
FOR j IN 1..i-1 LOOP
IF Larecords(j).phone = Larecords(i).phone
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
FOR j IN 1..i-1 LOOP
IF Larecords(j).email = Larecords(i).email
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
IF Larecords(i).id IS NULL THEN
Larecords(i).id := lncount;
lncount := lncount + 1;
END IF;
END IF;
END LOOP;
forall i IN 1..larecords.count
UPDATE tab_test
SET id = Larecords(i).id
WHERE ROWID = Larecords(i).ROWID;
COMMIT:
END;
It isn't the most efficient / pretty code but it works in case you just want sequential numbers
DECLARE
I NUMBER := 0;
BEGIN
ALTER TABLE TABLE
ADD ID NUMBER;
COMMIT;
FOR REC IN (SELECT * FROM TABLE)
LOOP
IF(REC.ID IS NULL) THEN
UPDATE TABLE T SET ID = I
WHERE (T.EMAIL = REC.EMAIL OR T.PHONE = REC.PHONE)
AND ID IS NULL;
COMMIT;
I := I + 1;
END IF;
END LOOP;
END;
How can I work around the Oracle's limitation of not allowing subqueries in triggers.
Here's an example trigger I'm trying to create, but am unable to because I can't use a subquery.
CREATE OR REPLACE TRIGGER trigger_w_subquery
AFTER UPDATE OR INSERT ON project_archiving
FOR EACH ROW WHEN (old.archiving_status <> new.archiving_status
AND new.archiving_status = 1
AND (SELECT offer FROM projects WHERE projnum = :new.projnum) IS NULL
)
BEGIN
INSERT INTO offer_log (offer, status, date)
VALUES (null, 9, sysdate);
END;
This trigger would do it:
CREATE OR REPLACE TRIGGER trigger_w_subquery
AFTER UPDATE OR INSERT ON project_archiving
FOR EACH ROW WHEN (old.archiving_status <> new.archiving_status
AND new.archiving_status = 1
)
DECLARE
l_offer projects.offer%TYPE;
BEGIN
SELECT offer INTO l_offer
FROM projects
WHERE projnum = :new.projnum;
IF l_offer IS NULL THEN
INSERT INTO offer_log (offer, status, date)
VALUES (null, 9, sysdate);
END IF;
END;
I have assumed that the select from projects will always find a row; if not it will raise a NO_DATA_FOUND exception that you may need to handle.
I expect that you want something like
CREATE OR REPLACE TRIGGER trigger_w_subquery
AFTER UPDATE OR INSERT ON project_archiving
FOR EACH ROW
WHEN (old.archiving_status <> new.archiving_status
AND new.archiving_status = 1)
DECLARE
l_offer projects.offer%TYPE;
BEGIN
SELECT offer
INTO l_offer
FROM projects
WHERE projnum = :new.projnum;
IF( l_offer IS NULL )
THEN
INSERT INTO offer_log (offer, status, date)
VALUES (null, 9, sysdate);
END IF;
END;
Can you put the condition into the action (between BEGIN and END) instead of in the 'whether it fires'? Yes, it means that the trigger body might be fired more often - but if it gets you around the problem...