Triggers in Liquibase - postgresql

I am using postgresql and I two tables customer (id , name, address, city) and update_customer(id, time). When ever I insert a new value in customer table I want to store that id and time in update_customer table. But I am currently getting an exception and I dont have an idea about what went wrong.
code:
<changeSet author="newUser" id="03">
<sql>
CREATE TRIGGER customer_trigger
AFTER INSERT ON customer
FOR EACH ROW
EXECUTE PROCEDURE customer_update();
CREATE FUNCTION customer_update() RETURNS trigger AS $example_table$
BEGIN
INSERT INTO update_customer(id,time) VALUES (NEW.id , current_timestamp);
RETURN NEW;
END;
</sql>
</changeSet>
error
Caused by: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for change set db/changelog/trigger.xml::03::newUser:

Using 'createProcedure' tag did the trick for me, I dont why while using 'sql' tag Liquibase kept on throwing errors saying something is wrong near Insert.

Related

Postgresql - Unable to create stored procedure as defintion contains temporary table

In postgresql 13, I am inserting data of a table into temporary table at run time. Query works fine when executed. But when I try to create store procedure on top of the query it fails with error: ERROR: "temp" is not a known variable
Can someone please help me to understand what am I missing?
DB FIDDLE
CREATE OR REPLACE PROCEDURE dbo.<proc-name>()
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS child;
SELECT Id, Name
into TEMP TABLE child
FROM dbo.master;
COMMIT;
END;
$$;
Thanks
SELECT ... INTO is PL/pgSQL syntax to store a query result in a variable. You should use CREATE TABLE ... AS SELECT.

Error handling in redshift stored procedures

When I tried to add exception handling in redshift stored procedure getting below error.
Please find the code below:
CREATE TABLE employee (firstname varchar, lastname varchar);
INSERT INTO employee VALUES ('Tomas','Smith');
CREATE TABLE employee_error_log (message varchar);
CREATE OR REPLACE PROCEDURE update_employee_sp() AS
$$
BEGIN
UPDATE employee SET firstname = 'Adam' WHERE lastname = 'Smith';
EXECUTE 'select invalid';
EXCEPTION WHEN OTHERS THEN
RAISE INFO 'An exception occurred.';
INSERT INTO employee_error_log VALUES ('Error message: ' || SQLERRM);
END;
$$
LANGUAGE plpgsql;
Error: Amazon invalid operation: ROLLBACK cannot be invoked from a procedure that is executing in an atomic context.
Anyone please provide the solution here.
One more query in redshift is how we can log an error message and error code in table. For example how we can log a DML or DDL errors in some table which occurs inside the stored procedure. Any system table available for getting error code and error message of DML errors like error_messages table in teradata.
Change the connection to autocommit=true. It will work.

Syntax Error in Postgresql Trigger Function

I'm brand new. The task was to create a trigger that will delete all dependent tuples from the depositor table when an account in a parent table is deleted. Here's what I've got so far. I keep getting a syntax error "at or near 'cust_id'" and I can't find it. Can you tell me if this trigger will do what I'm wanting it to do (delete the cust_id and account_number from the depositor table for the account that was deleted in the account table) and help me find the syntax error?
CREATE OR REPLACE FUNCTION Froehle_13_bankTriggerFunction()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
DELETE cust_id, account_number
FROM depositor
WHERE depositor.account_number = account.account_number;
END;
$$;
You need to make 3 changes:
Fix the delete statement syntax error by removing the columns. It must be just delete from table where ...
Replace account with the special keyword old, which refers to the row being deleted
Add a return statement, which is required for all trigger functions
Try this:
CREATE OR REPLACE FUNCTION Froehle_13_bankTriggerFunction()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
DELETE FROM depositor
WHERE depositor.account_number = OLD.account_number;
RETURN NULL;
END;
$$;
There is a final simplification you can make: Delete the entire function and instead alter the references constraint on the account_number column of depositor to add ON DELETE CASCADE.

postgres TRIGGER upsert with returning

