Oracle PL/SQL: Trigger Syntax - database

I'm kinda new (like 1 year of experience) in PL/SQL and I have to write a kind of tricky trigger, and I want to check if my syntax is correct, mainly the selects, inserts and variable value store I wrote.
I want to have a trigger that, when I insert a new row into a table, I have to make a select to that table to store inserted values into 2 variables I created:
create or replace trigger schema.trg_CP
after insert on "schema"."tdlrp"
referencing old as old
for each row
---------------------------------------------------------------------------------------------------------
declare
v_fkidnc schema.tdlrp.fkidnc%type;
v_errortype schema.tdlrp.xerrort%type;
v_fkerrorID schema.tepm.ferror%type;
v_linerror number;
v_pr schema.tpm.pipm%type
v_pkdocid_r schema.tddr.pidr%type
---------------------------------------------------------------------------------------------------------
begin
if inserting then
select fkidnc, xerrort
into v_fkidnc, v_errortype
from schema.tdlrp;
--
This is correctly made, right?
After that I have to make some if-elsif validations:
if v_fkidnc = 1 and if v_errortype = 1 i have to make a set of selects and inserts in a row
if v_fkidnc = 1 and if v_errortype = 2 i have to make another set of anothers selects and inserts, but the logic is all the same to every if-elsif validation:
create or replace trigger schema.trg_CP
after insert on "schema"."tdlrp"
referencing old as old
for each row
---------------------------------------------------------------------------------------------------------
declare
v_fkidnc schema.tdlrp.fkidnc%type;
v_errortype schema.tdlrp.xerrort%type;
v_fkerrorID schema.tepm.ferror%type;
v_linerror number;
v_pr schema.tpm.pipm%type
v_pkdocid_r schema.tddr.pidr%type
---------------------------------------------------------------------------------------------------------
begin
if inserting then
select fkidnc, xerrort
into v_fkidnc, v_errortype
from schema.tdlrp;
--
if v_fkidnc = 1 then
if v_errortype = 1 then
select ferror, fipcm
into v_fkerrorID, v_linerror
from schema.tepm;
select pipm
into v_pr
from schema.tpm
where fipcm := v_linerror;
insert into schema.tddr(pidr, fipc, xuser, datea, fiptm)
values(schema.seq_tddr.nextval, old.fipc,'A', systimestamp, v_pr);
select pidr
into v_pkdocid_r
from tddr
where fiptm := v_pr;
insert into schema.tere(pidr, ferror, fidre, xuser, datea, fipcm)
values(schema.seq_tere.nextval, v_fkerrorID, v_pkdocid_r, 'A', SYSTIMESTAMP, v_linerror);
elsif v_errortype = 2 then
select...
EXCEPTION
WHEN OTHERS THEN
RAISE;
END trg_CP;
For example: on these 2 select i made:
if v_errortype = 1 then
select ferror, fipcm
into v_fkerrorID, v_linerror
from schema.tepm;
select pipm
into v_pr
from schema.tpm
where fipcm := v_linerror;
i'm assigning values to v_fkerrorID and v_linerror (first select). On the second select i want a condition where fipcm is equal to the variable value i stored on the first select:
select ferror, **fipcm**
into v_fkerrorID, **v_linerror**
After that i want to make insert to another tables with the values i stored in above queries:
Before the second insert i have to make a select to the table where i first inserted to get the v_pkdocid_r value for the second insert
insert into schema.tddr(pidr, fipc, xuser, datea, fiptm)
values(schema.seq_tddr.nextval, old.fipc,'A', systimestamp, v_pr);
select pidr
into **v_pkdocid_r**
from tddr
where fiptm := v_pr;
----------------------------
insert into schema.tere(pidr, ferror, fidre, xuser, datea, fipcm)
values(schema.seq_tere.nextval, v_fkerrorID, **v_pkdocid_r**, 'A', SYSTIMESTAMP, v_linerror);
Am i doing it correctly?
Edit 16/09/2022
With all the suggestions, i changed my trigger to this:
create or replace trigger schema.trg_CP
after insert on tdlrp
referencing old as old new as new
for each row
---------------------------------------------------------------------------------------------------------
declare
v_fkerrorID schema.tepm.ferror%type;
v_linerror number;
v_pr schema.tpm.pipm%type;
v_pkdocid_r schema.tddr.pidr%type;
---------------------------------------------------------------------------------------------------------
--
begin
--
if :new.fkidnc = 1 then
if :new.errortype = 1 then
select ferror, fipcm
into v_fkerrorID, v_linerror
from schema.tepm; --this select only inserts one row to each variable
select pipm
into v_pr
from schema.tpm
where fipcm = v_linerror;
insert into schema.tddr(pidr, fipc, xuser, datea, fiptm)
values(schema.seq_tddr.nextval, old.fipc,'A', systimestamp, v_pr);
select pidr
into v_pkdocid_r
from tddr
where fiptm = v_pr;
insert into schema.tere(pidr, ferror, fidre, xuser, datea, fipcm)
values(schema.seq_tere.nextval, v_fkerrorID, v_pkdocid_r, 'A', SYSTIMESTAMP, v_linerror);
end if;
end if;
--
END trg_CP;
/
But i'm getting:
PL/SQL: ORA-00984 on:
select pipm
into v_pr
from schema.tpm
where fipcm = v_linerror;
The problem is not on the select statement, I removed both selects after both ifs, and now it tells me that the column is not allowed on the if statement... if i remove both ifs, turns me back to error on select statements
The error is when i put this insert:
insert into schema.tddr(pidr, fipc, xuser, datea, fiptm)
values(schema.seq_tddr.nextval, old.fipc,'A', systimestamp, v_pr);
after this select:
select pipm
into v_pr
from schema.tpm
where fipcm = v_linerror;

