How to get value from table using Firebird trigger - firebird

I want to get a value from the table and compare it with inserted value in the Firebird trigger.
Here is my code.
SET TERM ^;
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
select sys_code from system_table;
BEGIN
/* enter trigger code here */
if(sys_code == NEW.SYSTEM_CODE) then
insert into logs(log_detail)values('code matched');
end
END^
SET TERM;^

Alternatively, you can use a singular select expression instead.
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
BEGIN
sys_code = (select sys_code from system_table);
if(sys_code == NEW.SYSTEM_CODE) then
begin
insert into logs(log_detail)values('code matched');
end
END
If your select returns...
one single row or more, then it is the same as Mark's answer (error when multiple rows).
not a single row, the expression would return NULL while Mark's statement would do nothing (no change of variable value)
You may also think SQL SINGULAR existence predicate and about how it is different from EXISTS one.
Firebird docs - chapter 4.2.3. Existential Predicates
Interbase docs, stemming from old pre-Firebird documentation.
You also have to make your mind clearly what should happen if the transaction was rolled back (because of any database or network error, or because an application commanded to ROLLBACK changes): should your LOG still contain a record about the data modification that was not persisted or should the LOG record vanish with the un-inserted data row it describes.
If former is the case you have to insert log records in autonomous transaction (chapter 7.6.16).

You need to use the INTO clause:
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
BEGIN
select sys_code from system_table into sys_code;
if(sys_code == NEW.SYSTEM_CODE) then
begin
insert into logs(log_detail)values('code matched');
end
END

Related

How to make a PostgreSQL constraint only apply to a new value

I'm new to PostgreSQL and really loving how constraints work with row level security, but I'm confused how to make them do what I want them to.
I have a column and I want add a constraint that creates a minimum length for a text column, this check works for that:
(length((column_name):: text) > 6)
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
I want to make it so they can't change that value TO that, but can still update a row where that is already happening, so they can change it as needed according to my new policy.
Is this possible?
BUT, it also then prevents users updating any rows where column_name is already under 6 characters.
Well, no. When you try to add that CHECK constraint, all existing rows are checked, and an exception is raised if any violation is found.
You would have to make it NOT VALID. Then yes.
You really need a trigger on INSERT or UPDATE that checks new values. Not as cheap and not as bullet-rpoof, but still pretty solid. Like:
CREATE OR REPLACE FUNCTION trg_col_min_len6()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'UPDATE'
AND OLD.column_name IS NOT DISTINCT FROM NEW.column_name THEN
-- do nothing
ELSE
RAISE EXCEPTION 'New value for column "note" must have at least 6 characters.';
END IF;
RETURN NEW;
END
$func$;
-- trigger
CREATE TRIGGER tbl1_column_name_min_len6
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW
WHEN (length(NEW.column_name) < 7)
EXECUTE FUNCTION trg_col_min_len6();
db<>fiddle here
It should be most efficient to check in a WHEN condition to the trigger directly. Then the trigger function is only ever called for short values and can be super simple.
See:
Trigger with multiple WHEN conditions
Fire trigger on update of columnA or ColumnB or ColumnC
You can create separate triggers for Insert and Update letting each completely define when it should fired. If completely different logic is required for the DML action this technique allows writing dedicated trigger functions. In this case that is not required the trigger function reduces to raise exception .... See Demo
-- Single trigger function for both Insert and Delete
create or replace function trg_col_min_len6()
returns trigger
language plpgsql
as $$
begin
raise exception 'Cannot % val = ''%''. Must have at least 6 characters.'
, tg_op, new.val;
return null;
end;
$$;
-- trigger before insert
create trigger tbl_val_min_len6_bir
before insert
on tbl
for each row
when (length(new.val) < 6)
execute function trg_col_min_len6();
-- trugger before update
create trigger tbl_val_min_len6_bur
before update
on tbl
for each row
when ( length(new.val) < 6
and new.val is distinct from old.val
)
execute function trg_col_min_len6();

How to prevent bulk row deletion operations?

