How to update a record on another table based on the values of the record being modified using a trigger - triggers

I have two simple (only for explain my problem) tables
X with columns (among others): IDX,CODE,NUMBER
Y with columns (among others): CODE,NUMBER,id_fromX
I want to (after insert or update table X) update table Y using variables from actual record from X.
To do this I try to use trigger (in table X) like below:
SET TERM ^^ ;
CREATE TRIGGER XYZFOR X ACTIVE AFTER INSERT OR UPDATE POSITION 0 AS
begin
if (new.CODE is distinct old.CODE) then
BEGIN
EXECUTE STATEMENT ('UPDATE Y SET CODE=:old.CODE, id_fromX=:old.IDX WHERE NUMBER=:old.NUMBER');
END
end ^^
but I get error from the server:
Execute statement error at jrd8_prepare :\
335544569 : Dynamic SQL Error
335544436 : SQL error code = -104
335544634 : Token unknown - line 1, column 23
335544382 : .
Statement : UPDATE Y SET CODE=:old.CODE, id_fromX=:old.IDX WHERE NUMBER=:old.NUMBER\
Data source : Internal::
At trigger 'XYZ' line: 15, col: 7
Static update like this below:
CREATE TRIGGER XYZ FOR X ACTIVE AFTER INSERT OR UPDATE POSITION 0 AS
begin
if (new.CODE is distinct from old.CODE) then
BEGIN
EXECUTE STATEMENT ('UPDATE Y SET CODE=1, id_fromX=111 WHERE NUMBER=1');
END
end ^^
SET TERM ; ^^
works perfect.
How to reference to X table fields to update table Y (fields with similar names)?

You're using a colon, but the old/new records don't use it. Also, don't use a execute statement here, since the sql statement is static.
Change it to:
SET TERM ^^ ;
CREATE TRIGGER XYZFOR X ACTIVE AFTER INSERT OR UPDATE POSITION 0 AS
begin
if (new.CODE is distinct old.CODE) then
BEGIN
UPDATE Y
SET CODE = old.CODE, id_fromX = old.IDX
WHERE NUMBER = old.NUMBER;
END
end ^^

You can't write parameters directly into statement with EXECUTE STATEMENT, see documentation for correct syntax. Basically, it should be something like
EXECUTE STATEMENT ('UPDATE Y SET CODE = :CODE, id_fromX = :IDX WHERE NUMBER=:NUMBER')
(CODE := old.CODE, IDX := old.IDX, NUMBER := old.NUMBER);
But you actually don't need the EXECUTE STATEMENT here, use UPDATE statement "directly".

You can't use the trigger context variables (old.<column> and new.<column>) in EXECUTE STATEMENT as they are separate contexts (the statement in EXECUTE STATEMENT can't see them). You either need to use a normal UPDATE statement without resorting to EXECUTE STATEMENT, or you should pass parameters. Like:
EXECUTE STATEMENT
('UPDATE Y SET CODE=:code, id_fromX=:idx WHERE NUMBER=:number')
(code := old.CODE, idx := old.IDX, number := old.NUMBER);

Shouldn't :old.CODE be old.CODE?

Related

How do I avoid syntax error in FOR loops in postgresql?

I'm using PostgreSQL 10.5 and I have the following SQL:
FOR temprow IN
SELECT o.objectid, o.nametag, cor.userseqno, cor.commseqno
FROM "commuserobjectrights" as cor
LEFT JOIN "object" as o ON cor.objectid = o.objectid
WHERE o.nametag LIKE 'commission.video_questions'
LOOP
INSERT INTO u commuserobjectrights (objectid, commseqno, userseqno, access)
VALUES (temprow.objectid, temprow.commseqno, temprow.userseqno, TRUE);
END LOOP;
which throws the following error:
ERROR: syntax error at or near "FOR" Position: 3
I have never used loops before but according the documentation, postgresql should have support for these types of loops. And yes, I have checked and double checked that all tables and column names are spelled correctly.
You can't use FOR loops outside of procedural code. But, in general Postgres (and SQL) is optimized to already do set based operations. So, you may phrase this as an INSERT INTO ... SELECT:
INSERT INTO commuserobjectrights (objectid, commseqno, userseqno, access)
SELECT o.objectid, o.nametag, cor.userseqno, TRUE
FROM "commuserobjectrights" as cor
LEFT JOIN "object" as o ON cor.objectid = o.objectid
WHERE o.nametag LIKE 'commission.video_questions';
FOR is procedural code, so you need to use DO or use it in stored code.
DO $$
DECLARE
temprow record ;
BEGIN
FOR temprow IN
SELECT o.objectid, o.nametag, cor.userseqno, cor.commseqno
FROM "commuserobjectrights" as cor
LEFT JOIN "object" as o ON cor.objectid = o.objectid
WHERE o.nametag LIKE 'commission.video_questions'
LOOP
INSERT INTO commuserobjectrights (objectid, commseqno, userseqno, access)
VALUES (temprow.objectid, temprow.commseqno, temprow.userseqno, TRUE);
END LOOP;
END;
$$;
This is not the most efficient way to do this task but for other tasks where you can't easily write SQL DO may be useful.

Firebird dynamic Var New and Old