I think that mostly looks fine, except this part:
if inserting then
select fkidnc, xerrort
into v_fkidnc, v_errortype
from schema.tdlrp;
You don't need if inserting then, because your trigger has been defined as an after insert trigger - so it will always be TRUE.
Also, you can't select from the same table that your trigger is on (schema.tdlrp). That's what the NEW and OLD values are for - to access the new and old values of the row that you just inserted.
Instead, you can just do this:
v_fkidnc := :new.fkidnc;
v_xerrortype := :new.xerrort;
(or you can skip defining v_kidnc and v_xerrortype at all, and just use the :new variables)
Keep in mind that with the :new and :old variables, if you're using TOAD or SQL Developer, you may need to "run as script" rather than "run statement", to avoid it prompting you for a replacement value.

i solved my problem using a where condition on the first select, to not retrieve more than a row to the variables, used execute immediate, and used :old instead of old, like mentioned on above commentary.
Thank you everyone.

Related

Using CASE WHEN to insert into a new table with values() clause - Snowflake SQL (classic web interface)

I'm trying to use insert into() and values() based on an existing condition in my original table to create a new table. I do have working code that uses insert into() along with a select and where clause but I'm trying to see if it's possible to do a CASE WHEN statement outside of the values so that certain values are inserted into the new table based on a conditional.
-- Creation and inserting values into table invoice_original
create temporary table invoice_original (id integer, price number(12,2),
purpose varchar);
insert into invoice_original (id, price, purpose) values
(1, 11.11, 'Business'),
(2, 22.22, 'Personal'),
(3, 33.33, 'Business'),
(4, 44.44, 'Personal'),
(5, 55.55, 'Business');
-- Creates final empty table invoice_final
create temporary table invoice_final (
study_number varchar,
price number(12, 2),
price_type varchar
);
Code:
execute immediate $$
declare
new_price number(12,2);
new_purpose varchar;
c1 cursor for select price, purpose from invoice_original;
begin
for record in c1 do
new_price := record.price;
new_purpose := record.purpose;
-- This code runs!
insert into invoice_final(study_number, price, price_type)
select 1, :new_price, 'Dollars'
where :new_purpose ilike '%Business%';
insert into invoice_final(study_number, price, price_type)
select 2, :new_price, 'Dollars'
where :new_purpose not like '%Business%';
-- Does not run but this is what I'm trying to do instead
CASE
WHEN :new_purpose ilike '%Business%' then
insert into invoice_final(study_number, price, price_type)
values('1', :new_price, 'Dollars')
ELSE
insert into invoice_final(study_number, price, price_type)
values('2', :new_price, 'Dollars') END
end for;
end;
$$;
This is just a simplified example of what I'm trying to do as whole, but just really wondering if a case when insert into() values() is possible in this scenario.
You need to use INSERT ALL. Refer here for more.
Change code as below
execute immediate $$
declare
new_price number(12,2);
new_purpose varchar;
c1 cursor for select price, purpose from invoice_original;
begin
for record in c1 do
new_price := record.price;
new_purpose := record.purpose;
INSERT ALL
when npurpose ilike '%Business%' then
into invoice_final(study_number, price, price_type)
values ('1',nprice,'Dollars')
else
into invoice_final(study_number, price, price_type)
values('2',nprice,'Dollars')
select :new_price as nprice, :new_purpose as npurpose;
end for;
end;
$$;
Executing above will produce following result -
select * from INVOICE_FINAL;
STUDY_NUMBER
PRICE
PRICE_TYPE
1
11
Dollars
2
22
Dollars
1
33
Dollars
2
44
Dollars
1
56
Dollars
The CASE statement is allowed as branching construct:
Changes:
a) using INSERT INTO SELECT
b) each statement must end with ;
execute immediate $$
declare
new_price number(12,2);
new_purpose varchar;
c1 cursor for select price, purpose from invoice_original;
begin
for record in c1 do
new_price := record.price;
new_purpose := record.purpose;
CASE
WHEN :new_purpose ILIKE'%Business%' THEN
INSERT INTO invoice_final(study_number, price, price_type)
SELECT '1', :new_price, 'Dollars';
ELSE
INSERT INTO invoice_final(study_number, price, price_type)
SELECT '2', :new_price, 'Dollars';
END CASE;
end for;
end;
$$;
Disclaimer: Using cursor loop and loop in general should be used when there is no way of rewriting the code to set-based approach.
INSERT INTO invoce_final(study_number, price, price_type)
SELECT CASE WHEN purpose ILIKE'%Business%' THEN 1 ELSE 2 END,
price,
'Dollars'
FROM invoice_orginal;
I will write some example code soon.
But the form
INSERT INTO (SELECT FROM VALUES WHERE)
Allows all your N value lines to be present. And the CASE logic to be in the WHERE and the result SUB-SELECT is INSERTed.
The ofther option if you are in Snowflake Scripting is to use an IF around those INSERTS verse using a CASE
What I was meaning, done with "full SQL variables as my example is not part of a loop" is you can move the SQL around like so, and have one block:
create temporary table invoice_final (
study_number varchar,
price number(12, 2),
price_type varchar
);
set new_purpose = 'Business';
set new_price = 10.0::number(12, 2);
insert into invoice_final(study_number, price, price_type)
SELECT column1, column2, column3
FROM VALUES
('1', $new_price, 'Dollars'),
('2', $new_price, 'Dollars')
WHERE CASE WHEN $new_purpose ilike '%Business%' then '1' ELSE '2' END = column1;
Thus for you loop, the three $ would be replaced with :, but this inserts just one row.
the IF was meaning are the Snowflake Scripting IF, but there is examples of using CASE just below that also.

