SQLite using NEW in subquery in a trigger - sqlite

I'm trying to create a trigger that classifies some news in my database by category after every insert on the table news if the attribute title contains "COVID-19" or "coronavirus" (then the category id should be the one with the name "COVID-19") or if the attribute title contains "Ukraine" or "Rusia" (then the category id should be the one with the name "War").
The tables have the following structure:
news (id, title, text, date, user(FK))
category (id, name, description)
new_cat(news(FK), category(FK))
And I tried implementing the following trigger without success:
CREATE TRIGGER news_categorizer AFTER INSERT ON news
FOR EACH ROW
BEGIN
INSERT INTO new_cat (news, category) VALUES (NEW.id,
(SELECT id from category WHERE name = "COVID-19" AND (NEW.title = "%COVID-19%" or NEW.title = "%coronavirus%")));
END;
How could I properly develop this subquery or can I do different INSERTs depending on a condition?

Use properly the operator LIKE in a CASE expression:
CREATE TRIGGER news_categorizer AFTER INSERT ON news
FOR EACH ROW
BEGIN
INSERT INTO new_cat (news, category)
SELECT NEW.id,
id
FROM category
WHERE name = CASE
WHEN (NEW.title LIKE '%COVID-19%') OR (NEW.title LIKE '%coronavirus%') THEN 'COVID-19'
WHEN (NEW.title LIKE '%Ukraine%') OR (NEW.title LIKE '%Rusia%') THEN 'War'
END;
END;
See a simplified demo.

Related

Oracle 11g Triggers

I have create a table person(id, name ,samenamecount).The samenamecount attribute can be null but for each row can store the row count for same names.I am achieving this by calling a stored procedure inside a after insert trigger.Below is my code.
create or replace procedure automatic(s in person.name%type)
AS
BEGIN
update person set samenamecount=(select count(*) from person where name=s) where name=s;
END;
create or replace trigger inserttrigger
after insert
on person
for each row
declare
begin
automatic(:new.name);
end;
On inserting a row it is giving error like
table ABCD.PERSON is mutating, trigger/function may not see it.
Can somebody help me to figure out this?
If you have the table:
CREATE TABLE person (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT person__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
Then rather than creating a trigger, instead, you could use a view:
CREATE VIEW person_view (
id,
name,
samenamecount
) AS
SELECT id,
name,
COUNT(*) OVER (PARTITION BY name)
FROM person;
You can use the trigger:
CREATE TRIGGER inserttrigger
AFTER INSERT ON person
BEGIN
MERGE INTO person dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY name) AS cnt
FROM person
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE SET samenamecount = src.cnt;
END;
/
fiddle
If you want to make it more efficient then you could use a compound trigger and collate the names that are being inserted and only update the matching rows.

I don't know how to verify if a parameter exists in the funcion or procedure

Design a table named "customs agencies" with the following fields: name, address, patent number, id_ customs agent. Where customs agent is another table with name and seniority.
Create a function or procedure that allows inserting new agencies and being able to edit an agency giving the ID of the customs agency.
I don't know how to do this: If id exists in parameter we do an update, else we insert new agencies.
Suppose parameter's name is PAR_ID; then you'd
create or replace procedure p_agency (par_id in agency.id%type,
par_name in agency.name%type
)
is
begin
if par_id is null then
insert into agency (id, name) values (par_id, par_name);
else
update agency set name = par_name
where id = par_id;
end if;
end;
If you wonder where are other columns - well, I'll leave that piece of homework to you.

Oracle Apex Select List LOV conditional query

I have an Editable Interactive Grid (for product_sale table) with a Select List (to select a product) in Oracle Apex 20.2 app.
Currently I'm using below query to populate this list of values.
SELECT productName,productId FROM product WHERE productAvailability = 'Y'
There are a few products that I need to set the productAvailability as 'N'. When I made this change, Interactive Grid of product_sale shows productId instead of the productName.
What I need to achieve is, only show products with productAvailability = 'Y' for new records (when going to add new record in to the table by clicking Add Row button) and for the old records show the productName of the selected product regardless the productAvailability.
Table Structure
Sample Data
Interactive Grid View
How could I achieve this?
You can achieve this using the "cascading list of values" option in the column settings. Let me illustrate with an example on the EMP sample table - you should be able to translate this to your own code:
Situation: BLAKE can not be selected as manager for any other employees than the one he is already manager for.
This would be the select:
SELECT ename, empno FROM emp WHERE ename != 'BLAKE'
As expected, for records that have BLAKE as manager, the MGR column will display the id because the value is not in the result set of the select list query. This is the behaviour you are seeing.
The solution has 2 steps:
Union the query with a query that has the value of the current row. I have rewritten the query using a CTE.
WITH all_mgr(dv, rv) AS
(SELECT ename, empno FROM emp WHERE ename != 'BLAKE'
UNION
SELECT m.ename, m.empno
FROM emp e
JOIN emp m ON e.mgr = m.empno
WHERE e.ename = :ENAME
)
SELECT dv, rv FROM all_mgr
Make sure column ename from the current row is bind to :ENAME. In "Cascading List Of Values" set both "Parent Column" and "Items to Submit" to ENAME. Now the select list will take the current row value for :ENAME and the list of values will include 'BLAKE' for users that have 'BLAKE' as a manager.