I need validate dynamic Fields from a Table. For example:
CREATE TRIGGER BU_TPROYECTOS FOR TPROYECTOS
BEFORE UPDATE AS
DECLARE VARIABLE vCAMPO VARCHAR(64);
BEGIN
/*In then table "TCAMPOS" are the fields to validate*/
for Select CAMPO from TCAMPOS where TABLA = TPROYECTOS and ACTUALIZA = 'V' into :vCAMPO do
Begin
if (New.:vCAMPO <> Old.:vCampo) then
/*How i get dynamic New.Field1, New.Field2 on query return*/
End;
END ;
The question is : How can I put "The name of the field that the query returns me " in the above code .
Ie if the query returns me the field1 and field5 , I would put the trigger
if ( New.Field1 < > Old.Field1 ) or ( New.Field5 < > Old.Field5 ) then
There is no such feature in Firebird. You will need to create (and preferably) generate triggers that will reference all fields hard coded. If the underlying table changes or the requirements for validation, you will need to recreate the trigger to take the added or removed fields into account.

Getting a value from stored procedure within a stored procedure

I have a stored procedure which returns an XML file. At the moment some calculations are done in XSL but I would like to do these within the database using another stored procedure. (adding the result of that calculation to the XML)
ALTER PROCEDURE [dbo].[app_Get_Phone_And_Tariffs]
-- Add the parameters for the stored procedure here
#phone nvarchar(150)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT
PB.UID as '#phoneid',
PB.Short_Title as '#title',
PB.Description as '#desc',
PB.Camera as '#camera',
PB.Storage as '#storage',
PB.Screen_Size as '#screensize',
PB.OS as '#os',
PB.Processor as '#chip',
PB.Image1 as '#image',
PB.Trade_Price as '#tradeprice',
(SELECT
TB.UID as '#tariffid',
TB.Tariff_Name as '#name',
TB.Carrier as '#network',
TB.Inclusive_Minutes as '#mins',
TB.Inclusive_Texts as '#texts',
TB.Inclusive_Data as '#data',
TB.Monthly_Cost as '#monthly',
TB.Commission as '#comm',
(TB.Commission - PB.Trade_Price) as '#upfront'
FROM dbo.Tariff_Base TB
WHERE TB.Active = 1 AND TB.Type = 1
FOR XML PATH('tariff'), TYPE
),
(SELECT
OP.GP_Margin as '#gpmargin'
FROM dbo.Options OP
FOR XML PATH('options'), TYPE
)
FROM dbo.Phone_Base PB
WHERE PB.Friendly_URL_Name = #phone AND PB.Active = 1
FOR XML PATH('detail'), TYPE
END
What I want to do is:
In the inner select (TB) is to call another SP lets call it "calculate" passing 2 variables (TB.Commission and PB.Trade_Price) for the sum
Calculate will return a value i.e. #hp to the stored procedure which can be added/used in the XML List.
Can this be done in SQL Server 2014/T-SQL?
No. But you could do it with a function. See the MSDN documentation, especially example A.
Something like this (untested):
CREATE FUNCTION dbo.Calculate (#Comission float, #TradePrice float)
RETURNS float
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #SumVal float;
SET #SumVal = #Commission + # TradeValue;
RETURN(#SumVal);
END;
GO
--In your sub-query
SELECT values, dbo.Calculate(TB.Commission, PB.Trade_Price) AS A_Sum
FROM ...;

Initialize generator with existing value

I am trying to set a generator with a value that is in some table, I have already seen this question How to set initial generator value? and did what they suggested but I don't know where am I going wrong here.
set term #
execute block
as
declare i int = 0;
begin
i = (select max(some_col) from Table);
gen_id(some_gen,-(gen_id(some_gen,0))); ---set some_gen to 0
gen_id(some_gen,:i); --- set to i
end #
set term ;#
The problem with your code is that you can't execute gen_id in isolation; the parser expects gen_id (or more precisely: a function call) only in a place where you can have a value (eg in a statement or an assignment). You need to assign its return value to a parameter, for example:
set term #;
execute block
as
declare i int = 0;
declare temp int = 0;
begin
i = (select max(id) from items);
temp = gen_id(GEN_ITEMS_ID, -(gen_id(GEN_ITEMS_ID, 0))); ---set some_gen to 0
temp = gen_id(GEN_ITEMS_ID, :i); --- set to i
end #
set term ;#
Please be aware that changing sequences like this is 'risky': if there are any interleaving actions using this same sequence, you might not actually get the result you expected (the sequence might end up at a different value than i and you might get duplicate key errors when another transaction uses the sequence after you subtract the current value (set to 0) and before you add i.
As also noted in the comments, you can also replace your code with:
set term #;
execute block
as
declare i int = 0;
declare temp int = 0;
begin
i = (select max(id) from items);
temp = gen_id(GEN_ITEMS_ID, :i - gen_id(GEN_ITEMS_ID, 0));
end #
set term ;#
Doing it in one statement will reduce the risk of interleaving operations (although it will not remove it entirely).
If you want to use "execute block", you may use something like :
execute block
as
declare i int = 0;
begin
i = (select max(some_col) from some_table);
execute statement ('set generator MY_GENERATOR to ' || :i);
end

Create trigger sytax error in sybase

Below is the code i am using to create a trigger(before insert):
ALTER TRIGGER "delete_entry_before_inserting" BEFORE INSERT
ORDER 1 ON "XYZ"."ABC"
REFERENCING NEW AS "inserted"
FOR EACH ROW /* WHEN( search_condition ) */
BEGIN
IF EXISTS (select hostname from ABC WHERE hostname = inserted.hostname) THEN
UPDATE ABC
SET days_count = (days_count + 1)
WHERE hostname = inserted.hostname
ROLLBACK TRANSACTION
END IF
END
But the above thing is giving me error as: syntax error near rollback transaction on line 11
what am i doing wrong here?
You are missing a BEGIN TRANSACTION somewhere in your code.
Check this documentation.