Inserting rows from one database table into two different tables in another database

I need to add about 600 records from one database to another one.
The first part inserts from a select like this:
INSERT INTO RelayMapper.dbo.radioSignals(CstarID, StarName, SystemName, StarSystemCount, SuperNova, DateCreated)
SELECT NEWID(), startName, systemName, 1, 1, getDate()
FROM AISourceMapper.dbo.radioSignals
WHERE rangeICW = 5
This is where it gets tricky and I don't know how to do it.
So for each row inserted above, I need to also insert related data into another table.
The NEWID() above would be used to insert a row and then I'd need to insert the starCoordinates as well from AISourceMapper.dbo.radioSignals and it would look something like this:
INSERT INTO RelayMapper.dbo.radioSources(CstarID, starCoordinates, isVerified)
VALUES('1150C651-5D9A-4C13-9BE7-EF4AZ2549112', 'R.A. 13h 27m, DEC. -47deg, 29m', 1)
starCoordinates is also from the same table and row that I'm SELECTing from(AISourceMapper.dbo.radioSignals)
Is there a way to do something like this?
One option is to copy all data from AISourceMapper.dbo.radioSignals into a temp table and at the same time assign GUIDS and then insert from this table into your destination tables.
SELECT NEWID() AS CstarID, *
INTO #TempTable
FROM AISourceMapper.dbo.radioSignals
WHERE rangeICW = 5
INSERT INTO RelayMapper.dbo.radioSignals( CstarID, StarName, SystemName, StarSystemCount, SuperNova, DateCreated )
SELECT CstarID, startName, systemName, 1, 1, getDate()
FROM #TempTable
INSERT INTO RelayMapper.dbo.radioSources( CstarID, starCoordinates, isVerified )
SELECT CstarID, starCoordinates, isVerified
FROM #TempTable
You can use OUTPUT clause to get the inserted values and then use them to insert into another table.
DECLARE #insertedId TABLE(CStartID UNIQUEIDENTIFIER)
INSERT INTO RelayMapper.dbo.radioSignals(CstarID, StarName, SystemName, StarSystemCount, SuperNova, DateCreated)
OUTPUT inserted.CStarID INTO #insertedId
SELECT NEWID(), startName, systemName, 1, 1, getDate()
FROM AISourceMapper.dbo.radioSignals
WHERE rangeICW = 5;
--with values clause
INSERT INTO RelayMapper.dbo.radioSources(CstarID, starCoordinates, isVerified)
SELECT CStarId
'R.A. 13h 27m, DEC. -47deg, 29m', 1
FROM #insertedId;
--WITH select clause
INSERT INTO RelayMapper.dbo.radioSources(CstarID, starCoordinates, isVerified)
SELECT i.CStarId, rs.starCoordinates, 1
FROM AISourceMapper.dbo.radioSignals AS rs
CROSS JOIN #insertedId AS i
WHERE rs.rangeICW = 5;