Plsql wont insert because of duplicate records

So I am trying to fill a table that connect two different tables by randomly selection the id from a products table x amounts of time and then putting this id together with an id of the shops table. And then repeating this for all id from the shopa table. This way every shop gets a random amount of products; However because sometimes my randomly selected ID is the same as an id that is already in the Table for example
shop = 1 ||product =34
shop = 1 || product =20
shop = 1 || product =34
How can i prevent this from happening the code that i am trying to execute is
create or replace PROCEDURE GENERATEPRODUCTS
AS
PRODUCTTEMP NUMBER;
NROFPRODUCTS NUMBER;--total number of available products
NROFWINKELS NUMBER;--total number of shops
MAXNROFPRODUCT NUMBER;-- the maximum amount of products to be inserted
PRODUCTPERCENTAGEZONDER NUMBER;-- random percentage that will be added to 90%
PRODUCTPERCENTAGEMET NUMBER;-- total percentage of to be inserted products
WINKELS NUMBER;--counter for looping through shops
PRODUCTIDTEMP NUMBER;--the id of the product that needs to be inserted
BEGIN
PRODUCTIDTEMP :=1;
WINKELS := 1;
PRODUCTPERCENTAGEMET :=0;
PRODUCTPERCENTAGEZONDER := 0;
select count(ID)
into NROFWINKELS
FROM WINKEL;
select count(ID)
into NrofWinkels
FROM WINKEL;
select count(ID)
into NROFPRODUCTS
FROM PRODUCT;
select DBMS_RANDOM.VALUE(0,10) into PRODUCTPERCENTAGEZONDER FROM DUAL;
PRODUCTPERCENTAGEMET := (90+PRODUCTPERCENTAGEZONDER)*.010;
MAXNROFPRODUCT:=ROUND(NROFPRODUCTS*PRODUCTPERCENTAGEMET);
WHILE WINKELS <= NROFWINKELS
LOOP
WHILE MAXNROFPRODUCT<=NROFPRODUCTS
LOOP
SELECT ID
INTO PRODUCTTEMP
FROM(
SELECT ID
FROM PRODUCT
ORDER BY DBMS_RANDOM.VALUE)
WHERE ROWNUM=1;
INSERT INTO WINKEL_COUPON
("ID",WINKEL_ID,COUPON_ID)
VALUES
(PRODUCTIDTEMP,WINKELS,PRODUCTTEMP);
PRODUCTIDTEMP := PRODUCTIDTEMP+1;
END LOOP;
WINKELS := WINKELS+1;
END LOOP;
END;
In this code i want that every shop gets atleast 90% of the available products;
I think you have to put a CONSTRAINT on the table.
That way you can make sure that some columns have a unique value in them.
For example, if you want the value for ColumnShop and ColumnProduct together to be unique in the table you have to put a contstraint on the table that looks something like this:
CONSTRAINT <NameTheConstraint> PRIMARY KEY(Shop, Product)
By doing this, the table will only accept values to be inserted in the table that are unique.
So Shop = 1, Product = 34 will be inserted if that combination doesn't exist yet. If it does it will atomaticly raise an error that a constraint has been violated.

Refactor SQLite Table by splitting it in two and link with foreign keys

