I have a Row, which i wannt to Insert (or Update if the ID is already used) from another Schema with an DBLINK. This Table has many FKs, so i need to Update or Insert(if doesnt exist) around 20 Tables.
I tried it by writting all needed Rows from the different Tables from the DBLINK into Variables and then checked if the Targeted Tables had this rows already. Im sure that there is a much easier way to do it. A loop where the tables and their rowtypes are saved in an Collection with execute immdediate for example, but i couldnt find anything.
This example is like 20 times in my Code and im sure that there is an easier way to do it then writting this over and over.
PROCEDURE Test_copy (pi_id IN NUMBER) IS
V_REC_Table1 Table1%ROWTYPE;
V_REC_Table2 Table2%ROWTYPE;
V_REC_Table3 Table3%ROWTYPE;
V_REC_Table4 Table4%ROWTYPE;
V_REC_Table5 Table5%ROWTYPE;
V_REC_Table6 Table6%ROWTYPE;
V_REC_Table7 Table7%ROWTYPE;
V_REC_Table8 Table8%ROWTYPE;
V_REC_Table9 Table9%ROWTYPE;
V_REC_Table10 Table10%ROWTYPE;
V_REC_Table1_exists Table1%ROWTYPE;
V_REC_Table2_exists Table2%ROWTYPE;
V_REC_Table3_exists Table3%ROWTYPE;
V_REC_Table4_exists Table4%ROWTYPE;
V_REC_Table5_exists Table5%ROWTYPE;
V_REC_Table6_exists Table6%ROWTYPE;
V_REC_Table7_exists Table7%ROWTYPE;
V_REC_Table8_exists Table8%ROWTYPE;
V_REC_Table9_exists Table9%ROWTYPE;
V_REC_Table10_exists Table10%ROWTYPE;
v_sql VARCHAR2(2000);
Begin
v_sql := 'SELECT * FROM Table1#dblink WHERE ID = '||pi_id;
EXECUTE IMMEDIATE v_sql INTO V_REC_DBLINK_Table1;
Exception
when no_data_found then
Raise_application_Error(-20001, 'ID doesnt exist');
END;
Begin
v_sql := 'SELECT * FROM Table2#dblink WHERE ID = '||V_REC_Table1.T2_ID;
EXECUTE IMMEDIATE v_sql INTO V_REC_DBLINK_Table2;
Exception
when no_data_found then
Raise_application_Error(-20001, 'ID doesnt exist');
END;
(and so on)
.
.
.
BEGIN
v_sql := 'SELECT * FROM Table1 WHERE ID = '||V_REC_DBLINK_TABLE1.ID;
EXECUTE IMMEDIATE v_sql INTO V_REC_DBLINK_TABLE1_EXISTS;
UPDATE TABLE1
SET ID = V_REC_DBLINK_Table1.ID,
NAME = V_REC_DBLINK_Table1.NAME,
DESCRIPTION = V_REC_DBLINK_Table1.DESCRIPTION
NOTE = V_REC_DBLINK_Table1.NOTE
NUMBER = V_REC_DBLINK_Table1.NUMBER
ADDRESS = V_REC_DBLINK_Table1.ADDRESS
WHERE ID = V_REC_DBLINK_TABLE1.ID;
EXCEPTION
when no_data_found then
INSERT INTO TABLE1
(ID, NAME, DESCRIPTION, NOTE, NUMBER, ADDRESS)
Values(V_REC_DBLINK_TABLE1.ID
,V_REC_DBLINK_TABLE1.NAME
,V_REC_DBLINK_TABLE1.DESCRIPTION
,V_REC_DBLINK_TABLE1.NOTE
,V_REC_DBLINK_TABLE1.NUMBER
,V_REC_DBLINK_TABLE1.ADDRESS);
END;
BEGIN
v_sql := 'SELECT * FROM Table2 WHERE ID = '||V_REC_DBLINK_TABLE2.ID;
EXECUTE IMMEDIATE v_sql INTO V_REC_DBLINK_TABLE2_EXISTS;
UPDATE TABLE2
SET ID = V_REC_DBLINK_Table2.ID,
NAME = V_REC_DBLINK_Table2.NAME,
DESCRIPTION = V_REC_DBLINK_Table2.DESCRIPTION
WHERE ID = V_REC_DBLINK_TABLE2.ID;
EXCEPTION
when no_data_found then
INSERT INTO TABLE2
(ID, NAME, DESCRIPTION)
Values(V_REC_DBLINK_TABLE2.ID
,V_REC_DBLINK_TABLE2.NAME
,V_REC_DBLINK_TABLE2.DESCRIPTION);
END;
(and so on for all Tables via DBLINK)
Any suggestions? I just want to do that in a shorter way(maybe via Collection and loop).
Related
I need to run the query which is saved in a field in one table and insert the value of query output into another table.
To simplify the ask I have created dummy tables and try to test the logic.
Table 1
create table tsql(id string, q string);
insert into tsql values (1,'Select current_date');
Table tsql has select query in field name q.
Table 2
create table tinput(d date);
Table 2 will get updated from the value in table 1.
Below are the stored procedure I am trying to write. I know this can be done in javascript stored proc but I need to write this in sql as we are following SQL for all other.
Procedure so far.
create or replace procedure sqlreadwrite(id string)
returns string
language sql
as
$$
Declare
select_statement String;
begin
create or replace temp table tk
as select q from tsql where id = :id;
--select_statement := 'insert into tinp values (execute immediate 'Select Q from tk')';
execute immediate 'insert into tinp values (execute immediate 'Select Q from tk')';
return 'Success';
end;
$$;
Till now it is failing.
you don't need to call execute immediate twice, you should call it only once. One thing i wee is you have not specified the list of columns in you INSERT statement , it is always better to specify the list of column.
-- untested
execute immediate 'insert into tinp values (Select Q from tk)';
I figured out a way, however I am open to suggestion and better approach.
Below is the query.
create or replace procedure sqlread(id string)
returns string
language sql
as
$$
Declare
select_statement String;
res resultset;
value string;
res1 resultset;
v string;
begin
create or replace temp table tk as select q from tsql where id = :id;
select_statement := 'Select Q from tk';
res := (execute immediate :select_statement);
let c1 cursor for res;
for row_variable in c1 do
value := row_variable.Q;
res1 := (execute immediate :value);
let c1 cursor for res1;
for row_variable1 in c1 do
v := row_variable1.current_date::date;
insert into tinp values (:v);
end for;
End For;
return v;
end;
$$;
If the target table (tinput) will only accept dates, I wonder what kind of queries are stored in the source table (tsql)? Anyway, I assume there is a business requirement behind this, so your approach should be OK.
Some improvements:
You may remove the TEMP table as it doesn't provide any benefits.
If your procedure will only run 1 query from tinput (if IDs are unique), then you don't need a loop.
Here is the sample code:
create or replace procedure sqlread(p_id string)
returns string
language sql
as
$$
Declare
select_statement String;
res resultset;
value string;
res1 resultset;
v string;
begin
select_statement := 'select q from tsql where id = ?';
res := (execute immediate :select_statement using (P_ID));
let c1 cursor for res;
open c1;
fetch c1 into value;
res1 := (execute immediate :value);
let c2 cursor for res1;
open c2;
fetch c2 into value;
insert into tinput values (:value);
return :value;
exception
when other then
return 'error';
end;
$$;
I have a request to add data that triggers a trigger that checks one condition, and only after that adds it to the purchase table. He also swears at the lines of initializations total_rasr and id_buyer invalid number, although there are the same types in the table and in the trigger. And the biggest question is, this trigger worked, but at one point it stopped and gives these errors.
INSERT INTO PAYMENT (ID, ADDRESS, DATE_PAYMENT, PAYMENT_METHOD_ID, EMPLOYEE_ID, BUYER_ID)
VALUES (Key('PAYMENT'), 'Moscow', TO_DATE('2002-08-23', 'YYYY-MM-DD'),'005!00002','1','002!00005');
trigger
create or replace TRIGGER checking_the_availability_work
BEFORE INSERT
ON PAYMENT
FOR EACH ROW
DECLARE
rasr_is_possible boolean := true;
total_rasr VARCHAR(10);
id_buyer VARCHAR(10);
AVAILABILITY_OF_WORK VARCHAR(5);
xSQL VARCHAR(100);
BEGIN
total_rasr := :NEW.PAYMENT_METHOD_ID;
id_buyer := :NEW.BUYER_ID;
IF total_rasr = '005!00002' THEN
xSQL := 'select AVAILABILITY_OF_WORK FROM BUYER WHERE ID =' || id_buyer;
execute immediate xSQL into AVAILABILITY_OF_WORK;
IF AVAILABILITY_OF_WORK = 'Нет' THEN
rasr_is_possible := false;
END IF;
END IF;
if not rasr_is_possible THEN
Raise_application_error(-20201, 'У вас нет места работы!');
end if;
END;
Why use dynamic SQL here at all? It seems really convoluted and is resulting in an error, perhaps when :new.buyer_id is null or has some other unexpected value? Try something like this, and perhaps consider a check on :new.buyer_id to make sure it has a value as expected.
DECLARE
L_AVAILABILITY_OF_WORK VARCHAR(5);
BEGIN
IF :NEW.PAYMENT_METHOD_ID = '005!00002' THEN
select AVAILABILITY_OF_WORK into L_AVAILABILITY_OF_WORK
FROM BUYER WHERE ID = :NEW.BUYER_ID;
IF L_AVAILABILITY_OF_WORK = 'Нет' THEN
Raise_application_error(-20201, 'У вас нет места работы!');
END IF;
END IF;
END;
I have a case when using instead-of-insert trigger is necessary. My colleagues and I wonder which one is more effective (memory usage, time to run, etc.).
The trigger checks whether the record exists in table, if no inserts the new row, otherwise updates existing row by its key. The primary key in this example is composite key of (DocumentId, VatRate).
The first variant is with checking whether the record already exists:
CREATE TRIGGER docvatsum_trg
ON DocumentVatSummary
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS (
SELECT 1 FROM DocumentVatSummary a
JOIN inserted b ON (a.DocumentId = b.DocumentId AND a.VatRate = b.VatRate)
)
BEGIN
UPDATE DocumentVatSummary
SET
DocumentVatSummary.VatBase = i.VatBase,
DocumentVatSummary.VatTotal = i.VatTotal
FROM inserted i
WHERE
DocumentVatSummary.DocumentId = i.DocumentId AND
DocumentVatSummary.VatRate = i.VatRate
END
ELSE
BEGIN
INSERT INTO DocumentVatSummary
SELECT * FROM inserted
END
END;
The second variant tries to insert and if insert fails an update follows:
CREATE TRIGGER docvatsum_trg
ON DocumentVatSummary
INSTEAD OF INSERT
AS
BEGIN
SAVE TRANSACTION savepoint
BEGIN TRY
INSERT INTO DocumentVatSummary
SELECT * FROM inserted
END TRY
BEGIN CATCH
IF XACT_STATE() = 1
BEGIN
ROLLBACK TRAN savepoint
UPDATE DocumentVatSummary
SET
DocumentVatSummary.VatBase = i.VatBase,
DocumentVatSummary.VatTotal = i.VatTotal
FROM inserted i
WHERE
DocumentVatSummary.DocumentId = i.DocumentId AND
DocumentVatSummary.VatRate = i.VatRate
END
END CATCH
END;
Note: Rollback to savepoint is required, because of TRY-CATCH implementation in running transaction in TSQL.
Which one is better and why? If you have better solution, please share.
Use MERGE in your trigger as explained here:
MERGE SYNTAX
Code Example:
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(20));
MERGE INTO Sales.SalesReason AS Target
USING (VALUES ('Recommendation','Other'),
('Review', 'Marketing'),
('Internet', 'Promotion'))
AS Source (NewName, NewReasonType)
ON Target.Name = Source.NewName
WHEN MATCHED THEN
UPDATE SET ReasonType = Source.NewReasonType
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, ReasonType) VALUES (NewName, NewReasonType)
OUTPUT $action INTO #SummaryOfChanges;
In a master detail relationship
procedure TDataModule.TABLE1AfterScroll(DataSet: TDataSet);
begin
if TABLE1.FieldByName('UNIT_ID').AsString <> '' then
begin
QUERY1.Close;
QUERY1.SQL.Clear;
QUERY1.SQL.Text:= 'select * from TABLE2 where UNIT_ID = ' +
TABLE1.FieldByName('UNIT_ID').AsString;
QUERY1.Open;
end;
How can I make sure that when editing (and then saving) the QUERY1 results someone does not enter the nonexistent UNIT_ID?
I want the UNIT_ID to be able to get changed, but I want to make sure someone does not enter UNIT_ID that does not exist.
AfterScroll is not right event use beforepost instead in this way:
procedure TDataModule.TABLE1BeforePost(DataSet: TDataSet);
begin
if TABLE1.FieldByName('UNIT_ID').AsString <> '' then
begin
QUERY1.Close;
QUERY1.SQL.Text:= 'select UNIT_ID from TABLE2 where UNIT_ID = ' +
TABLE1.FieldByName('UNIT_ID').AsString;
QUERY1.Open;
if QUERY1.REcordcount=0 then ABORT; //so if there not a such
// unit_id just don't save it
end;
end;
I have a messages table. After an insert on messages, I need to insert the UserID and MsgID from that insert into the messageRecipient table. If the message was sent to a group, it needs to be inserted to every user that is a member of that group. Here is what I have, but it is not inserting into the messageRecipient table:
create or replace trigger update_messages
after insert on messages referencing new as new old as old
for each row
declare
userID1 int(10);
msgID1 int(10);
groupID1 int(10);
begin
userID1 := :new.ToUserID;
msgID1 := :new.msgID;
groupID1 := :new.ToGroupID;
if inserting then
if(userID1 <> null)
then INSERT INTO messageRecipient VALUES(msgID1, userID1);
elsif(groupID1 <> null)
THEN INSERT INTO messageRecipient(msgID, userID) SELECT msgID1, userID FROM groupMembership WHERE gID = groupID1;
end if;
end if;
end;
/
What exactly is going wrong here?
create or replace trigger update_messages
-- after insert on messages referencing new as new old as old
-- for each row
declare
userID1 int(10);
msgID1 int(10);
groupID1 int(10);
begin
userID1 := :new.ToUserID;
msgID1 := :new.msgID;
groupID1 := :new.ToGroupID;
if(userID1 is not null)
then INSERT INTO messageRecipient VALUES(msgID1, userID1);
elsif(groupID1 is not null)
THEN INSERT INTO messageRecipient(msgID, userID) SELECT msgID1, userID FROM groupMembership WHERE gID = groupID1;
end if;
end;
Comparing to null in PL/SQL will evaluate to null. And conditional statements execute only on true.
Check here for some reference.
Change userID1 <> null to userID1 is not null and groupID1 <> null to groupID1 is not null.
Also you don't need to add if inserting since this trigger is only for insert statement.
Perhaps you are checking the messageRecipient table from another session.
Remember that the trigger is part of the transaction that does the insert into messages table, therefore no changes are visible until a commit comes along.
I suggest you need to answer the question
'How do I work out what is going wrong here'
If you add an insert before your logic...
INSERT INTO messageRecipient VALUES(msgID1, userID1);
if inserting then.....
This would tell you the trigger is firing and it is picking up your new values
so it must be the comparison logic that is not working..
If you had a catch all condition you could raise an error and this would also allow you to see exactly where the problem is
if(userID1 is not null) then
INSERT INTO messageRecipient VALUES(msgID1, userID1);
elsif(groupID1 is not null) THEN
INSERT INTO messageRecipient(msgID, userID)
SELECT msgID1, userID FROM groupMembership WHERE gID = groupID1;
else
RAISE ERROR HERE
end if;