Firebird run execute statement inside trigger - triggers

I wrote a procedure STRING_SESTAVLJEN_ENAKOST_TABEL('MERILA_STRANKE') that generates part of code that i want to execute (some long if statement)
IF ((new.LOKACIJA IS DISTINCT FROM old.LOKACIJA )
OR (new.MODIFIED IS DISTINCT FROM old.MODIFIED )
OR (new.KARAKTERISTIKE IS DISTINCT FROM old.KARAKTERISTIKE )
OR (new.LETNIK IS DISTINCT FROM old.LETNIK )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS ))
and I want to call it in trigger, then add some code and run it all together.
The code is:
SET TERM ^ ;
ALTER TRIGGER BI_MERILA_STRANKE ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
declare variable besedilo_primerjave varchar(5000);
BEGIN
begin
if (new.ID_MERILA_STRANKE is null OR new.ID_MERILA_STRANKE = 0) then new.ID_MERILA_STRANKE = gen_id(GEN_ID_MERILA_STRANKE,1);
end
begin
execute procedure STRING_SESTAVLJEN_ENAKOST_TABEL('MERILA_STRANKE')
returning_values :besedilo_primerjave;
execute statement besedilo_primerjave || ' THEN BEGIN INSERT INTO SYNC_INFO(TABLE_NAME,ID_COLUMN_NAME,ID_VALUE,DATETIME)
VALUES (
''MERILA_STRANKE'',
''ID_MERILA_STRANKE'',
NEW.ID_MERILA_STRANKE,
CURRENT_TIMESTAMP
);
END ELSE BEGIN
exception ENAK_RECORD;
END';
end
END^
SET TERM ; ^
Now when I run the update and trigger triggers I get this error:
SQL Message : -104 Invalid token
Engine Code : 335544569 Engine Message : Dynamic SQL Error SQL
error code = -104 Token unknown - line 1, column 1 IF
On the other hand if I write it like this:
SET TERM ^ ;
ALTER TRIGGER BI_MERILA_STRANKE ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
BEGIN
begin
if (new.ID_MERILA_STRANKE is null OR new.ID_MERILA_STRANKE = 0) then new.ID_MERILA_STRANKE = gen_id(GEN_ID_MERILA_STRANKE,1);
end
begin
IF ((new.LOKACIJA IS DISTINCT FROM old.LOKACIJA )
OR (new.MODIFIED IS DISTINCT FROM old.MODIFIED )
OR (new.KARAKTERISTIKE IS DISTINCT FROM old.KARAKTERISTIKE )
OR (new.LETNIK IS DISTINCT FROM old.LETNIK )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS ))
THEN BEGIN
INSERT INTO SYNC_INFO(TABLE_NAME,ID_COLUMN_NAME,ID_VALUE,DATETIME)
VALUES (
'MERILA_STRANKE',
'ID_MERILA_STRANKE',
NEW.ID_MERILA_STRANKE,
CURRENT_TIMESTAMP
);
END ELSE BEGIN
exception ENAK_RECORD;
END
end
END^
SET TERM ; ^
It works as it should. I do not understand why it doesn't run if is more or less the same code.

