Firebird deactivate triggers - delphi

I have clients-->|cascade rule|-->orders_table-->|cascade rule|-->order_details
in my order_details I have after delete trigger that increment the quantity in my product table
CREATE OR ALTER TRIGGER TABLEAU_DETAIL_VENTES_AD0 FOR TABLEAU_DETAIL_VENTES
ACTIVE AFTER DELETE POSITION 0
AS
declare variable qte numeric_15_2;
begin
select qte_article from tableau_articles where id_article = old.id_article
into :qte;
qte = :qte + old.qte;
update tableau_articles
set qte_article = :qte
where id_article = old.id_article;
end
If I delete a client than all orders depending on it will be deleted
and the orders_detail so on.
The problem is that order_details after delete trigger will be fired and incrementing the product quantity I don't want that to happen.
My question: is there any way to know if the trigger has been fired by cascade rule or sql delete statement that come from the application?
I want to achieve something like:
If trigger triggered by the cascade rule then disable_all_triggers. Thanks in advance for your help.

You can try to wrap your delete code in stored procedure with execute statement for in/activate the trigers
CREATE PROCEDURE DeleteClient(
ID INTEGER)
AS
begin
execute statement 'alter trigger TABLEAU_DETAIL_VENTES_AD0 inactive;';
/*
Your Delete statement here
*/
execute statement 'alter trigger TABLEAU_DETAIL_VENTES_AD0 active;';
END^

I end up using context variables in my clients table i add after delete trigger and set a flag using rdb$set_context
SET TERM ^ ;
CREATE OR ALTER TRIGGER TABLEAU_CLIENTS_AD0 FOR TABLEAU_CLIENTS
ACTIVE AFTER DELETE POSITION 0
AS
declare variable id integer;
begin
execute statement 'select rdb$set_context(''USER_SESSION'', ''myvar'', 100) from rdb$database' into :id;
end
^
SET TERM ; ^
in the detail orders i check my flag with rdb$get_context and skip the trigger if the flag exist with the value associated
select rdb$get_context('USER_SESSION', 'myvar') from rdb$database into :i;
if (i = 100) then exit;

You can't determine that, but you can determine if your foreign key is still valid. Since Firebird cascaded deletes are sequential (rows that are referenced in a foreign keys are deleted first), you can check if your old.id_article is still valid before updating the record.

I'm not sure you would achieve what you want like that. What if you just delete an order and its items. Wouldn't you want to increment quantities in that case?
Anyway... I wouldn't deactivate triggers from within triggers. That is bad design.
Use some sort of variable... update a flag in a support table. From within the client delete trigger you can set this variable. Then in the order_items delete trigger you can check it to see if you need to update quantities.
Another better option is to analyze the situation better and determine why and when you actually want to update quantities. If you are deleting an old order which has already been fulfilled and delivered, you probably wouldn't want to. If you are canceling a new order, you probably would. So maybe updating the quantities depends actually more on the state of the order (or some other variable) then simply on the fact that you are deleting an order_items row.
Ok, so you say orders cannot be deleted, except when deleting the client. Then maybe you should flag the client or its order with a flag that states the client is being deleted. In the order_items delete trigger you update article quantities only if the client is not being deleted.

Related

Stored procedure to insert new records in the child tables

I'm trying to create a stored procedure to insert new records in a number of child tables (using Greenplum) I have a master table and a set of child tables. I would like to insert new records that I have in the master table into the child tables (I have around 20 child tables). My assumption is that I should create a function, then a trigger.
Note that I only want to insert some fields into the child tables.
I made a few attempts, but here's the last one: (sorry in advance if it looks very bad. I never created any trigger functions)
FUNCTION:
CREATE OR REPLACE FUNCTION
schema1.newcustomerdata() RETURNS trigger AS $new_customer_data$
BEGIN
INSERT INTO schema1.customeridentifiers
(customer_id,
date_time)
SELECT NEW.customer_id,
date_time
FROM schema1.customersmaster
;
RETURN NEW;
END;
$new_customer_data$ LANGUAGE plpgsql;
TRIGGER:
CREATE TRIGGER newcustomerdata
AFTER INSERT ON schema1.customersmaster
FOR EACH ROW EXECUTE PROCEDURE newcustomerdata();
The function and trigger runs. However, I can't insert data in the master table anymore.
I get this error message:
function cannot execute on segment because it issues a non-select statement
So my questions are:
What would be the best solutions to update the child tables?
What's wrong with my function or trigger?
What are your recommendations?
Apparently "Triggers are not supported since they typically rely on the use of VOLATILE functions" Use RULES instead of triggers
CREATE RULE newcustomerdata AS ON INSERT TO schema1.customersmaster
DO also INSERT INTO schema1.customeridentifiers VALUES (NEW.customer_id, NEW.date_time);

