How to optimize pgplsql function? - postgresql

I have function which is triggered for each row. It has next statements:
SELECT DocDate
FROM Operation
WHERE Operation.ID = New.OperationID
INTO _DocDate;
EXECUTE PROCEDURE ChangeSaldo( _DocDate, ... );
Each row belongs to parent table Operation where we fetch DocDate.
We need run ChangeSaldo for each row with its own _DocDate
That is guaranteed that all rows belongs to same row in Operation table
Will Postgres execute this SELECT for each row or this statement will be cached?
If it is not cached is there a way to optimize SELECT so it will be executed only once?

I plan to create stable function and put there that SELECT.
Because of nature of stable functions the result will be cached

Related

Is logic blocks in stored procedure/server function run sequentially or run parallel like in CTE?

I had a problem that some of the CTE didn't run in the order I wanted, and I had no way to call one from the other.
WITH insert_cte AS (
INSERT INTO some_table
SELECT *
FROM (...) AS some_values_from_first_relationship
)
UPDATE some_table
-- here I had no way to call insert_cte and values from first relationship were not updated
SET <some_values_from_first_and_second_relation_ship>
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=c584581a91fbb1ca8f51c3b32c04283f
So I created server function via CREATE OR REPLACE FUNCTION/PROCEDURE and moved CTE to logic block BEGIN - END; like
<<main_label>>
BEGIN
<<insert_cte_analogue>>
BEGIN
[insert_cte_logic]
END;
<<update_cte_analogue>>
BEGIN
[update_cte_logic]
END;
END;
Will it run sequentially or I am going to run into the same problem as in the CTE?
I apologize for the comment I left on your last question suggesting that you force the execution order by referencing the previous CTE. I use that frequently for setting FK values that rely on PKs generated in prior insert CTEs and force the order by referring to what comes back from RETURNING *.
I have never tried your use case, and the docs say it is not possible to update the same row twice within a single statement:
https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-MODIFYING
Trying to update the same row twice in a single statement is not supported. Only one of the modifications takes place, but it is not easy (and sometimes not possible) to reliably predict which one. This also applies to deleting a row that was already updated in the same statement: only the update is performed. Therefore you should generally avoid trying to modify a single row twice in a single statement. In particular avoid writing WITH sub-statements that could affect the same rows changed by the main statement or a sibling sub-statement. The effects of such a statement will not be predictable.
Okay, it works. First the insert_cte_analogue logical block created rows, then the update_cte_analogue logical block updated this rows. I didn't need a commit between blocks. Everything went without errors. I think that logical blocks always will run sequentially.

Is INSTEAD OF UPDATE trigger the best option

I have to check when a table is inserted to/updated to see if a column value exists for the same HotelID and different RoomNo in the same table. I'm thinking that an INSTEAD OF trigger on the table would be a good option, but I read that it's a bad idea to update/insert the table the trigger executes on inside the trigger and you should create the trigger on a view instead (which raises more questions for me)
Is it ok to create a trigger like this? Is there a better option?
CREATE TRIGGER dbo.tgr_tblInterfaceRoomMappingUpsert
ON dbo.tblInterfaceRoomMapping
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #txtRoomNo nvarchar(20)
SELECT #txtRoomNo = Sonifi_RoomNo
FROM dbo.tblInterfaceRoomMapping r
INNER JOIN INSERTED i
ON r.iHotelID = i.iHotelID
AND r.Sonifi_RoomNo = i.Sonifi_RoomNo
AND r.txtRoomNo <> i.txtRoomNo
IF #txtRoomNo IS NULL
BEGIN
-- Insert/update the record
END
ELSE
BEGIN
-- Raise error
END
END
GO
So it sounds like you only want 1 row per combo of HotelID and Sonifi_RoomNo.
CREATE UNIQUE INDEX UQ_dbo_tblInterfaceRoomMapping
ON dbo.tblInterfaceRoomMapping(HotelID,Sonifi_RoomNo)
Now if you try and put a second row with the same values, it will bark at you.
It's (usually) not okay to create a trigger like that.
Your trigger assumes a single row update or insert will only ever occur - is that guaranteed?
What will be the value of #txtRoomNo if multiple rows are inserted or updated in the same batch?
Eg, if an update is performed against the table resulting in 1 row with correct data and 1 row with incorrect data, how do you think your trigger would cope in that situation? Remember triggers fire once per insert/update, not per row.
Depending on your requirments you could keep the instead of trigger concept, however I would suggest a separate trigger for inserts and for updates.
In each you can then insert / update and include a where not exists clause to only allow valid inserts / updates, ignoring inserting or updating anything invalid.
I would avoid raising an error in the trigger, if you need to handle bad data you could also insert into some logging table with the reverse where exists logic and then handle separately.
Ultimately though, it would be best for the application to check if the roomNo is already used.

The order of executing triggers on bulk inserts

