Oracle SQL Trigger insert/update--- - triggers

Please help me with the issue, I want to write a trigger where I can insert the values into new table whenever insert/update happens in source table.
Below is the table structure from where I want to fetch data into another table.
LISTING TABLE
Name Null Type
LISTINGID NOT NULL VARCHAR2(28)
LISTINGMANAGERID NOT NULL VARCHAR2(28)
MANAGEAVAILABILITYFLAG VARCHAR2(1)
AVAILABILITYTEXT VARCHAR2(2000)
AREAINQUIRYFLAG VARCHAR2(1)
COUNTRYTEXT VARCHAR2(50)
STATEPROVINCETEXT VARCHAR2(50)
CITYTEXT VARCHAR2(50)
CHECKINTIME VARCHAR2(10)
CHECKOUTTIME VARCHAR2(10)
TIMEZONEID VARCHAR2(20)
PERSONALLINKURL VARCHAR2(150)
GOLDSUBSCRIPTIONSINCE DATE
AUDITPASSFLAG NOT NULL VARCHAR2(1)
MGRONLINEFLAG NOT NULL VARCHAR2(1)
ADMINAPPROVALFLAG NOT NULL VARCHAR2(1)
DELETEDFLAG VARCHAR2(1)
LASTUPDATED NOT NULL DATE
UPDATEDBY NOT NULL VARCHAR2(28)
OCA NOT NULL NUMBER(38)
SUSPENDEDFLAG NOT NULL VARCHAR2(1)
POSSIBLEFEATUREDCITYFLAG NOT NULL VARCHAR2(1)
TOTALPHOTOS NUMBER(38)
SUSPENDEDDATE DATE
OFFLINEDATE DATE
CURRENCY VARCHAR2(3)
POINTCHARGE VARCHAR2(2)
AVERAGEOFREVIEWS FLOAT(126)
NUMBEROFREVIEWS NUMBER(38)
RENTALMODEL VARCHAR2(10)
NOTHANKS VARCHAR2(1)
DIGITALSIGN VARCHAR2(50)
Need result in below table with any update or new insert from listing table.
i)listingid should contain listingid's from listing table.
ii)OFFLINE COLUMN should contain data with below condition.
AuditPassFlag = 'Y' AND AdminApprovalFlag='Y' AND MgrOnlineFlag='Y' AND DeletedFlag is NULL AND SuspendedFlag !='Y'
iii)TIMESTAMP COLUMN lastupdated date from listing table.
LISTING_LASTUPDATE TABLE
Name Null Type
LISTINGID NOT NULL VARCHAR2(20)
IS_OFFLINE VARCHAR2(1)
TIMESTAMP TIMESTAMP(0)
Below the trigger I am have written:
Its not working properly for where condition which means Listing is online: apart from that its work fine.
CREATE OR REPLACE TRIGGER listingLast_updated
AFTER INSERT OR UPDATE
ON listing
FOR EACH ROW
DECLARE
L_Listingid VARCHAR2 (20);
L_ISOFFLINE VARCHAR2 (1);
LASTUPDATED_TIMESTAMP DATE;
BEGIN
SELECT SYSDATE INTO LASTUPDATED_TIMESTAMP FROM DUAL;
IF ( :NEW.AuditPassFlag = 'Y'
AND :NEW.AdminApprovalFlag = 'Y'
AND :NEW.MgrOnlineFlag = 'Y'
AND :NEW.DeletedFlag IS NULL
AND :NEW.SuspendedFlag != 'Y') THEN
L_ISOFFLINE := 'Y';
ELSE
L_ISOFFLINE := 'N';
INSERT
INTO LISTING_LastUPDATED (LISTINGID, IS_OFFLINE, LASTUPDATED_TIMESTAMP)
VALUES (:NEW.LISTINGID, L_ISOFFLINE, LASTUPDATED_TIMESTAMP);
END IF;
END;