Instead of trigger not firing

CREATE OR REPLACE PROCEDURE insert_ol
(
p_ord_id order_line.order_id%type,
p_pid order_line.product_id%type,
p_qty order_line.quantity%type,
p_price order_line.price%type,
p_pname order_line.prod_name%type,
p_alias order_line.prod_alias%type)
IS
BEGIN
INSERT INTO order_line values (p_ord_id, p_pid, p_qty, p_price, p_pname,
p_alias);
END;
And I have a INSTEAD OF TRIGGER like this:
CREATE OR REPLACE TRIGGER insert_ol
INSTEAD OF INSERT ON ol_ins
FOR EACH ROW
BEGIN
insert_ol
(:new.order_id,:new.prod_code,
:new.qty,prod_cost,:new.prod_name,:new.palias);
end;
The view on which the trigger is based is:
CREATE OR REPLACE FORCE VIEW "OL_INS"
AS
SELECT ot.order_id, prd.prod_code, ot.qty, prd.prod_cost, prd.prod_name,
ot.palias
FROM ol_temp ot
JOIN product prd
ON ot.palias=prd.prod_alias;
When I insert data into the "OL_TEMP" table, the partial data gets joined with the "PRODUCT" table and gets into the view. But the trigger does not fire to call the procedure and insert data into the "ORDER_LINE" table.
What is the mistake I am doing and how to I correct it?
You seem to have things backwards, based on
When I insert data into the "OL_TEMP" table...
The instead of trigger fires when you insert into the OL_INS view, it will never fire if you manipulate the underlying table(s) directly. So you should be doing something like:
insert into ol_ins (order_id, prod_code, qty, prod_cost, prod_name, palias)
values ( ... )
The trigger will then fire, call the procedure, and insert data into the order_line table. Except it will error because your trigger refers to prod_cust (which isn't a known identifier) instead of :new.prod_cust.
From what you have described that isn't what you want at all though, and a trigger on the view doesn't make sense. You seem to want a simple insert, based on your temporary table and the permanent product table - it isn't even clear that you need the view at all:
INSERT INTO order_line (order_id, product_id, quantity, price, prod_name, prod_alias)
SELECT ot.order_id, prd.prod_code, ot.qty, prd.prod_cost, prd.prod_name, ot.palias
FROM ol_temp ot
JOIN product prd
ON ot.palias = prd.prod_alias;
If you really do need the view for something else and want to use it here you still could:
INSERT INTO order_line (order_id, product_id, quantity, price, prod_name, prod_alias)
SELECT order_id, prod_code, qty, prod_cost, prod_name, palias
FROM ol_ins;
but I wouldn't create a view just for that. And you could wrap either of those inserts in a procedure if you really wanted to, but you would still have to call the procedure explicitly - not from a trigger, unless you used an after-statement trigger on the temporary table.

Observing changes to database value via KVO

I'm building a messaging application. I update the badge count in the database via a sqlite trigger whenever any operation like insert/delete/read message happens.
Currently, though the value update in the DB happens asynchronously, I have no way to get notified about when the value changes in my application and hence am polling periodically.
Is there some way to setup an observer on a database value/publish some notification when a given value changes?
I know that I can do this easily by first updating the badge count in an in-memory property and then persisting the changes to the DB; but I am not very inclined to do this, since there are too many entry points for this value to change, and I don't want to add a SET property everywhere.
One possible option would be to define a trigger that is only called when this specific value in the database is updated. The trigger should then make a call to a user defined function you create in your app. You use the sqlite3_create_function function to add your own function to SQLite. Your trigger would like something like:
CREATE TRIGGER some_trigger_name
AFTER UPDATE OF some_column ON some_table
FOR EACH ROW BEGIN
SELECT my_custom_fuction();
END;
If needed, you can pass 1 or more arguments to your function.
Though that this might not be an option for you, Core Data does this well.

Informix 4GL and triggers

I want a simple SQL (trigger) for duplicate checking.
My table name is test1 with 2 columns, code and sname. Before inserting a new record, check if the record already exists: if it does, generate an error and do not insert; if it does not, let the insert proceed.
How do I do that?
The simplest, most reliable way to ensure that there is no duplicate data in the table is not using triggers at all, but using UNIQUE or PRIMARY KEY constraints:
CREATE TABLE test1
(
code INTEGER NOT NULL PRIMARY KEY,
sname VARCHAR(32) NOT NULL UNIQUE
);
The four constraints (two NOT NULL, one PRIMARY KEY, one UNIQUE) automatically ensure that no duplicate records are inserted into the table.
If you choose to add a trigger, it will be duplicating the work that is done by these constraints.
As to how to do it, you will need to create a stored procedure which is invoked from the trigger statement. It will be given the new code and new name, and will do a SELECT to see whether any matching record occurs, and will raise an exception if it does and will not raise an exception if not.
CREATE PROCEDURE trig_insert_test1(ncode INTEGER, nname VARCHAR(32))
DEFINE ocode INTEGER;
FOREACH SELECT code INTO ocode
FROM test1
WHERE code = ncode OR sname = nname
RAISE EXCEPTION -271, -100, "Value to be inserted already exists in table test1";
END FOREACH;
END PROCEDURE
Then you use:
CREATE TRIGGER ins_test1 INSERT ON test1
REFERENCING NEW AS NEW
FOR EACH ROW (EXECUTE PROCEDURE ins_trig_test1(new.code, new.sname))
In Informix 4GL, you can either create strings containing these statements, and then PREPARE and EXECUTE (and FREE) them, or you can use SQL blocks:
SQL
CREATE TRIGGER ins_test1 INSERT ON test1
REFERENCING NEW AS NEW
FOR EACH ROW (EXECUTE PROCEDURE ins_trig_test1(new.code, new.sname))
END SQL
But, as I said at the outset, using triggers for this is not the best way to go; it is redundant given the table definition.
I've not run any of the SQL or SPL past the server; you'll need to check that the semi-colons are in the right places in the SPL, as SPL is fussy about that.
You can find the syntax for the SQL and SPL statements in the Informix 11.70 Information Centre.

to track the modified rows and manually update from the TClientDataSet's Delta

Is there any way to manually track the changes done to a clientdataset's delta and update the changes manually on to then db. i have dynamically created a clientdataset and with out a provider i am able to load it with a tquery, now user will do some insert update and delete operations on the data available in the cds, and at final stage these data(modified) should be post in to database by using a tquery(not apply updates)..
After populating your data set from the TQuery call MergeChangeLog so that the records do not stand out as newly inserted, and be sure that LogChanges is set.
Then when at the final stage, before updating the query with the dataset, set StatusFilter so that only the records that you want to take action on should be showing. For instance;
ClientDataSet1.StatusFilter := [usDeleted];
You can also use UpdateStatus on a record to see if it has been modified etc..
But be careful that, is seems that there will be multiple versions of a record, and it is a bit difficult to understand how the "change log" keeps track. And there also can be multiple actions on a record, like modifying it a few times and then deleting it.
Change:= TPacketDataSet.create;
Change.Data:= YourClientDataSet.Delta;
while not Change.Eof do
begin
Change.InitAltRecBuffers(False);
if Change.UpdateStatus = usUnmodified then
Change.InitAltRecBuffers(True);
case Change.UpdateStatus of
usModified: ;//your logic read codes in Provider.pas for further hint
usInserted: ;//your logic read codes in Provider.pas for further hint
usDeleted: ;//your logic read codes in Provider.pas for further hint
end;
Change.Next;
end;
Above should work regardless of number of modified
Cheers
Pham

Resources