How can I easily detect the trigger depth in sqlite3? - sqlite

I have two SQLite3 tables, A and B. When column A.X is updated, I want to modify B.Y, and when B.Y is updated, I want to modify A.X.
I can use two triggers:
CREATE TRIGGER AtoB AFTER UPDATE OF X ON A BEGIN UPDATE B SET Y = ...
and
CREATE TRIGGER BtoA AFTER UPDATE OF Y ON B BEGIN UPDATE A SET X = ...
but it seems that both triggers are called once, no matter which table I modify, i.e. one always invokes the other.
I only want one of them to execute, since the updates are lossy in the direction of A to B. I don't want the loss in the reverse direction B to A, but if both triggers fire, then it makes it lossy in both directions.
A simple solution would be to implement three UDFs "increment_trigger_depth", "decrement_trigger_depth", and "get_trigger_depth", and then use "WHEN trigger_depth == 1" in the update statements. But, is there an easier way to determine trigger depth in SQLite3?

Use a new table to hold the trigger depth.
CREATE TABLE trigger_depth (depth);
INSERT INTO trigger_depth VALUES (0);
Then for increment_trigger_depth use
UPDATE trigger_depth SET depth = depth + 1;
etc.
Use
... WHEN (SELECT depth FROM trigger_depth) <= 0 BEGIN ...
to guard your trigger actions

Related

SQLite: How to exectue statements based on other conditions?

A video game exists with 2 stages: Set-Up, and Gameplay. Players can use Set-Up, to alter a database, which affects Gameplay. However some features of Set-Up must account for other features of Set-Up when interacting with the database, and must therefore alter statements of execution depending on other factors in the database.
For example:
In Set-Up, a player can either choose to enable or disable a setting called "complex commerce", which creates new rows in table "Buildings" with the IDs "BUILDING_WAYSTATION", and "BUILDING_MINT", among many other changes in the database. A player can choose to enable a second setting called "easier commerce" which updates the table "ModiferArguments", increasing the value in the column "VALUE" for the column ID associated through many key constraints to "BUILDING_MARKET" (which will always exist, no matter what) in table "Buildings". However, if "complex commerce" is enabled, then we will want to change update the column "VALUE" differently for market, as well as other buildings introduced by "complex commerce" setting inserts.
Is it possible to perform this kind of logical interaction within an SQLite statement?
Code attempt:
IF
(
EXISTS
(
SELECT
*
FROM
Buildings
WHERE
BuildingType = 'BUILDING_WAYSTATION'
;
)
)
THEN
(
Update
ModifierArguments
SET
Value = Value+1
WHERE
ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY'
;
Update
ModifierArguments
SET
Value = Value+2
WHERE
ModifierID = 'MINT_TRADE_ROUTE_CAPACITY'
;
Update
ModifierArguments
SET
Value = Value+3
WHERE
ModifierID = 'WAYSTATION_TRADE_ROUTE_CAPACITY'
;
)
ELSE
(
Update
ModifierArguments
SET
Value = Value+2
WHERE
ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY'
)
;
I am very sorry about the eye-sore formatting, but I wanted to make sure that the logic of what I am trying to do is as clear as possible.
Code theory:
Using IF and EXISTS we can see in the database whether or not the "complex commerce" set of database updates have occurred, simply by seeing if there is a "Building" table entry row with the Id of "BUILDING_WAYSTATION". This allows to choose which set of UPDATEs to execute for the setting of "easier commerce". If "complex commerce" has not been enabled, then we only need to update a single value to "2". However, if it has been enabled, then that single value must instead be updated to "1" AND we must update other values that would otherwise not exist.
Alternatively:
I have looked at using CASE, but I am not sure if it is capable of fulfilling the same purpose, simply just substituting IF for CASE WHEN. I have also looked at using iif(), but have the same issue in that it does not seem fit for purpose outside of replacing return data from SELECT statements upon display.
You can use CASE expressions to apply your logic:
UPDATE ModifierArguments
SET Value = Value +
CASE
WHEN EXISTS (SELECT * FROM Buildings WHERE BuildingType = 'BUILDING_WAYSTATION')
THEN CASE ModifierID
WHEN 'MARKET_TRADE_ROUTE_CAPACITY' THEN 1
WHEN 'MINT_TRADE_ROUTE_CAPACITY' THEN 2
WHEN 'WAYSTATION_TRADE_ROUTE_CAPACITY' THEN 3
END
ELSE CASE WHEN ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY' THEN 2 ELSE 0 END
END
WHERE ModifierID IN ('MARKET_TRADE_ROUTE_CAPACITY', 'MINT_TRADE_ROUTE_CAPACITY', 'WAYSTATION_TRADE_ROUTE_CAPACITY');