I'm working on a SQLite Database. The database is already filled, but I want to refactor it. Here is a sample of what I need to do:
I currently have one table:
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT,
EngineCap FLOAT);
I want to split this into two tables:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
I have figured out to create a temporary table with the Cars table contents, and I can fill up the Vehicles table with the contents of the Cars table:
CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;
INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;
But I am still looking for a way to go over that same selection, while putting the EngineCap field into the new Cars table and somehow extracting the corresponding ID value from the Vehicles table to put into the VehicleID foreign key field on the Cars table.
I'm open for workaround or alternative approaches.
Thanks.
Since #mateusza did not provide an example, I've made one:
Suppose you have this table:
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
Now you want to move street and city into a separate table Address, so you'll end up with two tables:
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(For this example, I'm doing the conversion in the same database. You'd probably use two DBs, converting one into the other, with an SQL ATTACH command.)
Now we create a view (which imitates our original table using the new tables) and the trigger:
CREATE VIEW Customer1 (name, street, city) AS
SELECT C.name, A.street, A.city FROM Customer2 AS C
JOIN Address as A ON (C.addr == A.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Customer1 FOR EACH ROW BEGIN
INSERT INTO Address (street, city) SELECT NEW.street, NEW.city;
INSERT INTO Customer2 (addr, name) SELECT last_insert_rowid(), NEW.name;
END;
Now you can copy the table rows:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
The above is a simplified case where you'd only move some data into a single new table.
A more complex (and more general) case is where you want to...
Separate your original table's columns into several foreign tables, and
Have unique entries in the foreign tables (that's usually the reason why you'd refactor your table).
This adds some additional challenges:
You'll end up inserting into multiple tables before you can insert their rowids into the table with the referencing rowids. This requires storing the results of each INSERT's last_insert_rowid() into a temporary table.
If the value already exists in the foreign table, its rowid must be stored instead of the one from the (non-executed) insertion operation.
Here's a complete solution for this. It manages a database of music records, constisting of a song's name, album title and artist name.
-- Original table
CREATE TABLE [Song] (
[title] TEXT,
[album] TEXT,
[artist] TEXT
);
-- Refactored tables
CREATE TABLE [Song2] (
[title] TEXT,
[album_rowid] INTEGER,
[artist_rowid] INTEGER
);
CREATE TABLE [Album] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[title] TEXT UNIQUE
);
CREATE TABLE [Artist] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[name] TEXT UNIQUE
);
-- Fill with sample data
INSERT INTO Song VALUES ("Hunting Girl", "Songs From The Wood", "Jethro Tull");
INSERT INTO Song VALUES ("Acres Wild", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Broadford Bazar", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Statue of Liberty", "White Music", "XTC");
INSERT INTO Song VALUES ("Standing In For Joe", "Wasp Star", "XTC");
INSERT INTO Song VALUES ("Velvet Green", "Songs From The Wood", "Jethro Tull");
-- Conversion starts here
CREATE TEMP TABLE [TempRowIDs] (
[album_id] INTEGER,
[artist_id] INTEGER
);
CREATE VIEW Song1 (title, album, artist) AS
SELECT Song2.title, Album.title, Artist.name
FROM Song2
JOIN Album ON (Song2.album_rowid == Album.rowid)
JOIN Artist ON (Song2.artist_rowid == Artist.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Song1 FOR EACH ROW BEGIN
INSERT OR IGNORE INTO Album (title) SELECT NEW.album;
UPDATE TempRowIDs SET album_id = (SELECT COALESCE (
(SELECT rowid FROM Album WHERE changes()==0 AND title==NEW.album), last_insert_rowid()
) ) WHERE rowid==1;
INSERT OR IGNORE INTO Artist (name) SELECT NEW.artist;
UPDATE TempRowIDs SET artist_id = (SELECT COALESCE (
(SELECT rowid FROM Artist WHERE changes()==0 AND name==NEW.artist), last_insert_rowid()
) ) WHERE rowid==1;
INSERT INTO Song2 (title, album_rowid, artist_rowid) SELECT
NEW.title, (SELECT album_id FROM TempRowIDs), (SELECT artist_id FROM TempRowIDs);
END;
INSERT INTO TempRowIDs DEFAULT VALUES;
INSERT INTO Song1 (title, album, artist) SELECT title, album, artist FROM Song;
DROP TRIGGER TempTrig;
DROP TABLE TempRowIDs;
-- Conversion ends here
-- Print results
SELECT * FROM Song;
SELECT * FROM Song1;
-- Check if original and copy are identical (https://stackoverflow.com/a/13865679/43615)
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM Song UNION SELECT * FROM Song1)) == (SELECT COUNT() FROM Song) THEN 'Success' ELSE 'Failure' END;
Note that this example has one potential issue: If the constraints on the foreign table are more complex, the SELECT rowid FROM search for the existing entry needs to be updated accordingly. Ideally, SQLite should provide a way to determine the conflicting rowid somehow, but it doesn't, unfortunately (see this related question).
Simple solution without triggers:
create VEHICLES_TEMP table including the CAR_ID
create your new CARS table without the VEHICLES columns you don't want
update CARS with VEHICLE_ID taken from VEHICLES_TEMP (identified by the CAR_ID)
create final VEHICLES table without the CAR_ID
Create a table New_Cars and a INSTEAD OF INSERT trigger, which will insert data to both tables Vehicles and Cars. When inserting to Cars, you can use last_insert_rowid() function to refer to inserted row in Vehicles table.
This can be temporary solution, or you can leave it in your database for further modifications.

Resources