SQL Server script loop update and insert to different table without cursor

I am writing a simple SQL Server script to get id from a table and update the source table and insert into another table using cursor.
Is there a better way to use without cursor?
Here is my code with cursor:
DECLARE #CourseDelegateId INT;
DECLARE #UserId UNIQUEIDENTIFIER;
DECLARE Invite_Cursor CURSOR FOR
SELECT CourseDelegateId, UserGuid
FROM [dbo].[CourseDelegate]
WHERE StatusTypeId=1 AND EmailSent = 0 AND [Disabled] = 0
ORDER BY CourseDelegateId
OPEN Invite_Cursor
FETCH NEXT FROM Invite_Cursor INTO #CourseDelegateId, #UserId
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION
UPDATE [dbo].[CourseDelegate]
SET EmailSent = 1, NotificationTypeId = 1, Modified= GETUTCDATE()
WHERE CourseDelegateId = #CourseDelegateId;
INSERT INTO [dbo].[TrainingNotification]
([CourseDelegateId]
,[UserId]
,[NotificationTypeId]
,[CreatedBy]
,[Created]
,[ModifiedBy]
,[Modified]
,[Disabled])
VALUES(
#CourseDelegateId,
#UserId,
1,
ORIGINAL_LOGIN(),
GETUTCDATE(),
ORIGINAL_LOGIN(),
GETUTCDATE(),
0)
COMMIT
FETCH NEXT FROM Invite_Cursor INTO #CourseDelegateId, #UserId
END
CLOSE Invite_Cursor
DEALLOCATE Invite_Cursor
Seems like a temporary table and a INSERT INTO...SELECT FROM would far easier:
SELECT CourseDelegateId,
UserGuid
INTO #Invites
FROM [dbo].[CourseDelegate]
WHERE StatusTypeId = 1
AND EmailSent = 0
AND [Disabled] = 0
ORDER BY CourseDelegateId;
INSERT INTO [dbo].[TrainingNotification] ([CourseDelegateId],
[UserId],
[NotificationTypeId],
[CreatedBy],
[Created],
[ModifiedBy],
[Modified],
[Disabled])
SELECT CourseDelegateId,
UserGuid,
1,
ORIGINAL_LOGIN(),
GETUTCDATE(),
ORIGINAL_LOGIN(),
GETUTCDATE(),
0
FROM #Invites I;
UPDATE CD
SET EmailSent = 1,
NotificationTypeId = 1,
Modified = GETUTCDATE()
FROM [dbo].[CourseDelegate] CD
JOIN #Invites I ON CD.CourseDelegateId = I.CourseDelegateId;

After Update Trigger Insert old records to another table

