How to handle a missing feature of SQLite : disable triggers? - sqlite

How to handle a missing feature of SQLite: disable triggers?
I don't have it stored the name of triggers for a specific table.
For example how can I drop all triggers?
What would you do?

So here it is 2015 and there still is no 'disable triggers' in SQLite. For a mobile Application this can be problematic--especially if it's a corporate App requiring offline functionality and local data.
An initial data load can be slowed to crawl by trigger execution even when you don't wrap each insert in an individual transaction.
I solved this issue using SQLite SQL fairly simply. I have a settings table that doesn't participate in the init load. It holds 'list' of key/value pairs. I have one key called 'fireTrigger' with a bit value of 0 or 1. Every trigger I have has an expression that selects value and if it equals 1 it fires the trigger, otherwise it doesn't.
This expression is in addition to any expressions evaluated on the data relating to the trigger. e.g.:
AND 1 = (SELECT val FROM MTSSettings WHERE key = 'fireTrigger')
In simple clean effect this allows me to disable/enable the trigger with a simple UPDATE to the settings table

I wrote a very simple extension function to set a boolean value to true or false.
And a function to retrieve this value (GetAllTriggersOn()).
With this function I can define all my triggers like:
CREATE TRIGGER tr_table1_update AFTER UPDATE ON TABLE1 WHEN GetAllTriggersOn()
BEGIN
-- ...
END

SQLite stores schema (meta) information in the built-in sqlite_master table.
To get a list of available triggers use the below query:
SELECT name FROM sqlite_master
WHERE type = 'trigger' -- AND tbl_name = 'a_table_name'

Set a flag in your database and use it in the triggers WHEN condition.
Say you want to create a trigger on the "clients" table after an insert. You have created a table "trigger_settings" with a TINYINT "triggers_on" field - this is your flag. Then you can set the field to 0 if you want to turn off the filters and to 1 when you want to turn them back on.
Then you create your filter with a WHEN condition that checks the "triggers_on" field.
For example:
CREATE TRIGGER IF NOT EXISTS log_client_data_after_insert
AFTER INSERT
ON [clients]
WHEN (SELECT triggers_on FROM trigger_settings)=1
BEGIN
your_statement
END;

Maybe you can make a stored procedures for droping and creating them. Is that good for you ?

Expanding on Nick Dandoulakis's answer, you could drop all relevant triggers and then reinstate them before the transaction's completion:
BEGIN;
SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = 'mytable';
-- store all results
-- for each name: DROP TRIGGER $name;
-- do normal work
-- for each sql: execute the SQL verbatim
COMMIT;

Expanding other answers this is how i'm doing it. Take into account that this is disabling all triggers for all tables in the database except some of then used by spatialite
SQLITE_FILE=/tmp/my.sqlite
# Define output sql files as variables
CREATE_TRIGGER_SQL=/tmp/create_triggers.sql
DROP_TRIGGER_SQL=/tmp/drop_triggers.sql
## Dump CREATE TRIGGER statements to a file ##
# To wrap statements in a transaction
echo -e "BEGIN;\n\n" > "${CREATE_TRIGGER_SQL}"
# `SELECT sql` does not output semicolons, so we must concatenate them
sqlite3 -bail "${SQLITE_FILE}" "SELECT sql || ';' FROM sqlite_master WHERE type = 'trigger' AND (name NOT LIKE 'gid_%' AND name NOT LIKE 'ggi_%' AND name NOT LIKE 'ggu_%' AND name NOT LIKE 'gii_%' AND name NOT LIKE 'giu_%' AND name NOT LIKE 'vwgcau_%' AND name NOT LIKE 'vtgcau_%' AND name NOT LIKE 'gcau_%' AND name NOT LIKE 'geometry_columns_%' AND name NOT LIKE 'gcfi_%' AND name NOT LIKE 'gctm_%' AND name NOT LIKE 'vtgcfi_%' AND name NOT LIKE 'vwgcfi_%' AND name NOT LIKE 'vtgcs_%' AND name NOT LIKE 'vwgc_%' AND name NOT LIKE 'vtgc_%' AND name NOT LIKE 'gcs_%');" >> "${CREATE_TRIGGER_SQL}"
echo -e "\n\nCOMMIT;" >> "${CREATE_TRIGGER_SQL}"
## Dump DROP TRIGGER statements to a file ##
echo -e "BEGIN;\n\n" > "${DROP_TRIGGER_SQL}"
sqlite3 -bail "${SQLITE_FILE}" "SELECT 'DROP TRIGGER ' || name || ';' FROM sqlite_master WHERE type = 'trigger' AND (name NOT LIKE 'gid_%' AND name NOT LIKE 'ggi_%' AND name NOT LIKE 'ggu_%' AND name NOT LIKE 'gii_%' AND name NOT LIKE 'giu_%' AND name NOT LIKE 'vwgcau_%' AND name NOT LIKE 'vtgcau_%' AND name NOT LIKE 'gcau_%' AND name NOT LIKE 'geometry_columns_%' AND name NOT LIKE 'gcfi_%' AND name NOT LIKE 'gctm_%' AND name NOT LIKE 'vtgcfi_%' AND name NOT LIKE 'vwgcfi_%' AND name NOT LIKE 'vtgcs_%' AND name NOT LIKE 'vwgc_%' AND name NOT LIKE 'vtgc_%' AND name NOT LIKE 'gcs_%');" >> "${DROP_TRIGGER_SQL}"
echo -e "\n\nCOMMIT;" >> "${DROP_TRIGGER_SQL}"
## Execute like ##
sqlite3 -bail /"${SQLITE_FILE}" < "${DROP_TRIGGER_SQL}"
# do things
sqlite3 -bail /"${SQLITE_FILE}" < "${CREATE_TRIGGER_SQL}"