You've written:
Need result in below table with any update or new insert from listing table.
That is not what your trigger does. Take another look at your IF statement. It simplifies to:
if ... then
...
else
insert ...
end if;
Your INSERT is in the ELSE clause. You will therefore execute the INSERT when the initial IF statement is true. Not "with any update or new insert".
There are a number of other things you can change:
The variable L_Listingid is never used, remove it.
There's no need to use SELECT INTO ... in order to assign a variable. This can be done as follows in the declaration block:
lastupdated_timestamp date := sysdate;
However, I see no particular need for this variable to exist, so you can remove it and simply use SYSDATE in your sole INSERT.
There's no need to execute an ELSE at all. Set the default value of L_ISOFFLINE to be N and then use that if the IF condition doesn't evaluate to be true.
Two cosmetic changes, there's no need to wrap the ANDs in brackets; I've added the trigger name to the END so it's clearer.
You've you've also written:
TIMESTAMP COLUMN lastupdated date from listing table.
You actually have a LASTUPDATED date in your LISTING table, which means you're not following your own guidelines. You should use the column instead.
Putting all this together, your trigger might look like this:
create or replace trigger listinglast_updated
after insert or update
on listing
for each row
declare
l_isoffline varchar2(1) := 'N';
begin
if :new.auditpassflag = 'Y'
and :new.adminapprovalflag = 'Y'
and :new.mgronlineflag = 'Y'
and :new.deletedflag is null
and :new.suspendedflag != 'Y' then
l_isoffline := 'Y';
end if;
insert into listing_lastupdated (listingid, is_offline, lastupdated_timestamp)
values (:new.listingid, l_isoffline, :new.lastupdated);
end listinglast_updated;

Related

Get row number of row to be inserted in Postgres trigger that gives no collisions when inserting multiple rows