i want an upsert functionality that returns the (new/existing) id of the row.
Linking to my previous question. I asked previously about RULE postgres create rule on insert do nothing if exists insert otherwise; RETURNING id but looks like it is not possible.
So I resort to a trigger
CREATE OR REPLACE FUNCTION upsert_asset() RETURNS trigger AS $trigger_bound$
BEGIN
INSERT INTO asset(symbol, name, type, status)
VALUES (NEW.symbol, NEW.name, NEW.type, NEW.status)
ON CONFLICT (symbol) DO UPDATE SET symbol = EXCLUDED.symbol;
RETURN NEW;
END;
$trigger_bound$
LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER upsert_asset_trigger
AFTER INSERT ON asset
FOR EACH ROW
EXECUTE PROCEDURE upsert_asset();
I tested the above and works. So my questions are
Is this trigger a correct way to achieve this functionality? Any race conditions/performance issues that i should know about?
How can I generalize this query, by not giving the column names? asset(symbol, name, type, status). i do not want to pay attention to this rule every time I change my table. Is it possible to say NEW.* or column.* or something? What psuedorelations are available to achieve this? Please note there are some default columns too. So how does NEW.default_column get a value incase the insert statement has left that column in the insert statement?
Thanks,

PL/pgSQL query in PostgreSQL returns result for new, empty table

I am learning to use triggers in PostgreSQL but run into an issue with this code:
CREATE OR REPLACE FUNCTION checkAdressen() RETURNS TRIGGER AS $$
DECLARE
adrCnt int = 0;
BEGIN
SELECT INTO adrCnt count(*) FROM Adresse
WHERE gehoert_zu = NEW.kundenId;
IF adrCnt < 1 OR adrCnt > 3 THEN
RAISE EXCEPTION 'Customer must have 1 to 3 addresses.';
ELSE
RAISE EXCEPTION 'No exception';
END IF;
END;
$$ LANGUAGE plpgsql;
I create a trigger with this procedure after freshly creating all my tables so they are all empty. However the count(*) function in the above code returns 1.
When I run SELECT count(*) FROM adresse; outside of PL/pgSQL, I get 0.
I tried using the FOUND variable but it is always true.
Even more strangely, when I insert some values into my tables and then delete them again so that they are empty again, the code works as intended and count(*) returns 0.
Also if I leave out the WHERE gehoert_zu = NEW.kundenId, count(*) returns 0 which means I get more results with the WHERE clause than without.
--Edit:
Here is an example of how I use the procedure:
CREATE TABLE kunde (
kundenId int PRIMARY KEY
);
CREATE TABLE adresse (
id int PRIMARY KEY,
gehoert_zu int REFERENCES kunde
);
CREATE CONSTRAINT TRIGGER adressenKonsistenzTrigger AFTER INSERT ON Kunde
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE checkAdressen();
INSERT INTO kunde VALUES (1);
INSERT INTO adresse VALUES (1,1);
It looks like I am getting the DEFERRABLE INITIALLY DEFERRED part wrong. I assumed the trigger would be executed after the first INSERT statement but it happens after the second one, although the inserts are not inside a BEGIN; - COMMIT; - Block.
According to the PostgreSQL Documentation inserts are commited automatically every time if not inside such a block and thus there shouldn't be an entry in adresse when the first INSERT statement is commited.
Can anyone point out my mistake?
--Edit:
The trigger and DEFERRABLE INITIALLY DEFERRED seem to be working all right.
My mistake was to assume that since I am not using a BEGIN-COMMIT-Block each insert would be executed in an own transaction with the trigger being executed afterwards every time.
However even without the BEGIN-COMMIT all inserts get bundled into one transaction and the trigger is executed afterwards.
Given this behaviour, what is the point in using BEGIN-COMMIT?
You need a transaction plus the "DEFERRABLE INITIALLY DEFERRED" because of the chicken and egg problem.
starting with two empty tables:
you cannot insert a single row into the person table, because the it needs at least one address.
you cannot insert a single row into the address table, because the FK constraint needs a corresponding row on the person table to exist
This is why you need to bundle the two inserts into one operation: the transaction. You need the BEGIN+ COMMIT, and the DEFERRABLE allows transient forbidden database states to exists: it causes the check to be evaluated at commit time.
This may seem a bit silly, but the answer is you need to stop deferring the trigger and run it BEFORE the insert. If you run it after the insert, of course there is data in the table.
As far as I can tell this is working as expected.
One further note, you probably dont mean:
RAISE EXCEPTION 'No Exception';
You probably want
RAISE INFO 'No Exception';
Then you can change your settings and run queries in transactions to test that the trigger does what you want it to do. As it is, every insert is going to fail and you have no way to move this into production without editing your procedure.