I can prevent DELETE completely like this:
CREATE TRIGGER prevent_multiple_row_del
BEFORE DELETE ON ALL
BEGIN
RAISE EXCEPTION 'Cant delete more than 1 row at a time';
END;
But how do I check if the delete operation will lead to deletion of multiple rows? Deletion is not a problem, as long as it's limited to a constant number (1 or 5 or 10, as long as it's not unlimited).
Alternatively, how do I allow deletions but prevent deletions of full tables?
A before statement trigger is too early to know the affected rows.
As to full table deletes, use an after statement trigger. All you'd have to do is select from the table and see whether there is some record left in it.
As to deletes of up to n records, this too would have to be determined after statement. You tagged your question PostgreSQL, but as a_horse_with_no_name pointed out your code is Oracle. In PostgreSQL you'd want to check pg_affected_rows() and in Oracle SQL%ROWCOUNT. I don't know whether PostgreSQL allows to check pg_affected_rows() in an after statement trigger. For Oracle, checking SQL%ROWCOUNT in an after statement trigger doesn't work. It's too early for this variable to check.
So at least for Oracle the trick is to have some custom counter to set to zero before statement, increase after each row and check after statement. I don't know precisely how to do that in PostgreSQL, but there certainly will be a way. In Oracle you'd use a compound trigger, i.e. a super trigger housing the individual triggers.
CREATE OR REPLACE TRIGGER prevent_multiple_row_del
FOR DELETE ON mytable COMPOUND TRIGGER
v_count INTEGER := 0;
AFTER EACH ROW IS
BEGIN
v_count := v_count + 1;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
IF v_count > 1 THEN
raise_application_error(-20000, 'Can''t delete more than 1 row at a time');
END IF;
END AFTER STATEMENT;
END prevent_multiple_row_del;

Inserted, Deleted tables in postgreSQL, like SQL Server?

I want to create a trigger after a inserted event, but I need the data that I inserted in order to register into a new table for my trigger in PostgreSQL
In SQL Server I capture these values from the Inserted or deleted pseudo tables but do these tables also exists in PostgreSQL? Or what can I do?
This is my trigger code
CREATE TRIGGER tri_compago
AFTER INSERT
ON matricula
FOR EACH ROW
EXECUTE PROCEDURE fn_insCompPago();
CREATE OR REPLACE FUNCTION fn_insCompPago()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
insert into compromisopago(codigotasa,descripcion,precio,fechavencimiento,codigomatricula)
select codigotasa,descripcion,precio,fechavencimiento,i.codigo
from programacionpago pp join inserted i on isnull(i.codigoconvenio,0) = isnull (pp.codigoconvenio,0)
and pp.codigopresentacion = i.codigopresentacion
where pp.vigencia = 1 and i.vigencia = 1;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION fn_insCompPago()
OWNER TO postgres;
I have no idea how triggers work in SQL Server but in PostgreSQL, you use the OLD and NEW special variables:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is NULL in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is NULL in statement-level triggers and for INSERT operations.
So you probably want to look at NEW.codigo, NEW.codigoconvenio, NEW.codigopresentacion, and NEW.vigencia in your case. You'd probably replace the i.vigencia = 1 part of the WHERE clause with a simple IF i.vigencia = 1 conditional as well.
A trigger defined as for each row is fired - well - for each row in Postgres. SQL Server does not support row level triggers, only statement level triggers.
Inside a row level trigger you always deal with exactly one row with the old and new values accessible (very roughly comparable to "inserted" and "deleted" virtual tables in SQL Server)
You can specify under which name you want to reference those records, the default is new and old (as mu is too short has already explained).
So as the values you are interested in are available as "scalar" values, you don't need any join to do your insert:
insert into compromisopago
(codigotasa,descripcion,precio,fechavencimiento,codigomatricula)
select codigotasa,
descripcion,
precio,
fechavencimiento,
new.codigo
from programacionpago pp
where pp.vigencia = 1
and i.vigencia = 1;
and pp.codigoconvenio = new.codigoconvenio
and pp.codigopresentacion = new.codigopresentacion;

PostgreSQL cannot begin/end transactions in PL/pgSQL