I have MedicalAllowance Table i want to make vesrion from this table as history table "MedicalAllowanceHistory" i want to create trigger to do that after update
MedicalAllowance Table copy data to "MedicalAllowanceHistory" table .which way is better to do that .
MedicalAllowance table
ID | Employee_ID | Limit | Used | Balance
MedicalAllowanceHistory table
ID | Employee_ID | Limit | Used | Balance
you can use a trigger for that
But I would also store the date of the event, and the kind of event (insert, update or delete)
Here is an example to get you started
CREATE trigger tr_UID_MedicalAllowance on dbo.MedicalAllowance
after update, insert, delete
as
begin
set nocount on
declare #Insert bit = 0
declare #Update bit = 0
declare #Delete bit = 0
--find out why we where triggered
if (exists(select 1 from inserted)) and (exists(select 1 from deleted))
set #Update = 1
else if (exists(select 1 from inserted))
set #Insert = 1
else if (exists (select 1 from deleted))
set #Delete = 1
if #Update = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'UPDATED',
i.other columns...
from inserted i
end
if #Insert = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'INSERTED',
i.other columns...
from inserted i
end
if #Delete = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
other columns...)
select d.MedicalAllowanceID,
getdate(),
'DELETED',
d.other columns...
from deleted d
end
end
It is also possible to store old and new values in case of update, the new values are in the inserted table and the old are in the deleted table.
In that case the update part could look something like this
if #Update = 1
begin
insert into MedicalAllowanceHistory (
MedicalAllowanceID,
HistoryDate,
HistoryEvent,
NewLimit,
OldLimit,
other columns...)
select i.MedicalAllowanceID,
getdate(),
'UPDATED',
i.Limit,
d.Limit,
other columns...
from inserted i
inner join deleted d on i.MedicalAllowanceID = d.MedicalAllowanceID
end
I suggest you to use "Change Data Capture" instead of "Trigger". In this solution SQL tracks any changes. If you have need to learn more about it,click here.
You can use the following example to solve your problem:
CREATE TRIGGER tg_MedicalAllowance ON MedicalAllowance
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
INSERT MedicalAllowanceHistory
SELECT ID,Employee_ID,Limit,Used,balance
FROM deleted
END

Stored procedure multiple inserts error

I'm writing a simple stored procedure in SQL Server 2016 but when I have 2 insert statements I keep getting an error that says
Incorrect syntax near 'End'
Is there something wrong with my syntax or is this not possible?
Note that it is a table-valued parameter that I am sending into the stored procedure as arguments
SQL statement:
-- This is the stored procedure
CREATE PROCEDURE [dbo].[SampleProcedure]
( -- which accepts 2 table value parameters
-- It should be noted that the parameter is readonly
#Sample As [dbo].[SampleDataType] Readonly,
#Rec As [dbo].[SampleRecType] ReadOnly
)
AS
BEGIN
-- We simply insert values into the DB table from the parameter
-- The table value parameter can be used like a table with only read rights
-- INSERT INTO SampleTable(SampleString, SampleDate)
-- SELECT SampleString, SampleDate
-- FROM #Sample
INSERT INTO tbl1Recipients(strEmailSendIDx, strEmailAddress, strDisplayName, strRecipientType)
INSERT INTO tbl1SentEmails(strEmailSendID, dtmSent, strSubject, strBody, strConversationID, strConversationTopic, strConversationIndex, dtmReplied, dtmOpened, dtmClicked, blnTrackOpens, blnTrackClicks, blnTrackReplies, lngMergeID, blnHide, lngLatestEventID, strClickResponse, dtmClickResponse)
SELECT *
FROM #Sample
END
Edited, after reading the comments and the suggested answer this is what solved the issue:
-- This is the stored procedure
CREATE PROCEDURE [dbo].[SampleProcedure]
(
-- which accepts one table value parameter.
-- It should be noted that the parameter is readonly
#Sample As [dbo].[SampleDataType] Readonly,
#Rec As [dbo].[SampleRecType] ReadOnly
)
AS
BEGIN
-- We simply insert values into the DB table from the parameter
-- The table value parameter can be used like a table with only read rights
INSERT INTO tbl1Recipients(strEmailSendID, strEmailAddress, strDisplayName, strRecipientType)
SELECT *
FROM #Rec
INSERT INTO tbl1SentEmails(strEmailSendID, dtmSent, strSubject, strBody, strConversationID, strConversationTopic, strConversationIndex, dtmReplied, dtmOpened, dtmClicked, blnTrackOpens, blnTrackClicks, blnTrackReplies, lngMergeID, blnHide, lngLatestEventID, strClickResponse, dtmClickResponse)
SELECT *
FROM #Sample
END
All it needed was another SELECT after I entered the data into tbl1Recipients
This is your first insert:
Insert Into tbl1Recipients(strEmailSendIDx, strEmailAddress, strDisplayName, strRecipientType)
Select * From #Rec
What is expected next is VALUES or SELECT. The next token is INSERT, and that just doesn't make sense.
You need to include the data you want to insert.

Resources