As I also mentioned in your previous question, execute statement cannot be used to execute snippets of PSQL (procedural SQL) like that, it can only execute normal DSQL (dynamic SQL). And as it doesn't understand PSQL, you get the "token unknown - if" error, because if is not valid in DSQL.
execute statement is equivalent to executing SQL yourself from a query tool or application (it uses the same API), you can't use if there either.
There is a loophole by using an execute block statement, but that still would not allow you to gain access to the NEW (or OLD) trigger context variables unless explicitly passed as parameters, which would negate most of the usefulness of dynamically generated code in this context.
The only real solution is to write the trigger and not do it dynamically, maybe using a code generator (I'm not sure if any exist, otherwise you need to write that yourself).

Related

Can you use IF ELSE statements in PostgreSQL Rule

Can I do something like this, the error i get is?
ERROR: syntax error at or near "IF"
LINE 5: IF EXISTS(SELECT nr_albumu FROM roznosci.suma_ocen)
^
Here is my code:
CREATE OR REPLACE RULE oceny_change_rule AS
ON UPDATE TO dziekanat.oceny
WHERE NEW.ocena > 3.0 AND NEW.ocena <> OLD.ocena
DO
(
IF EXISTS(SELECT nr_albumu FROM roznosci.suma_ocen)
THEN
UPDATE roznosci.suma_ocen SET suma_ocen = suma_ocen + NEW.ocena WHERE suma_ocen.nr_albumu = NEW.nr_albumu
ELSE
INSERT roznosci.suma_ocen VALUES(NEW.nr_albumu,NEW.ocena)
END IF;
);
You can only use SQL commands in a rule.
As SQL has no IF (only PL/pgSQL does) you can't use IF inside the commands for a rule.
You can put your conditional into a PL/pgSQL function and then call that function from your rule.
With the given example, you seem to want what's also known as UPSERT. If you create a unique constraint on suma_ocen (nr_albumu)
you could use an insert on conflict
INSERT suma_ocen (nr_albumu, ocena)
VALUES (NEW.nr_albumu, NEW.ocena)
ON conflict (nr_albumu)
DO UPDATE
SET suma_ocen = suma_ocen.suma_ocen + excluded.ocena;

UPDATE/SET/OUTPUT/FROM with output into variable

Consider the following stored procedure
CREATE PROCEDURE AssignCodeToCustomer (#customerId int)
AS
BEGIN
DECLARE #code NVARCHAR(255)
BEGIN TRY
BEGIN TRANSACTION
SELECT #code = (
UPDATE
Codes
SET
CustomerId = #customerId
OUTPUT
INSERTED.Code
FROM (
SELECT TOP 1
Code
FROM
Codes
) AS c
WHERE
c.Code = Codes.Code
-- Other stuff
COMMIT TRANSACTION
END TRY
BEGIN CATCH
BEGIN
ROLLBACK TRANSACTION
EXEC spLogSQLError
END
END CATCH
END
GO
I get an error 'Incorrect syntax near the keyword UPDATE' on line 10 (which holds the keyword UPDATE). I could also first select a code and then assign it, but with concurrency in mind I want only one query. The query works if I don't try to set the output value into the variable. How can I fix this error or should I use another approach?

Trying to run dynamic sql using a UDF in DB2

I am very new to DB2 even though have experience in Oracle. I am not able to resolve this issue.I have a requirement where I need to find missing child records in the parent table .The parent table , child table and the join_key are all passed as input parameter.
I have tried this in a procedure was able to achieve this, but the admin wants it in a function so that they can just use it in a select statment and get the result in a table format. Since the parent table , child table and the join_key are comming as input parement, I am not able to run them as dynamic sql.
create or replace function missing_child_rec(PARENT_TBL VARCHAR(255),JOIN_KEY VARCHAR(255),CHILD_TBL VARCHAR(255))
RETURNS TABLE(Key VARCHAR(255))
LANGUAGE SQL
BEGIN
DECLARE V_SQL VARCHAR(500);
DECLARE C_SQL CURSOR WITH RETURN FOR S_SQL;
SET V_PARENT_TAB = PARENT_TBL;
SET V_KEY = JOIN_KEY;
SET V_CHILD_TAB = CHILD_TBL;
SET V_SQL = 'SELECT DISTINCT '|| JOIN_KEY || ' FROM ' || V_CHILD_TAB || ' A WHERE NOT EXISTS
(SELECT ' ||V_KEY || ' FROM ' || V_PARENT_TAB || ' B WHERE A.'||JOIN_KEY || '= B.'||JOIN_KEY ||' )' ;
PREPARE S_SQL FROM V_SQL;
OPEN C_SQL;
CLOSE C_SQL;
RETURN
END
When I try to compile it , it says prepare is invalid , I have tried even execute immediate but that also gave error.Can you please help me with how to use dynamic sql in UDF or an alternative logic for this problem
There is more than one way to solve this, here's one way.
If you already have a working stored-procedure that returns the correct result-set then you can call that stored-procedure from a pipelined table function. The idea is that a pipelined table function can consume the result-set and pipe it to the caller.
This will work on Db2-LUW v10.1 or higher, as long as the database is not partitioned over multiple nodes.
It may work on Db2-for-i v7.1 or higher.
It will not work with Db2 for Z/os at current versions.
Suppose your stored procedure is sp_missing_child_rec and it takes the same input parameters as the function you show in your question, and suppose the data type of the join column is varchar(100).
The pipelined wrapper table function would look something like this:
--#SET TERMINATOR #
create or replace function missing_child_rec(PARENT_TBL VARCHAR(255),JOIN_KEY VARCHAR(255),CHILD_TBL VARCHAR(255))
returns table ( join_column_value varchar(100))
begin
declare v_rs result_set_locator varying;
declare v_row varchar(100); -- to match the join_column_datatype, adjust as necessary
declare sqlstate char(5) default '00000';
CALL sp_missing_child_rec( parent_tbl, join_key, child_tbl);
associate result set locator (v_rs) with procedure sp_missing_child_rec ;
allocate v_rscur cursor for result set v_rs;
fetch from v_rscur into v_row;
while ( sqlstate = '00000') do
pipe(v_row);
fetch from v_rscur into v_row;
end while;
return;
end#
select * from table(missing_child_rec( 'parent_table' , 'join_column', 'child_table'))
#

Getting Error "query has no destination for result data"

I trying to select query based on condition using IF ElSE in postgres. Below is my query.
DO
$do$
DECLARE res varchar(50) := 'a';
BEGIN
IF (res = 'a') THEN
SELECT "Name" FROM "TestTable";
ELSE
SELECT "ID" FROM "TestTable";
END IF;
END
$do$
but I am getting following error
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function inline_code_block line 5 at SQL statement
What I am doing wrong here??
DO purpose is to execute anonymous code block and it doesn't return anything (it returns void, to be specific).
You can execute your SELECT statement afterwards (outside of DO block), or perform an INSERT to temporary table which you need to create beforehand (and this can be done within the block).

What is wrong in my dynamic query postgres statements inside function?

I have a PL/pgSQL function that takes table name as dynamic parameter. As I am updating an existing query to take table name as dynamic parameter, this is what I have as my function:
DECLARE rec RECORD;
BEGIN
EXECUTE 'insert into stat_300_8_0(ts, target, data)
select distinct timestamp-(timestamp%3600) as wide_row_ts,
target, array[]::real[] as data
from ' || temp_table_name || ' as temp
where class_id=8
and subclass_id=0
and not exists (select ts from stat_300_8_0
where ts=temp.timestamp-(temp.timestamp%3600)
and target=temp.target)';
FOR rec IN EXECUTE 'SELECT DISTINCT timestamp AS ts
FROM ' || temp_table_name ||
' WHERE class_id=8'
LOOP
EXECUTE 'update stat_300_8_0 as disk_table
set data[new_data.start_idx:new_data.end_idx] = array[data_0,data_1]
from (select timestamp-(timestamp%3600) as wide_row_ts,
(timestamp%3600)/300 * 2 + 1 as start_idx,
((timestamp%3600 / 300) + 1) * 2 as end_idx,
target, data_0, data_1
from ' || temp_table_name ||
' where class_id=8 and subclass_id=0
and timestamp=rec.ts) as new_data
where disk_table.ts=new_data.wide_row_ts
and disk_table.target=new_data.target';
END LOOP;
END;
However, when this function is executed I get an error saying
ERROR: missing FROM-clause entry for table "rec"
However, rec is declared in the first line of the above code. I am not able to figure what is wrong with my queries. Any help is appreciated.
Supplemental to Eelke's answer:
Assuming temp_table_name is an argument, you really, really want to run it through quote_ident() because otherwise someone could create a table with a name that could inject sql into your function.
Instead of the change suggested there, you are better off using EXECUTE...USING since that gives you parameterization regarding values (and hence protection against SQL injection). You would change rec.ts to $1 and then add to the end USING ts.rec (outside the quoted execute string). This gives you a parameterized statement inside your execute which is safer. However parameters cannot include table names, so it doesn't spare you from the first point above.