Related

How do I sort by names or postcodes using a drop-down list to determine what to sort by?

Currently, I am trying to create some software on Progress OpenEdge that sorts by customer's names or account codes.
So essentially, a box will open up when the program runs, the user will select "Name" from the drop-down list, and the program will display all the names in the database from alphabetical order.
Or, they will pick "Account" from the drop-down list, and it will display all the account codes in numeric order. I have attached a picture of the program here:
And this is currently the code I am using to print the results:
However, I'm not sure what I need to add for the others. Would I need IF statements, such as:
OR IF [drop down list] = "Account" THEN or something like that?
Any help would be appreciated.
While you can perform a static order by with a convulted set of if statements, it is a lot cleaner with a dynamic query.
To expand on Tom and Stefan's answers, you can use a query. The ABL lets you create a lot of things with static constructs and use them as dynamic. I think that in this case, you want to so something like the below.
Note that you can do build the query string either using OPEN QUERY qry FOR EACH ... or QUERY qry:QUERY-PREPARE('FOR EACH ... ') ; both will work equally well.
What I think you'd want is
(a) having a static definition of the query (ie DEFINE QUERY) since the cost of adding the buffer(s) to the query is done at compile time, not run time, and
(b) accessing the buffer fields statically (ie slmast.name rather than b::name )
define query qry for slmast.
define variable wc as character no-undo.
if condition eq true then
wc = "WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name".
else
wc = "WHERE TRUE".
/* alternate
if condition then
open query qry for each slmast no-lock WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name.
else
open query qry for each slmast no-lock.
*/
query qry:query-prepare(wc).
open query qry.
query qry:get-first().
do while available slmast:
/* do stuff with the buffer */
{&OUT} slmast.name.
query qry:get-next().
end.
query qry:query-close().
Using static constructs as far as possible means that you have less cleanup code to write and the code becomes more readable (IMO).
There are multiple ways to loop through the query results: using DO WHILE NOT QUERY qry:QUERY-OFF-END works as well as AVAILABLE slmast or b:AVAILABLE (if using a purely dynamic query).
As Stefan says, a dynamic query is what you want. This might help get you started:
define variable wc as character no-undo.
define variable q as handle no-undo.
define variable b as handle no-undo.
/* run your UI to get selection criteria amd then
* create a WHERE clause as appropriate
*/
wc = "WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name".
create buffer b for table "slmast".
create query q.
q:set-buffers( b ).
q:query-prepare( substitute( "FOR EACH slmast NO-LOCK &1", wc )).
q:query-open().
do while q:get-next():
display
b:buffer-field( "name" ):buffer-value
b:buffer-field( "acode" ):buffer-value
b:buffer-field( "pcode" ):buffer-value
b:buffer-field( "trunmtd" ):buffer-value
b:buffer-field( "turnytd" ):buffer-value
.
end.

