Goal: Create a trigger function to update a table, payment_detail. The table payment_detail comprises of an inner join of two other tables (customer & payment). The trigger function is focused only on updating payment_detail when an UPDATE operation occurs on the payment table.
Step 1: Create detail table
CREATE TABLE IF NOT EXISTS payment_detail AS
SELECT customer.customer_id,
customer.first_name,
customer.last_name,
payment.payment_id,
payment.amount,
to_char(payment.payment_date, 'Mon YYYY') AS month_yr
FROM payment
INNER JOIN customer ON payment.customer_id = customer.customer_id;
The table above works great. Next, I create a trigger function to automatically update the above table (I know, VIEWS would be better than updating a table, but that's not the problem):
Step 2: Create trigger function
CREATE OR REPLACE FUNCTION update_payment_detail()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$$
BEGIN
UPDATE payment_detail
SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE customer.customer_id = NEW.customer_id;
RETURN NULL;
END;
$$
CREATE TRIGGER update_payment_detail
AFTER UPDATE
ON payment
FOR EACH ROW
EXECUTE PROCEDURE update_payment_detail();
Step 3: Test the trigger function
Now, in order to test the trigger function, I update the payment table as follows:
UPDATE payment
SET amount = 4.35
WHERE payment_id = 32126 AND customer_id = 1;
View the payment_detail table to verify updated record:
SELECT * FROM payment_detail WHERE customer_id = 1;
The result is a single record (the same exact record I updated above) being repeated throughout the entire payment_detail table and that's the problem. Why does it do that? There should only be one such record, among many other unique records. And if I DROP the payment_detail at this point and then re-create it and then just run the SELECT * FROM payment_detail statement above, the payment_detail table comes out perfectly fine with just the one record updated. So it's not clear what is happening here. How could I resolve this?
Take a look at your update script:
UPDATE payment_detail
SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE customer.customer_id = NEW.customer_id;
It says you update all payment_detail records with the data from customer of currently updated record. So on every update you update all the rows in payment_detail while you should update only the rows matching current payment (add AND payment_detail.payment_id = OLD.payment_id to your WHERE part).
EDIT 1
So the result UPDATE statement would look like:
UPDATE payment_detail SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE
customer.customer_id = NEW.customer_id
AND payment_detail.payment_id = OLD.payment_id;
You have to use OLD.payment_id(not NEW) in WHERE clause to handle cases where payment ID changes.
Related
So i want to insert data to history_rent table and delete data in rent table after update status_peminjaman column on rent table, i am already create Trigger but it doesn't triggered
CREATE OR ALTER TRIGGER AfterUpdateStatus on dbo.peminjaman
FOR UPDATE
AS DECLARE
#nama_peminjam varchar(100),
#tanggal_pinjam datetime,
#tanggal_kemblali datetime,
#nama_guru varchar(100),
#status_peminjaman varchar(50),
#kode_barang varchar(255);
SELECT #nama_peminjam = ins.nama_peminjam FROM INSERTED ins;
SELECT #tanggal_pinjam = ins.tanggal_pinjam FROM INSERTED ins;
SELECT #tanggal_kembali = ins.tanggal_kembali FROM INSERTED ins;
SELECT #nama_guru = ins.nama_guru FROM INSERTED ins;
SELECT #kode_barang = ins.kode_barang FROM INSERTED ins;
SELECT #status_peminjaman = ins.status_peminjaman FROM INSERTED ins;
IF UPDATE(status_peminjaman)
BEGIN
SET #status_peminjaman = 'Selesai'
END
INSERT INTO dbo.history_peminjaman
VALUES(#nama_peminjam,#tanggal_pinjam,#tanggal_kembali,#nama_guru,#kode_barang,#status_peminjaman);
PRINT 'TRIGEREDDDDDDDDD'
GO
I have a trigger like this: (Basically on update of a column in table1, I update a column in table 2)
CREATE OR REPLACE TRIGGER AAA AFTER UPDATE
ON TABLE_1 REFERENCING NEW AS NEWROW OLD AS OLDROW
FOR EACH ROW
WHEN (
NEWROW.DELETED ='Y' AND NEWROW.ID IN (41,43)
AND OLDROW.DELETED = 'N'
)
DECLARE
id_1 number;
id_2 number;
id_3 number;
BEGIN
select id_1, id_2,id_3 into id_1,id_2,id_3 from table_1 where id_1 = :NEWROW.id1 and id2 = some_other_row.id2;
if id_1 is null
then
update table2 set deleted = 'Y' , where table2.id_1 = id_1 and table2.id_2=id_2 and table2.id_3 = id_3;
end if;
EXCEPTION
WHEN OTHERS
THEN
-- Consider logging the error and then re-raise
RAISE;
END AAA;
/
When I update table1 I get:
ORA-04091: table table1 is mutating, trigger/function may not see it
I thought this error happens only when you are updating the table on which the trigger is trying to update something. But here I am updating table1 and trigger is supposed to update table2. SO why is the error?
It's the SELECT statement that is causing the problem here. Inside the trigger, you cannot SELECT from the same table. In your example, you don't need/can't use the SELECT statement. You can get the values by simply using :newrow.id_1, :newrow.id_2 and :newrow.id_3.
I'm trying to search the same text across to fields in my database for a livesearch box.
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(
ui.user_id = u.id AND
CAST(ui.invoice AS TEXT) = 'searchterm'
)
This query searches the invoice table and returns results properly and extremely quickly.
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(u.username like '%searchterm%')
This query searches for a matching username and returns extremely quickly as well.
But when I combine the two like this:
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(
u.username like '%searchterm%' OR
(
ui.user_id = u.id AND
CAST(ui.invoice AS TEXT) = 'searchterm'
)
)
It returns the proper results but take almost a minute to do so. What am I doing wrong?
EDIT: EXPLAINs of my queries:
First:
http://explain.depesz.com/s/PvS
Second:
http://explain.depesz.com/s/D5c
Combined:
http://explain.depesz.com/s/Dhf
Edited for mistake in copying the cast lines.
Here's how I solve this problem in my main app.
I have a main entity that I want users to be able to search for. Call it customer. This entity has associated detail records in a 1:n contact (for phone, email, etc) table.
I define a view, customer_quicksearch, that calculates a quicksearch key - a text field containing the concatenation of contact records for a customer along with some of the customer fields directly.
I've added triggers to customer and contact customer_summary table. The customer trigger adds a record to customer_summary when a row is inserted into customer and delete the row when the customer record is deleted. They update customer_summary by SELECTing an updated quicksearch key from `customer_quicksearch. I could use a SQL function for this instead of a view, but found the view both more useful and faster. With a view it's quicker to calculate the quicksearch keys for all customers, say, after a bulk insert or update.
CREATE VIEW customer_quicksearch AS
SELECT
customer.id AS customer_id, array_to_string(ARRAY[
customer.code,
customer.name,
string_agg(array_to_string(ARRAY[
contact.email::text,contact.altemail::text, contact.mobile_phone, contact.work_phone, contact.home_phone, contact.fax
],'|'),'|')
], '|') AS quicksearch_key
FROM customer
LEFT OUTER JOIN contact ON (customer.id = contact.customer_id)
GROUP BY customer.id;
and one of the triggers:
CREATE OR REPLACE FUNCTION customer_summary_update_for_contact() RETURNS trigger AS $$
DECLARE
_customer_id integer;
BEGIN
-- When a contact is added/removed/changed we have to regenerate the customer search key
IF tg_op = 'INSERT' OR tg_op = 'UPDATE' THEN
_customer_id = NEW.customer_id;
ELSE
_customer_id = OLD.customer_id;
END IF;
UPDATE customer_summary
SET quicksearch_key = (SELECT quicksearch_key FROM customer_quicksearch WHERE customer_id = _customer_id)
WHERE customer_id = _customer_id;
RETURN NULL;
END;
$$
LANGUAGE 'plpgsql'
SET search_path = 'public';
CREATE TRIGGER customer_summary_update_for_contact_trg AFTER INSERT OR UPDATE OR DELETE ON contact
FOR EACH ROW EXECUTE PROCEDURE customer_summary_update_for_contact();
You also need a trigger on customer to handle insert, update and delete of customer, maintaining the customer_summary record for that customer appropriately.
The customer_summary table contains records that include a quicksearch_key that's a pipe-concatenation of fields, like:
'1800MA|1800 MAKE IT BUILDERS|info#1800makeit.example.com|1234 5678|0499 999 999'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
[from customer record] [from 1st contact record] [from another contact record]
This is searched with a simple LIKE pattern. I could add a text_pattern_ops index on it for improved performance if I was doing prefix searches, but since I'm mostly doing searches with no left or right anchor - LIKE '%search%' - there's no benefit.
No-op (transformed into JOIN syntax) (not an anwer!) :
SELECT DISTINCT u.id, u.username
FROM
users AS u
JOIN user_invoice AS ui ON u.username like '%searchterm%'
OR ( ui.user_id = u.id AND ui.invoice = CAST('searchterm' AS INTEGER))
JOIN user_roles AS ur ON u.id = ur.user_id
JOIN roles AS r ON ur.role_id = r.id
WHERE r.name = 'teacher'
;
The CAST('searchterm' AS INTEGER)) makes no sense to me. Double quotes? parameter?
I have a trigger which is as follows:
ALTER TRIGGER [trigger_CATEGORY_VALUE_ID] ON [dbo].[tblA]
FOR UPDATE
AS
SET NOCOUNT ON
IF ( UPDATE([CATEGORY_VALUE_ID]))
BEGIN
INSERT INTO [dbo].[htblB]
( ID
, CATEGORY_VALUE_ID
, STATUS_END_DATE
, STATUS_END_DATE_SOURCE)
SELECT
t.ID
, t.CATEGORY_VALUE_ID
, GETDATE()
, t.UPDATE_SOURCE
FROM [dbo].[tblCAPITATION] t
INNER JOIN inserted ins
ON t.CATEGORY_VALUE_ID = ins.CATEGORY_VALUE_ID
END
What it needs to do is insert a new row in htblB when the column CATEGORY_VALUE_ID is updated. It works fine if only one row is updated. But if it has multiple row updates, then 2 to the power number of rows updated amount of new rows are inserted in htblB.
UPDATE dbo.tblCAPITATION
SET CAPITATION_STATUS_CATEGORY_VALUE_ID = '80574', UPDATE_SOURCE = 'TEST3'
WHERE CAPITATION_ID = 2 OR CAPITATION_ID = 3
This statement will insert 4 new rows to htblB instead of 2.
May you please shed some light on why this is hapening and how to prevent it?
Thanks!
I'm going to assume that ID is the primary key, if so then you should be joining inserted on ID not category_value_id
ALTER TRIGGER [trigger_CATEGORY_VALUE_ID] ON [dbo].[tblA]
FOR UPDATE
AS
SET NOCOUNT ON
IF ( UPDATE([CATEGORY_VALUE_ID]))
BEGIN
INSERT INTO [dbo].[htblB]
( ID
, CATEGORY_VALUE_ID
, STATUS_END_DATE
, STATUS_END_DATE_SOURCE)
SELECT
t.ID
, t.CATEGORY_VALUE_ID
, GETDATE()
, t.UPDATE_SOURCE
FROM [dbo].[tblCAPITATION] t
INNER JOIN inserted ins
ON t.ID = ins.ID
END
How to create trigger on Update in transact sql, to set another field in updated row?
For example:
UPDATE table SET true_false = 1 WHERE ID = #ID
will run command:
UPDATE table SET date = GETDATE() WHERE ID = #ID
.
Please help. I can't figure it out ;)
Keep in mind that you must always allow for the possibility of multi-row updates in any trigger you write.
create trigger tr_U_YourTable
on YourTable
for Update
as
begin
if update(true_false)
update yt
set date = getdate()
from Inserted i
inner join Deleted d
on i.ID = d.ID
inner join YourTable yt
on i.ID = yt.ID
where coalesce(i.true_false,0) <> coalesce(d.true_false,0)
end