SQLite trigger Issue

I have a trigger on a table which gets triggered on any new insert...Here is the code for the trigger. This trigger has to calculate duration in seconds and insert the record in the table.
CREATE TRIGGER duration_trigger
BEFORE INSERT on StudentData
BEGIN
UPDATE StudentData set duration = (select cast( (julianday(enddatetime) - julianday(startdatetime) ) *24 * 60 *60 as integer) from StudentData);
END
In StudentsData table Duration column is defined as INTEGER,
StartDatetime and EndDatetime are defined as TEXT
Here comes my issue.
Trigger gets triggered, but the value in Duration column is always 7
When I execute the same select query that is in the the trigger in a SQL tool, it gives me correct duration in seconds. Trigger on the database is not producing the same result...what could be the issue?
I am also attaching screenshots of the trigger data in the table and select query results from same table.
Table results after trigger.
Select Query results
Basically you are updating all rows as you are not specifying a WHERE clause for the update. So the very last successful update will apply the value to all rows, hence why they are all 7.
Furthermore before you have inserted a row what is there to update? I don't think this can be done an analogy would be; Before you build the wall paint the wall.
Now you could UPDATE after the insert, but care needs to be taken when using UPDATE i.e. if you want to update anything other than all rows then you need to restrict the update to the required rows. A WHERE clause can do this.
As such if you were to ensure that an inserted column were set to an invalid value (as far as your view of the data e.g. a duration of -1 would only suit Dr. Who (apologies to any other Time Travellers)).
Null could also be used.
However, I prefer using a value that is specifically set. Assuming that the row is inserted with duration being given a value of -1 (e.g. duartion INTEGER DEFAULT -1) Then :-
CREATE TRIGGER duration_trigger001
AFTER INSERT on StudentData
BEGIN
UPDATE StudentData SET duration = ((julianday(enddatetime) - julianday(startdatetime)) * 24 * 60 * 60) WHERE duration = -1;
END;
Would work e.g. :-
Notes
The first two rows were added before the trigger was created.
Row 10 was deleted because I used . instead of : as a separator it did nothing.
I didn't cast to INT for simplicity/laziness.

How to retrieve the data from db in progress 4gl?

How to fetch the value from db in progress 4gl, initially have a input from user to select the record based on the value the record will be displayed. we try that but we can't fetch the exact value.
This is our program:
def var b as int.
def var obj as int.
/*set obj.
prompt-for obj.*/
def var sum as int.
def var a as int no-undo.
def var i as int no-undo.
for each po_mstr break by po_nbr.
select count (*) from po_mstr.
assign
a = 1
b = 583 .
repeat:
if first-of (po_nbr) then
set obj.
prompt-for obj.
if (a le obj and obj lt b) then
disp po_nbr po_vend po_ship po_ord_date with 2 col.
end.
end.
I can retrieve a single data only if we give more than 2 value means it will display the same first value.
Let's break this down. You're using a lot of commands without really realizing what they're for.
FOR EACH opens a block and perform record reads for each loop, in the selected sorting order (if none is selected, then it uses the primary index).
SELECT will perform a database operation, not necessarily doing on the fly, but it might.
REPEAT will also open a block, in which you might or might not be looping records with additional commands. Having said that, here's how I'd write this:
def var a as int no-undo.
def var b as int.
def var sum as int.
/*
def var obj as int.
update obj.
def var i as int no-undo.
you're not using this */
select count (*) into sum from po_mstr.
/* is this what you wanted? To have the count in sum? I don't see a reason, but suit yourself */
assign a = 1 b = 583 . /* or use UPDATE a b. if you would like to make that variable */
/* Since a and b are not changing, I moved it to outside the loop */
for each po_mstr where po_nbr >= a and po_nbr < b:
disp po_nbr po_vend po_ship po_ord_date with 2 col.
end.
I took some liberties. I removed obj because as far as I could assess, you were trying to copy po_nbr values to it, then use it to see if it was in the range you wanted to see. Since I believe po_nbr stands for po number, and that is probably unique, I'd also guess every iteration will have a different value for it. So no need to use if first-of. It also eliminates the need to copy it to a variable. Just compare it directly to the range of po's you want to see. Finally, the display should be fine.
I'm going to go ahead and assume your team hasn't been trained. This would be very important going forward, because QAD (any ERP software, in fact) is going to inflate really fast and badly written code, even when harmless, may impact performance and usability, which could be a problem to operation as a whole. Stack Overflow may be helpful for punctual questions, but the kind of problems you will run into if you aren't prepared will likely not be possible to solve here.
Hope this helps.