How to implement NOT EXISTS in OPEN QUERY statement in PROGRESS 4GL - OpenEdge 10.2A

I want to create a browse such that it will show all the records from one table if the values of a field do NOT exist in another table.
It is possible to get the records using SQL as:
SELECT myField FROM pub.myTable WHERE
NOT EXISTS (SELECT myField FROM pub.myTable2 WHERE myTable2.myField=myTable.myField)
It is also possible using 4GL as:
FOR EACH myTable WHERE
NOT CAN-FIND(FIRST myTable2 WHERE myTable2.myField=myTable.myField)
The problem is when I put this query in a browse as:
OPEN QUERY myBrowse
FOR EACH myTable WHERE
NOT CAN-FIND(FIRST myTable2 WHERE myTable2.myField=myTable.myField)
it gives an error message
CAN-FIND is invalid within an OPEN QUERY. (3541)
The question is, is it possible to write such an OPEN QUERY statement?
I didn't come up with this, Steve Moore shared it on https://community-archive.progress.com/forums/00026/27143.html
define temp-table ttNoOrder
field field1 as char.
create ttNoOrder.
define query q1 for Customer, Order, ttNoOrder.
open query q1 for each Customer no-lock,
first Order of Customer outer-join no-lock,
first ttNoOrder where not available(Order).
get first q1.
repeat while not query-off-end("q1"):
display Customer.CustNum Customer.Name available(Order).
get next q1.
end.
Works even with dynamic queries:
DEFINE TEMP-TABLE ttNoOrder
FIELD field1 AS CHARACTER .
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
CREATE ttNoOrder.
CREATE QUERY hQuery .
hQuery:SET-BUFFERS (BUFFER Customer:HANDLE,
BUFFER Order:HANDLE,
BUFFER ttNoOrder:HANDLE) .
hQuery:QUERY-PREPARE ("for each Customer no-lock, ~
first Order of Customer outer-join no-lock, ~
first ttNoOrder where not available(Order)") .
hQuery:QUERY-OPEN() .
hQuery:GET-FIRST () .
REPEAT WHILE NOT hQuery:QUERY-OFF-END:
DISPLAY Customer.CustNum FORMAT ">>>>>>>>>9" Customer.Name AVAILABLE(Order).
hQuery:GET-NEXT ().
END.

UPDATE table SET column=DEFAULT not working

What is the correct way to UPDATE a column to the DEFAULT value?
The documentation suggests that it can be used only in INSERT statements.
drop table if exists testjulian;
create table testjulian(
jul REAL NOT NULL DEFAULT ( julianday('now'))
,whatever STRING);
insert into testJulian values(null,"a"); --null not allowed
insert into testJulian(whatever) values("b"); --default value is used
update testjulian set jul= DEFAULT ; --syntax error
update testjulian set jul= NULL ; --null not allowed
The DEFAULT keyword is limited to being used when creating a table and it is designed for when inserting.
It cannot be used for an update, hence the syntax error.
In your case you could use :-
update testjulian set jul = julianday('now');
Or if you really wanted to dynamically get the DEFAULT value you could extract it from the sql column of sqlite_master.
e.g. using
SELECT sql FROM sqlite_master WHERE name = 'testjulian';
gives you :-
CREATE TABLE testjulian(jul REAL NOT NULL DEFAULT (
julianday('now')),whatever STRING)

How to validate/debug trigger statement after it is loaded

There are two tables ... the 'master' (tblFile) holds record details of files that have been processed by some java code .. the PK is the file name. A column of interest in this table is the 'status' column (VALID or INVALID).
In the subordinate table (tblAnomaly) there are many records that hold the anomalies from processing each file .. this table has a FK as the file name from tblFile ... and along with other columns of relevant data there is a boolean type column which acts as an acceptance flag of the anomaly. NULL is accept .. # is not.
The user manually works their way through the list of anomalies presented in a swing ListPane and checks off the anomalies as they address the issue in the source file. When all the anomalies have been dealt with i need the status of the file in tblFile to change to VALID so that it can be imported into a database.
Here is the trigger i have settled on having designed the statements individually via an SQL editor .. however, i do not know how to validate/debug the trigger statement after it is loaded to the database, so cannot work out why it does not work ... no action and no feedback!!
CREATE TRIGGER
updateFileStatus
AFTER UPDATE ON tblAnomaly
WHEN 0 = (SELECT COUNT(*) FROM tblAnomaly WHERE tblAnomaly.file_name = tblFile.file_name AND tblAnomaly.accept = '#')
BEGIN
UPDATE tblFile
SET tblFile.file_status = 'VALID'
WHERE tblFile.file_name = tblAnomaly.file_name;
END;
So i worked it out! .. here is the solution that works.
CREATE TRIGGER
updateFileStatus
AFTER UPDATE ON tblAnomaly
WHEN 0 = (SELECT COUNT(*)
FROM tblAnomaly
WHERE file_name = old.file_name
AND accept = '#')
BEGIN
UPDATE tblFile
SET file_status = 'VALID'
WHERE file_name = old.file_name;
END;

Incorrect default value passed to the SQL Server database

I have set my column to int not null default 1... but whenever I save my record, it sets default value for that record to be 0.
I am not setting it anywhere. I don't know where I am making a mistake.
I have debugged my code , and when I am passing new entity object it is setting default value for not null to 0 .May be it is something with LINQ, But I don't know how to handle it.I don't want to explicitly assign value.
Thanks!
For sql-server, you can use SQL Server Profiler to catch all the scripts you run into the DB.
This may show you some details
Try running this query, replacing the 'myTable' and 'myColumn' values with your actual TABLE and COLUMN names, and see what's returned:
SELECT
OBJECT_NAME(C.object_id) AS [Table Name]
,C.Name AS [Column Name]
,DC.Name AS [Constraint Name]
,DC.Type_Desc AS [Constraint Type]
,DC.Definition AS [Default Value]
FROM sys.default_constraints DC
INNER JOIN sys.Columns C
ON DC.parent_column_id = C.column_id
AND DC.parent_object_id = C.object_id
WHERE OBJECT_NAME(DC.parent_object_id) = 'myTable'
AND COL_NAME(DC.parent_object_id,DC.parent_column_id) = 'myColumn'
;
Should return something like this:
[Table Name] [Column Name] [Constraint Name] [Constraint Type] [Default Value]
-------------------------------------------------------------------------------------------
myTable myColumn DF_myTable_myColumn DEFAULT_CONSTRAINT ('0')
If the [Default Value] returned is indeed (1), then it means that you have set the constraint properly and something else is at play here. It might be a trigger, or some other automated DML that you've forgotten/didn't know about, or something else entirely.
I am not the world's biggest fan of using a TRIGGER, but in a case like this, it could be handy. I find that one of the best uses for a TRIGGER is debugging little stuff like this - because it lets you see what values are being passed into a table without having to scroll through mountains of profiler data. You could try something like this (again, switching out the myTable and myColumn values with your actual table and column names):
CREATE TABLE Default_Check
(
Action_Time DATETIME NOT NULL DEFAULT GETDATE()
,Inserted_Value INT
);
CREATE TRIGGER Checking_Default ON myTable
AFTER INSERT, UPDATE
AS
BEGIN
INSERT INTO Default_Check (Inserted_Value)
SELECT I.myColumn
FROM Inserted I
;
END
;
This trigger would simply list the date/time of an update/insert done against your table, as well as the inserted value. After creating this, you could run a single INSERT statement, then check:
SELECT * FROM Default_Check;
If you see one row, only one action (insert/update) was done against the table. If you see two, something you don't expect is happening - you can check to see what. You will also see here when the 0 was inserted/updated.
When you're done, just make sure you DROP the trigger:
DROP TRIGGER Checking_Default;
You'll want to DROP the table, too, once it's become irrelevant:
DROP TABLE Default_Check;
If all of this still didn't help you, let me know.
In VB use
Property VariableName As Integer? = Nothing
And
In C# use
int? value = 0;
if (value == 0)
{
value = null;
}
Please check My Example:
create table emp ( ids int null, [DOJ] datetime NOT null)
ALTER TABLE [dbo].[Emp] ADD CONSTRAINT DF_Emp_DOJ DEFAULT (GETDATE()) FOR [DOJ]
1--Not working for Default Values
insert into emp
select '1',''
2 ---working for Default Values
insert into emp(ids) Values(13)
select * From emp

Resources