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

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?

Related

How to insert data from R into Oracle table with identity column?

Assume I have a simple table in Oracle db
CREATE TABLE schema.d_test
(
id_record integer GENERATED AS IDENTITY START WITH 95000 NOT NULL,
DT DATE NOT NULL,
var varchar(50),
num float,
PRIMARY KEY (ID_RECORD)
)
And I have a dataframe in R
dt = c('2022-01-01', '2005-04-01', '2011-10-02')
var = c('sgdsg', 'hjhgjg', 'rurtur')
num = c(165, 1658.5, 8978.12354)
data = data.frame(dt, var, num)%>%
mutate(dt = as.Date(dt))
I'm trying to insert data into Oracle d_test table using the code
data %>%
dbWriteTable(
oracle_con,
value = .,
date = T,
'D_TEST',
append = T,
row.names=F,
overwrite = F
)
But the following error returned
Error in .oci.WriteTable(conn, name, value, row.names = row.names, overwrite = overwrite, :
Error in .oci.GetQuery(con, stmt, data = value) :
ORA-00947: not enough values
What's the problem?
How can I fix it?
Thank you.
This is pure Oracle (I don't know R).
Sample table:
SQL> create table test_so (id number generated always as identity not null, name varchar2(20));
Table created.
SQL> insert into test_so(name) values ('Name 1');
1 row created.
My initial idea was to suggest you to insert any value into the ID column, hoping that Oracle would discard it and generate its own value. However, that won't work.
SQL> insert into test_so (id, name) values (-100, 'Name 2');
insert into test_so (id, name) values (-100, 'Name 2')
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
But, if you can afford recreating the table so that it doesn't automatically generate the ID column's value but use a "workaround" (we used anyway, as identity columns are relatively new in Oracle) - a sequence and a trigger - you might be able to "fix" it.
SQL> drop table test_so;
Table dropped.
SQL> create table test_so (id number not null, name varchar2(20));
Table created.
SQL> create sequence seq_so;
Sequence created.
SQL> create or replace trigger trg_bi_so
2 before insert on test_so
3 for each row
4 begin
5 :new.id := seq_so.nextval;
6 end;
7 /
Trigger created.
Inserting only name (Oracle will use a trigger to populate ID):
SQL> insert into test_so(name) values ('Name 1');
1 row created.
This is what you'll do in your code - provide dummy ID value, just to avoid
ORA-00947: not enough values
error you have now. Trigger will discard it and use sequence anyway:
SQL> insert into test_so (id, name) values (-100, 'Name 2');
1 row created.
SQL> select * from test_so;
ID NAME
---------- --------------------
1 Name 1
2 Name 2 --> this is a row which was supposed to have ID = -100
SQL>
The way you can handle this problem is to create table with GENERATED BY DEFAULT ON NULL AS IDENTITY like this
CREATE TABLE CM_RISK.d_test
(
id_record integer GENERATED BY DEFAULT ON NULL AS IDENTITY START WITH 5000 NOT NULL ,
DT date NOT NULL,
var varchar(50),
num float,
PRIMARY KEY (ID_RECORD)
)

SQLite3 run a trigger insert multiple times besed on inserted vlaue

Want to run an insert n-times, the n is defined by inserted value on another table.
table 1:
create table Table1
(
id INTEGER default 1 not null
primary key
unique,
name TEXT not null
unique,
keys_number INTEGER default 1 not null
);
table 2:
create table table_2
(
id INTEGER default 1 not null
primary key
unique,
table_1_id INTEGER not null
references table_1,
);
Now when i insert into table_1 like:
INSERT INT table_1 (name, keys_number) VALUES ('dummy_name', 5)
want to run a trigger
CREATE TRIGGER insert_keys_after_insert_table_1
AFTER INSERT ON table_1
WHEN (SELECT COUNT(*) FROM table_2 WHERE table_1_id=new.id) < new.keys_number
BEGIN
INSERT INTO keys (table_1_id) VALUES (new.id);
END;
But this runs only once.
Finally, I want to have on table_2 values after the trigger
INSERT INT table_1 (name, keys_number) VALUES ('dummy_name_1', 5)
make the result like:
id;name;keys_number
1;dummy_name_1;5
and the result on table_2 after the trigger, should be
id;table_1_id
1;1
2;1
3;1
4;1
5;1
or if I insert another one on table_1
INSERT INT table_1 (name, keys_number) VALUES ('dummy_name_2', 2)
will make the result on table_1 like:
id;name;keys_number
1;dummy_name_1;5
2;dummy_name_2;2
and the trigger will give the result on table_2:
id;table_1_id
1;1
2;1
3;1
4;1
5;1
6;2
7;2
For this requirement you need a loop, which in SQL is implemented via a recursive CTE.
Unfortunately SQLite does not allow CTEs inside a trigger.
A workaround is to create a new table with only 1 column which will store the numbers 1 to the max expected value of keys_number in table_1:
CREATE TABLE table_numbers AS
WITH cte(keys_number) AS (
SELECT 1
UNION ALL
SELECT keys_number + 1 FROM cte WHERE keys_number < 100 -- change 100 to the max keys_number you expect
)
SELECT keys_number FROM cte
Now you can create your trigger:
CREATE TRIGGER insert_keys_after_insert_table_1 AFTER INSERT ON table_1
BEGIN
INSERT INTO table_2(table_1_id)
SELECT NEW.id
FROM table_numbers
WHERE keys_number <= NEW.keys_number - (SELECT COUNT(*) FROM table_2 WHERE table_1_id = NEW.id);
END;
See the demo.

Updating a singular row in a trigger SQLite

Table rental has values (ID, odo_out, date),
table vehicle has values (ID, odo, car),
both with more columns but not relevant to this.
I have attempted to create a trigger:
CREATE TRIGGER odo_update AFTER INSERT ON rental
BEGIN
UPDATE rental SET odo_out = (SELECT Vehicle.odo FROM Vehicle WHERE rental.ID = Vehicle.ID)
WHERE EXISTS (SELECT * FROM Vehicle WHERE Vehicle.ID = rental.ID);
END;
which should detect a NULL for rental.odo_out and replace it with the value in Vehicle.odo for corresponding ID. This does work, but it updates every row in table, whereas I want it to ONLY update the row with NULL, ie the new row being inserted. An ID can be repeated multiple times in the rental table. How can I do this?
You must set the condition so that only the new row is updated.
This is where you need the keyword NEW to refer to the columns of the new row:
CREATE TRIGGER odo_update AFTER INSERT ON rental
WHEN NEW.odo_out IS NULL
BEGIN
UPDATE rental
SET odo_out = (SELECT odo FROM Vehicle WHERE ID = NEW.ID)
WHERE ID = NEW.ID;
END;

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

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;

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)

Resources