SQLite trigger to update a column on editing updates the column for all records in table - sqlite

Hypothetical table x, with columns (all of type text):
name
sname
sqlmodded
I'd like to create a trigger to set the value of sqlmodded to '1' if any column in the record is updated.
My trigger is as follows:
CREATE TRIGGER trigger1
AFTER UPDATE
ON x
FOR EACH ROW
WHEN old.sqlmodded IS NULL
BEGIN
UPDATE x
SET sqlmodded = '1';
END;
When I run an update statement to change a value of name or sname, the trigger kicks in but alters sqlmodded for all records in the table.
Here's code to reproduce:
CREATE TABLE "x"(
"NAME" Text,
"SNAME" Text,
"sqlmodded" Text );
CREATE TRIGGER "trigger1"
AFTER UPDATE
ON "x"
FOR EACH ROW
WHEN old.sqlmodded is null
BEGIN UPDATE x SET sqlmodded = '1'; END;
Now insert a few records:
INSERT INTO x
(
name
, sname
) VALUES
(
"Joe"
, "Bloggs"
)
;
INSERT INTO x
(
name
, sname
) VALUES
(
"Joline"
, "Bloggs"
)
;
Now run an update:
UPDATE x
SET
name = "Justine"
WHERE name = "Joline"
;
If you view the resulting two records both records will have had sqlmodded set to 1.

The trigger runs this command:
UPDATE x
SET sqlmodded = '1';
This statement indeed updates all rows in the table. This is how SQL works.
If you want to update only a specific row, you have to tell the database which row that would be:
UPDATE x
SET sqlmodded = '1'
WHERE rowid = NEW.rowid;

Here's the working code:
CREATE TRIGGER sqlmods
AFTER UPDATE
ON x
FOR EACH ROW
WHEN old.sqlmodded IS NULL
BEGIN
UPDATE x
SET sqlmodded = TRUE
WHERE rowid = NEW.rowid;
END;

Related

Error while deleting row from table color: sub-select returns 3 columns - expected 1