Given the following (simplified) schema:
CREATE TABLE period (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE course (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE registration (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
period_id UUID NOT NULL REFERENCES period(id),
course_id UUID NOT NULL REFERENCES course(id),
inserted_at timestamptz NOT NULL DEFAULT now()
);
I now want to add a new column client_ref, which identifies a registration unique within a period, but consists of only a 4-character string. I want to use pg_hashids - which requires a unique integer input - to base the column value on.
I was thinking of setting up a trigger on the registration table that runs on inserting a new row. I came up with the following:
CREATE OR REPLACE FUNCTION set_client_ref()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
next_row_number integer;
BEGIN
WITH rank AS (
SELECT
period.id AS period_id,
row_number() OVER (PARTITION BY period.id ORDER BY registration.inserted_at)
FROM
registration
JOIN period ON registration.period_id = period.id ORDER BY
period.id,
row_number
)
SELECT
COALESCE(rank.row_number, 0) + 1 INTO next_row_number
FROM
period
LEFT JOIN rank ON (rank.period_id = period.id)
WHERE
period.id = NEW.period_id
ORDER BY
rank.row_number DESC
LIMIT 1;
NEW.client_ref = id_encode (next_row_number);
RETURN NEW;
END
$function$
;
The trigger is set-up like: CREATE TRIGGER set_client_ref BEFORE INSERT ON registration FOR EACH ROW EXECUTE FUNCTION set_client_ref();
This works as expected when inserting a single row to registration, but if I insert multiple within one statement, they end up having the same client_ref. I can reason about why this happens (the rows don't know about each other's existence, so they assume they're all just next in line when retrieving their row_order), but I am not sure what a way is to prevent this. I tried setting up the trigger as an AFTER trigger, but it resulted in the same (duplicated) behaviour.
What would be a better way to get the lowest possible, unique integer for the rows to be inserted (to base the hash function on) that also works when inserting multiple rows?

POSTGRES INSERT/UPDATE ON CONFLICT using WITH CTE

I have a table like below. I am trying to merge into this table based on the value in a CTE. But when I try to update the table when there is a conflict, it cannot get the value in CTE
CREATE TABLE IF NOT EXISTS master_config_details
(
master_config_id INT NOT NULL,
account_id INT NOT NULL,
date_value TIMESTAMP(3) NULL,
number_value BIGINT NULL,
string_value VARCHAR(50) NULL,
row_status SMALLINT NOT NULL,
created_date TIMESTAMP(3) NOT NULL,
modified_date TIMESTAMP(3) NULL,
CONSTRAINT pk_master_config_details PRIMARY KEY (master_config_id, account_id, row_status)
);
INSERT INTO master_config_details VALUES (
1, 11, NULL,100,NULL, 0, '2020-11-18 12:01:18', '2020-11-18 12:02:31');
select * from master_config_details;`
Now using a cte I want to insert/update records in this table. Below is the code I am using to do the same. When the record already exist in the table I want to update the table based on the data_type_id value in the cte (cte_input_data.data_type_id ) but it fails with the error.
SQL Error [42703]: ERROR: column excluded.data_type_id does not exist
what it should achieve is
if cte_input_data.data_type_id = 1 update master_config_details set date_value = cte.value
if cte_input_data.data_type_id = 2 update master_config_details set number_value = cte.value
if cte_input_data.data_type_id = 3 update master_config_details set string_value = cte.value
The below code should do an update to the table master_config_details.number_value = 22 as there is already a record in that combination (master_config_id, account_id, row_status) which is (1,11,1) ( run this to see the record select * from master_config_details;) but its throwing an error instead
SQL Error [42703]: ERROR: column excluded.data_type_id does not exist
WITH cte_input_data AS (
select
1 AS master_config_id
,11 AS account_id
,2 AS data_type_id
,'22' AS value
,1 AS row_status)
INSERT INTO master_config_details
SELECT
cte.master_config_id
,cte.account_id
,CASE WHEN cte.data_type_id = 1 THEN cte.value::timestamp(3) ELSE NULL END AS date_time_value
,CASE WHEN cte.data_type_id = 2 THEN cte.value::integer ELSE NULL END AS number_value
,CASE WHEN cte.data_type_id = 3 THEN cte.value ELSE NULL END AS string_value
,1
,NOW() AT TIME ZONE 'utc'
,NOW() AT TIME ZONE 'utc'
FROM cte_input_data cte
ON CONFLICT (master_config_id,account_id,row_status)
DO UPDATE SET
date_value = CASE WHEN excluded.data_type_id = 1 THEN excluded.date_time_value::timestamp(3) ELSE NULL END
,number_value = CASE WHEN excluded.data_type_id = 2 THEN excluded.number_value::integer ELSE NULL END
,string_value = CASE WHEN excluded.data_type_id = 3 THEN excluded.string_value ELSE NULL END
,modified_date = NOW() AT TIME ZONE 'utc';
Special excluded table is used to reference values originally proposed for insertion.
So you’re getting this error because this column doesn’t exist in your target table, and so in special excluded table. It exists only in your cte.
As a workaround you can select it from cte using nested select in on conflict statement.

I'm having an issue with this code when I try to input values into the transactions table

So I'm setting up a schema in which I can input transactions of a journal entry independent of each other but also that rely on each other (mainly to ensure that debits = credits). I set up the tables, function, and trigger. Then, when I try to input values into the transactions table, I get the error below. I'm doing all of this in pgAdmin4.
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
id UUID PRIMARY KEY,
test_date DATE NOT NULL,
balance NUMERIC(12,2)
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_biut()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS ''balance''
FROM
transactions
GROUP BY
entry_id;';
END;
$$;
CREATE TRIGGER transactions_biut
BEFORE INSERT OR UPDATE ON transactions
FOR EACH ROW EXECUTE PROCEDURE transactions_biut();
INSERT INTO transactions (
entry_id,
post_date,
account_id,
description,
amount
)
VALUES
(
'1',
'2019-10-01',
'101',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
-200.00
),
(
'1',
'2019-10-01',
'505',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
200.00
);
After I execute this input, I get the following error:
ERROR: column "id" of relation "entries" does not exist
LINE 1: INSERT INTO entries (id,test_date,balance)
^
QUERY: INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS "balance"
FROM
transactions
GROUP BY
entry_id;
CONTEXT: PL/pgSQL function transactions_biut() line 2 at EXECUTE
SQL state: 42703
There are a few problems here:
You're not returning anything from the trigger function => should probably be return NEW or return OLD since you're not modifying anything
Since you're executing the trigger before each row, it's bound to fail for any transaction that isn't 0 => maybe you want a deferred constraint trigger?
You're not grouping by post_date, so your select should fail
You've defined entry_id as INTEGER, but entries.id is of type UUID
Also note that this isn't really going to scale (you're summing up all transactions of all days, so this will get slower and slower...)
#chirs I was able to figure out how to create a functioning solution using statement-level triggers:
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
entry_id INTEGER PRIMARY KEY,
post_date DATE NOT NULL,
balance NUMERIC(12,2),
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_entries() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO entries
SELECT o.entry_id, o.post_date, SUM(o.amount) FROM old_table o GROUP BY o.entry_id, o.post_date;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO entries
SELECT o.entry_id, n.post_date, SUM(n.amount) FROM new_table n, old_table o GROUP BY o.entry_id, n.post_date;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO entries
SELECT n.entry_id,n.post_date, SUM(n.amount) FROM new_table n GROUP BY n.entry_id, n.post_date;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER transactions_ins
AFTER INSERT ON transactions
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_upd
AFTER UPDATE ON transactions
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_del
AFTER DELETE ON transactions
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
Any thoughts on optimization?

t-sql: Return 1 row or everything based on optional paramater

I have a stored procedure that takes a single optional parameter. If the parameter exists it should update the 1 record, otherwise everything else. Are there a way to do in with a single SQL statement without using dynamic SQL?
Something like this:
CREATE PROCEDURE UpdateEmployees (#PersonID varchar(10) = null)
AS
BEGIN
UPDATE Employees
SET Field1 = 'Changed'
WHERE (PersonID Is Not Null OR PersonID = ISNULL(#PersonID, '')) --this not 100% yet.
END
You're almost there - you need to perform the update on a row with the supplied #PersonId or if the parameter is null (not if it's not, as you currently have).
Additionally, The isnull is redundant, as null will return "unknown" (which is not true) on any = operation.
CREATE PROCEDURE UpdateEmployees (#PersonID varchar(10) = null)
AS
BEGIN
UPDATE Employees
SET Field1 = 'Changed'
WHERE (#PersonID IS NULL OR PersonID = #PersonID)
END

Update specific columns in SQL Server table and ignoring Null values

I have a database table with many columns. Is there sql that will update records in that table where all or only specific columns are handled in such a way that if NULL is passed for any column value that the existing value not be changed?
Currently I can use solutions like these
UPDATE table
SET column1 = COALESCE(#param1, column1),
column2 = COALESCE(#param2, column2),
...
WHERE id = #id
or
UPDATE table
set column1 = isnull(#param1,column1),
column2 = isnull(#param2,column2)
They both works well, though sometimes I want to explicitly save null in any column and I can't do it with the above solutions. How?
One approach is to declare two parameters for each column, the first contains the value, the second is a bit instructs the query to insert null explicitly.
Example
create table example (column1 nvarchar(255), column2 nvarchar(255))
create procedure pUpdate(
#column1 nvarchar(255) = null,
#nullColumn1 tinyint = 0,
#column2 nvarchar(255) = null,
#nullColumn2 tinyint = 0
) as
BEGIN
update example
set column1 = Case When #nullcolumn1 = 1
Then NULL ELSE IsNull(#column1, column1) End
set column2 = Case When #nullcolumn2 = 1
Then NULL ELSE IsNull(#column2, column2) End
END
Then when calling from code, you only have to pass the parameters that you know need updating, or explicitely set the #nullcolumn to force a null.