How to create a PL/SQL package to discard multiple level of cascading views

I am working on a CR where I need to create a PL/SQL package and I am bit confused about the approach.
Background : There is a View named ‘D’ which is at end of the chain of interdependent views in sequence.
We can put it as :
A – Fact table (Populated using Informatica, source MS-Dynamics)
B – View 1 based on fact table
C – View 2 based on View1
D – View 3 based on view2
Each view has multiple joins with other tables in structure along with the base view.
Requirement: Client wants to remove all these views and create a PL/SQL Package which can insert data directly from MS-Dynamics to View3 i.e., ‘D’.
Before I come up with something complex. I would like to know, is there any standard approach to address such requirements.
Any advice/suggestions are appreciated.
It should be obvious that you still need a fact table to keep some data.
You could get rid of B and C by making D more complex (the WITH clause might help to keep it overseeable).
Inserting data into D is (most likely) not possible per se, but you can create and INSTEAD OF INSERT trigger to handle that, i.e. insert into the fact table A instead.
Example for using the WITH clause:
Instead of
create view b as select * from dual;
create view c as select * from b;
create view d as select * from c;
you could write
create view d as
with b as (select * from dual),
c as (select * from b)
select * from c;
As you can see, the existing view definition goes 1:1 into the WITH clause, so it's not too difficult to create a view to combine all views.
If you are on Oracle 12c you might look at DBMS_UTILITY.EXPAND_SQL_TEXT, though you'll probably want to clean up the output a bit for readability.
A few things first
1) A view is a predefined sql query so it is not possible to insert records directly into it. Even a materialized view which is a persistant table structure only gets populated with the results of a query thus as things stand this is not possible. What is possible is to create a new table to populate the data which is currently aggregated at view D
2) It is very possible to aggregate data at muliple levels in Informatica using combination of multiple inline sorter and aggregater transformations which will generate the data at the level you're looking for.
3) Should you do it? Data warehousing best practices would say no and keep the data as granular as possible per the original table A so that it can be rolled up in many ways (refer Kimball group site and read up on star schema for such matters). Do you have much sway in the choice though?
4) The current process (while often used) is not that much better in terms of star schema

How to limit row insertion in a recursive trigger using SQLite?

First of all I am querying directly in a SQLite database managment software. Therefore, any use of programming language is impossible in my case and my only option is to work with triggers.
My database has a table named Article that I would like to populate with n dummy objects for test purpposes without reaching the recursive limit of triggers(limit I am unable to change since I would have to recompile the database). I suppose, by reading the official documentation, that this limit is fixed to 500 by default.
So far I have created a functionnal trigger but I am unable to stop its recursion after n insertion:
CREATE TRIGGER 'myTrigger'
AFTER INSERT ON 'Article'
WHEN (insertedRowNumber < 500)
BEGIN
INSERT INTO Article(...) VALUES(...);
END;
The Article table structure doesn't contain any kind of timestamp and it can not be changed because the database is already deployed for production.
How would one limit the number of rows inserted with the trigger pattern I provided ?
Thank you for your help !
If you can limit entries in Article instead of number of insertions, just:
CREATE TRIGGER myTrigger AFTER INSERT ON Article WHEN ((SELECT COUNT() FROM Article)<500) BEGIN INSERT INTO Article(...) VALUES(...); END;
Another option is using a helper view:
CREATE VIEW hlpArticle(a, ..., z, hlpCnt) AS SELECT a, ..., z, 1 AS hlpCnt FROM Article;
CREATE TRIGGER hlpTrigger INSTEAD OF INSERT ON hlpArticle WHEN (NEW.hlpCnt>0) BEGIN
INSERT INTO Article(a, ..., z) VALUES(NEW.a, ..., NEW.z);
INSERT INTO hlpArticle(a, ..., z) VALUES(NEW.a, ..., NEW.z, NEW.hlpCnt-1);
END;
So when you do:
INSERT INTO hlpArticle(a, ..., z, hlpCnt) VALUES('val_a', ..., 'val_z', 500);
it will insert 500 records on Article.

Resources