I am trying to write a trigger so that whenever a change occurs in any table in the db the trigger records the changes in a sql table with schema
I am trying to run this script
def log_changes(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT IN ('changes', 'sqlite_sequence');")
tables = cursor.fetchall()
for table in tables:
table_name = table[0]
column_names = ', '.join([f"{col[1]} as '{col[0]}'" for col in cursor.execute(
f"PRAGMA table_info({table_name})").fetchall()])
# print(column_names)
cursor.execute(f"DROP TRIGGER IF EXISTS log_{table_name}_insert")
cursor.execute(f"DROP TRIGGER IF EXISTS log_{table_name}_update")
cursor.execute(f"DROP TRIGGER IF EXISTS log_{table_name}_delete")
# create trigger so that when a row is inserted, the row is logged in the changes table with the action 'insert' and the current timestamp and the new data is the row that was inserted (NEW)
insert_trigger = f"CREATE TRIGGER IF NOT EXISTS log_{table_name}_insert AFTER INSERT ON {table_name} BEGIN INSERT INTO changes (table_name, action, timestamp, old_data, new_data) VALUES ('{table_name}', 'insert', datetime('now','localtime'), null, (SELECT {column_names} FROM {table_name} WHERE rowid = NEW.rowid)); END;"
# print(insert_trigger)
delete_trigger = f"CREATE TRIGGER IF NOT EXISTS log_{table_name}_delete AFTER DELETE ON {table_name} BEGIN INSERT INTO changes (table_name, action, timestamp, old_data, new_data) VALUES ('{table_name}', 'delete', datetime('now','localtime'), (SELECT {column_names} FROM {table_name} WHERE rowid = OLD.rowid), null); END;"
# print(delete_trigger)
update_trigger = f"CREATE TRIGGER IF NOT EXISTS log_{table_name}_update AFTER UPDATE ON {table_name} BEGIN INSERT INTO changes (table_name, action, timestamp, old_data, new_data) VALUES ('{table_name}', 'update', datetime('now','localtime'), (SELECT {column_names} FROM {table_name} WHERE rowid = OLD.rowid), (SELECT {column_names} FROM {table_name} WHERE rowid = NEW.rowid)); END;"
# print(update_trigger)
cursor.execute(insert_trigger)
cursor.execute(delete_trigger)
cursor.execute(update_trigger)
conn.commit()
conn.close()
this query works but the trigger is not working properly.
I want to insert the row data that has been modified in the new data column so I am using rowid are the queries correct?

Dynamic Column Update PLSQL trigger

I have two tables A and B
In table A there are two columns "Sequence Number" and "Content".
Name Null? Type
------- ----- ------------
SEQ_NO NUMBER(6)
CONTENT VARCHAR2(20)
In table B there are multiple statement columns like "Stmt_1", "Stmt_2", "Stmt_3" etc.
Name Null? Type
------ ----- ------------
STMT_1 VARCHAR2(20)
STMT_2 VARCHAR2(20)
STMT_3 VARCHAR2(20)
STMT_4 VARCHAR2(20)
I want to create a trigger on table A such that after every insert on table A, according to the "Sequence Number" value the corresponding column in table B gets updated.
For example: If table A has "Sequence Number" = 1 , then "Stmt_1" of table B gets updated to the value of "Content" column in table A.
If table A is given as
"SEQ_NO" "CONTENT"
1 "This is Content"
Then Table B should look like:
"STMT_1","STMT_2","STMT_3","STMT_4"
"This is Content","","",""
My approach is as follows:
create or replace trigger TestTrig
after insert on A for each row
begin
declare
temp varchar2(6);
begin
temp := concat("Stmt_",:new.seq_no);
update B
set temp = :new.content;
end;
end;
But I am getting an error in the update statement.
Does anyone know how to approach this problem?
You need to use dynamic SQL (and ' is for string literals, " is for identifiers):
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
end;
/
You probably want to handle errors when seq_no is input as 5 and there is no STMT_5 column in table B:
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
INVALID_IDENTIFIER EXCEPTION;
PRAGMA EXCEPTION_INIT(INVALID_IDENTIFIER, -904);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
EXCEPTION
WHEN INVALID_IDENTIFIER THEN
NULL;
end;
/
However
I would suggest that you do not want a table B or a trigger to update it and you want a VIEW instead:
CREATE VIEW B (stmt_1, stmt2, stmt3, stmt4) AS
SELECT *
FROM A
PIVOT (
MAX(content)
FOR seq_no IN (
1 AS stmt_1,
2 AS stmt_2,
3 AS stmt_3,
4 AS stmt_4
)
);
fiddle;

How to create a dynamic where clause in oracle

My requirement is to create a procedure or SQL query in which where clause should be created in run time depending on the paramters provided by the user.
Example if user provides data for three columns then where clause should have filter conditions for these three columns only to select the data from database table, like wise if user provides data for 4 columns then where caluse should have 4 columns.
I don't see any very specific solution to your question but it can be done using putting OR in where clause with different set of user input. See below:
Created procedure: Here my employee table has emp_id,name and salary columns. Now am assuming user sometimes passes emp_id and emp_name alone.
CREATE OR REPLACE PROCEDURE dynmc_selec (id NUMBER DEFAULT 0,
name VARCHAR2 DEFAULT 'A',
salary NUMBER DEFAULT 0,
emp_output IN OUT SYS_REFCURSOR)
AS
var VARCHAR2 (100);
BEGIN
--You need to make several combinations in where clause like ( emp_id , emp_name , salary ) OR ( emp_id , emp_name ) OR (emp_name , salary ) and use it in where clause with 'OR'.
--Also its needed that all the columns of the table is in where clause. If user doesnot pass anything then defualt value will be passed.
var :=
'select emp_id from employee where ( emp_id ='
|| id
|| ' and emp_name = '''
|| name
|| ''' ) OR emp_sal = '
|| salary;
DBMS_OUTPUT.put_line (var);
EXECUTE IMMEDIATE var;
OPEN emp_output FOR var;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
Calling it:
declare
a SYS_REFCURSOR;
v_emp_id employee.emp_id%type;
begin
--passing emp_id and name only
dynmc_selec(id=>1,name=>'KING',emp_output=>a);
loop
FETCH a INTO v_emp_id;
EXIT WHEN a%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_id );
end loop;
end;
Output:
select emp_id from employee where ( emp_id =1 and emp_name = 'KING' ) OR emp_sal = 0
1
select * from test_table
where
(1 = nvl2(:l_date, 0, 1) or created_at > :l_date)
and (1 = nvl2(:l_no, 0,1) or no = :l_no);
With Oracle nvl2 function:
When the parameter l_date is null, then 1 = nvl2(l_date, 0, 1) evaluates to true and the filter by created_at is not taking place.
When the parameter l_date is not null, then 1 = nvl2(l_date, 0, 1) evaluates to false and the filter by created_at is taking place.
The same thing happens with the parameter l_no.

How to segregate a stored procedure into a table row by row?

There is a stored procedure which contains many insert, update, delete and truncate statements. I want to group all the statements one by one into a table.
For example:
create proc Get_Tables as
begin
UPDATE BB_FMCTransactionsTwo SET wsTradeDate = wsSettleDate WHERE wsEntryCode = 'NRT'
UPDATE BB_FMCTransactionsTwo SET wsOK = 1, wsSpecialLogic = SpecialLogic FROM BB_EntryCode INNER JOIN BB_FmcTransactionsTwo ON EntryCode = wsEntryCode
UPDATE BB_FMCTransactionsTwo SET wsFMCFtNt = '' FROM BB_EntryCode INNER JOIN BB_FmcTransactionsTwo ON EntryCode = wsEntryCode WHERE wsBS <> 'B' AND ( FMCFtNtB IS NULL OR FMCFtNtB = 'PR' )
UPDATE BB_B204_Tran SET AMOUNT = dbo.BCA_AMT(Desc1, Desc2, Desc3, Udesc1, Udesc2, Udesc3) WHERE TRANIND = 14 AND PORTTYPE IN (2,3)
DELETE BB_B204_Tran WHERE TRANIND = 14 AND PORTTYPE IN(2,3) AND (Quantity IS NULL OR ISNUMERIC(Quantity) = 0 OR Quantity = -1 OR ISNUMERIC(AMOUNT) = 0
end
Required:
I want to insert the update statements, delete statements into a table one by one...
Create a stored procedure which divides the stored procedure based on type of statements (Insert, Update, Truncate) and inserts that into a table row by row. So that the table looks like below.
Table:
Sl.No Statement
1 UPDATE BB_FMCTransactionsTwo SET wsTradeDate = wsSettleDate WHERE wsEntryCode = 'NRT'
2 DELETE BB_B204_Tran WHERE TRANIND = 14 AND PORTTYPE IN(2,3)
I have implemented for your first update statement and assume that your AuditTable will have one column SQLData and so you need to store these SQL Statements in your store procedure and if you have used any variable then you need to store into SQL Data variable and insert into Audit Table. it may help you.
declare ##SQL1 varchar(max)
UPDATE BB_FMCTransactionsTwo SET wsTradeDate = wsSettleDate WHERE wsEntryCode = 'NRT'
set ##SQL1 = 'UPDATE BB_FMCTransactionsTwo SET wsTradeDate = wsSettleDate WHERE wsEntryCode = ''NRT'''
insert into AuditTable (SQLData) values(##SQL1)
For Variable:
DECLARE ##wsEntryCode VARCHAR(100)
SET ##wsEntryCode='NRT'
PRINT 'UPDATE BB_FMCTransactionsTwo SET wsTradeDate = wsSettleDate WHERE wsEntryCode = ''' + ##wsEntryCode + ''''
insert into AuditTable (SQLData) values(##SQL1)

Verify existence of two columns in different tables in a single SQL transaction

I'm trying to verify if data exists in two different tables in a single transaction. The reason for the single transaction is the database gets hit about 1-3 million times a day so adding anymore than 1 extra transaction would increase that number up to 9 million, and my poor little server needs a break :)
So I need to check if an ID exists in table X and table Y and return the results to my VB.net script so I can handle the outcome Ideally something like this would work
if exists (select id from X where id = #id)
print 'True,' else print 'False,'
if exists (select id from Y where id = #id)
print 'True' else print 'False'
Which gives me "True, True" if exists in both or "True, False" etc etc... But that only displays in SQL print and not actually returning it as an object/string or array values that I can use.
I'm open to any sort of solution of this nature that can give me two results from a single transaction and how to handle that response in vb. Thanks
SELECT
Case When EXISTS(SELECT 1 FROM X WHERE id = #id) Then 1 Else 0 End AS IsInX,
Case When EXISTS(SELECT 1 FROM Y WHERE id = #id) Then 1 Else 0 End AS IsInY
select (select COUNT(*) from X where id = #id) AS x_exists,
(select COUNT(*) from Y where id = #id) AS y_exists
This returns one data row with two fields, each containing either 0 or 1 (or more, if id is not unique).
CREATE PROCEDURE CheckIDOnTables(#ID int)
AS
BEGIN
DECLARE #X AS NVARCHAR(10)
DECLARE #Y AS NVARCHAR(10)
Set #X = 'False'
Set #Y = 'False'
if exists (select id from TableX where id = #ID)
Set #X = 'True'
if exists (select id from TableY where id = #ID)
Set #Y = 'True'
SELECT #X AS XExists, #Y AS YEsists
END
It will give you your desired results.

Resources