Assume we are inserting 1000 (from 1 to 1000) rows in one statement into one table.
The table has one before and one after trigger.
What the order of processing of these rows?
At this moment I suppose:
Before trigger execution for 1st row
insert the 1st row
Before trigger execution for 2nd row
insert the 2nd row
......
Then at the end the after triggers will be called in an undefined order.
Am I right? Where can I find the proofs of my assumptions?
It depends your after insert trigger is row-level triggers or statement-level triggers.
Statement triggers are triggered once after each statement
...
FOR EACH STATEMENT
EXECUTE PROCEDURE xyz();
And if you want the trigger to execute for each affected row and that means you want a row-level trigger.
...
FOR EACH ROW
EXECUTE PROCEDURE xyz();

Select into an array of composite types in plpgsql

Are composite type arrays the correct way to simulate a "function scoped" table in plpgsql?
I want to store the results of a query for access throughout the function,
but I don't want to use a temp table because a single session may execute multiple function calls concurrently.
DECLARE
projectDocuments document_tracking.docmaster[];
SELECT * INTO projectDocuments FROM document_tracking.docmaster WHERE jobnumber = jobnumberparam;
Produces the malformed array literal error, am I supposed to use aggregate functions, or :=?
consider using
ON COMMIT DELETE ROWS
https://www.postgresql.org/docs/current/static/sql-createtable.html
All rows in the temporary table will be deleted at the end of each
transaction block. Essentially, an automatic TRUNCATE is done at each
commit.

Is it possible to dynamically loop through a table's columns?

I have a trigger function for a table test which has the following code snippet:
IF TG_OP='UPDATE' THEN
IF OLD.locked > 0 AND
( OLD.org_id <> NEW.org_id OR
OLD.document_code <> NEW.document_code OR
-- other columns ...
)
THEN
RAISE EXCEPTION 'Message';
-- more code
So I am statically checking all the column's new value with its previous value to ensure integrity. Now every time my business logic changes and I have to add new columns into that table, I will have to modify this trigger each time. I thought it would be better if somehow I could dynamically check all the columns of that table, without explicitly typing their name.
How can it be done?
From 9.0 beta2 documentation about WHEN clause in triggers, which might be able to be used in earlier versions within the trigger body:
OLD.* IS DISTINCT FROM NEW.*
or possibly (from 8.2 release notes)
IF row(new.*) IS DISTINCT FROM row(old.*)
Take a look at the information_schema, there is a view "columns". Execute a query to get all current columnnames from the table that fired the trigger:
SELECT
column_name
FROM
information_schema.columns
WHERE
table_schema = TG_TABLE_SCHEMA
AND
table_name = TG_TABLE_NAME;
Loop through the result and there you go!
More information can be found in the fine manual.
In Postgres 9.0 or later add a WHEN clause to your trigger definition (CREATE TRIGGER statement):
CREATE TRIGGER foo
BEFORE UPDATE
FOR EACH ROW
WHEN (OLD IS DISTINCT FROM NEW) -- parentheses required!
EXECUTE PROCEDURE ...;
Only possible for triggers BEFORE / AFTER UPDATE, where both OLD and NEW are defined. You'd get an exception trying to use this WHEN clause with INSERT or DELETE triggers.
And radically simplify the trigger function accordingly:
...
IF OLD.locked > 0 THEN
RAISE EXCEPTION 'Message';
END IF;
...
No need to test IF TG_OP='UPDATE' ... since this trigger only works for UPDATE anyway.
Or move that condition in the WHEN clause, too:
CREATE TRIGGER foo
BEFORE UPDATE
FOR EACH ROW
WHEN (OLD.locked > 0
AND OLD IS DISTINCT FROM NEW)
EXECUTE PROCEDURE ...;
Leaving only an unconditional RAISE EXCEPTION in your trigger function, which is only called when needed to begin with.
Read the fine print:
In a BEFORE trigger, the WHEN condition is evaluated just before the
function is or would be executed, so using WHEN is not materially
different from testing the same condition at the beginning of the
trigger function. Note in particular that the NEW row seen by the
condition is the current value, as possibly modified by earlier
triggers. Also, a BEFORE trigger's WHEN condition is not allowed to
examine the system columns of the NEW row (such as oid), because those
won't have been set yet.
In an AFTER trigger, the WHEN condition is evaluated just after the
row update occurs, and it determines whether an event is queued to
fire the trigger at the end of statement. So when an AFTER trigger's
WHEN condition does not return true, it is not necessary to queue an
event nor to re-fetch the row at end of statement. This can result in
significant speedups in statements that modify many rows, if the
trigger only needs to be fired for a few of the rows.
Related:
Fire trigger on update of columnA or ColumnB or ColumnC
To also address the question title
Is it possible to dynamically loop through a table's columns?
Yes. Examples:
Handle result when dynamic SQL is in a loop
Removing all columns with given name
Iteration over RECORD variable inside trigger
Use pl/perl or pl/python. They are much better suited for such tasks. much better.
You can also install hstore-new, and use it's row->hstore semantics, but that's definitely not a good idea when using normal datatypes.