I am seeking clarification of how to ensure an atomic transaction in a plpgsql function, and where the isolation level is set for this particular change to the database.
In the plpgsql function shown below, I want to make sure that BOTH the deletion AND the insertion succeed. I am getting an error when I try to wrap them in a single transaction:
ERROR: cannot begin/end transactions in PL/pgSQL
What happens during execution of the function below if another user has added a default behavior for circumstances ('RAIN', 'NIGHT', '45MPH') after this function has deleted the custom row but before it has had a chance to insert the custom row? Is there an implicit transaction wrapping the insert and delete so that both are rolled back if another user has changed either of the rows referenced by this function? Can I set the isolation level for this function?
create function foo(v_weather varchar(10), v_timeofday varchar(10), v_speed varchar(10),
v_behavior varchar(10))
returns setof CUSTOMBEHAVIOR
as $body$
begin
-- run-time error if either of these lines is un-commented
-- start transaction ISOLATION LEVEL READ COMMITTED;
-- or, alternatively, set transaction ISOLATION LEVEL READ COMMITTED;
delete from CUSTOMBEHAVIOR
where weather = 'RAIN' and timeofday = 'NIGHT' and speed= '45MPH' ;
-- if there is no default behavior insert a custom behavior
if not exists
(select id from DEFAULTBEHAVIOR where a = 'RAIN' and b = 'NIGHT' and c= '45MPH') then
insert into CUSTOMBEHAVIOR
(weather, timeofday, speed, behavior)
values
(v_weather, v_timeofday, v_speed, v_behavior);
end if;
return QUERY
select * from CUSTOMBEHAVIOR where ... ;
-- commit;
end
$body$ LANGUAGE plpgsql;
A plpgsql function automatically runs inside a transaction. It all succeeds or it all fails. The manual:
Functions and trigger procedures are always executed within a
transaction established by an outer query — they cannot start or
commit that transaction, since there would be no context for them to
execute in. However, a block containing an EXCEPTION clause
effectively forms a subtransaction that can be rolled back without
affecting the outer transaction. For more about that see Section 42.6.6.
So, if you need to, you can catch an exception that theoretically might occur (but is very unlikely).
Details on trapping errors in the manual.
Your function reviewed and simplified:
CREATE FUNCTION foo(v_weather text
, v_timeofday text
, v_speed text
, v_behavior text)
RETURNS SETOF custombehavior
LANGUAGE plpgsql AS
$func$
BEGIN
DELETE FROM custombehavior
WHERE weather = 'RAIN'
AND timeofday = 'NIGHT'
AND speed = '45MPH';
INSERT INTO custombehavior (weather, timeofday, speed, behavior)
SELECT v_weather, v_timeofday, v_speed, v_behavior
WHERE NOT EXISTS (
SELECT FROM defaultbehavior
WHERE a = 'RAIN'
AND b = 'NIGHT'
AND c = '45MPH'
);
RETURN QUERY
SELECT * FROM custombehavior WHERE ... ;
END
$func$;
If you actually need to begin/end transactions like indicated in the title look to SQL procedures in Postgres 11 or later (CREATE PROCEDURE). See:
In PostgreSQL, what is the difference between a “Stored Procedure” and other types of functions?
Update: after PostgreSQL version 11. you can control transaction inside Store Procedure.
=====
Before Version 10:
START TRANSACTION;
select foo() ;
COMMIT;
"Unfortunately Postgres has no stored procedures, so you always need to manage the transaction in the calling code" – a_horse_with_no_name
Transaction in an exception block - how?

How can I insert the return of DELETE into INSERT in postgresql?

I am trying to delete a row from one table and insert it with some additional data into another. I know this can be done in two separate commands, one to delete and another to insert into the new table. However I am trying to combine them and it is not working, this is my query so far:
insert into b (one,two,num) values delete from a where id = 1 returning one, two, 5;
When running that I get the following error:
ERROR: syntax error at or near "delete"
Can anyone point out how to accomplish this, or is there a better way? or is it not possible?
You cannot do this before PostgreSQL 9.1, which is not yet released. And then the syntax would be
WITH foo AS (DELETE FROM a WHERE id = 1 RETURNING one, two, 5)
INSERT INTO b (one, two, num) SELECT * FROM foo;
Before PostgreSQL 9.1 you can create a volatile function like this (untested):
create function move_from_a_to_b(_id integer, _num integer)
returns void language plpgsql volatile as
$$
declare
_one integer;
_two integer;
begin
delete from a where id = _id returning one, two into strict _one, _two;
insert into b (one,two,num) values (_one, _two, _num);
end;
$$
And then just use select move_from_a_to_b(1, 5). A function has the advantage over two statements that it will always run in single transaction — there's no need to explicitly start and commit transaction in client code.
For all version of PostgreSQL, you can create a trigger function for deleting rows from a table and inserting them to another table. But it seems slower than bulk insert that is released in PostgreSQL 9.1. You just need to move the old data into the another table before it gets deleted. This is done with the OLD data type:
CREATE FUNCTION moveDeleted() RETURNS trigger AS $$
BEGIN
INSERT INTO another_table VALUES(OLD.column1, OLD.column2,...);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER moveDeleted
BEFORE DELETE ON table
FOR EACH ROW
EXECUTE PROCEDURE moveDeleted();
As above answer, after PostgreSQL 9.1 you can do this:
WITH tmp AS (DELETE FROM table RETURNING column1, column2, ...)
INSERT INTO another_table (column1, column2, ...) SELECT * FROM tmp;
That syntax you have there isn't valid. 2 statements is the best way to do this. The most intuitive way to do it would be to do the insert first and the delete second.
As "AI W", two statements are certainly the best option for you, but you could also consider writing a trigger for that. Each time something is deleted in your first table, another is filled.