SQLite: Update one column based on a condition set in another table - sql

I'm a total noob in SQL and I've only just begun working with SQLite, and this is my first post here, so please pardon my inherent lack of understanding. I've been self-teaching myself after graduating with a BA, so please bear with me lol.
I've managed to get the following query to work, but only if all of the data was a part of the same table. I've since broken my two tables into three separate tables: widgetCustomer, widgetSale, and widgetOrderInfo.
UPDATE widgetOrderInfo
SET extendedcost = (cost * qty) * (0.80)
WHERE widgetCustomer.IsASenior = 'Y';
-- Seniors get a 20 percent discount;
^^ This is the part I'm having trouble with. Whenever I use this query SQLite returns, "No such column: IsASenior."
--NOTE: IsASenior is a column in widgetCustomer set to either 'Y' or 'N' based on a trigger earlier on in the code. I've gotten base multiplication to work very easily using only the data in the widgetOrderInfo table using the following query:
UPDATE widgetOrderInfo
SET extendedcost = (cost * qty);
Very basic, I'm aware, but I'm unsure what I must do in order to get my widgetOrderInfo table to acknowledge the data in widgetCustomer's 'IsASenior' column.
Do I have to use a JOIN in order to access data between two tables in an UPDATE statement?
Any assistance would be great! Thanks!
FULL LIST AND ORDER OF QUERIES I'M CURRENTLY PERFORMING (Lots of place-holders; WIP)
CREATE TABLE widgetCustomer
( id INTEGER PRIMARY KEY, name TEXT, age INT, IsASenior TEXT, last_order_id INT, soItemID INT );
CREATE TABLE widgetSale
( id INTEGER PRIMARY KEY, item_id INT, item_name TEXT, customer_id INT, quan INT, price MONEY(8,2) );
CREATE TABLE widgetOrderInfo
( id INTEGER PRIMARY KEY, salesOrderID INT, cost MONEY(8,2), qty INT, extendedcost MONEY(8,2) )
;
CREATE TRIGGER SeniorCheck AFTER INSERT ON widgetCustomer
BEGIN
UPDATE widgetCustomer
SET IsASenior = 'Y' WHERE age >= 65;
UPDATE widgetCustomer
SET IsASenior = 'N' WHERE age < 65;
END
;
INSERT INTO widgetCustomer (name, age, soItemID)
VALUES ('Ian', 24, 3);
INSERT INTO widgetCustomer (name, age, soItemID)
VALUES ('Andrew', 29, 2);
INSERT INTO widgetCustomer (name, age, soItemID)
VALUES ('John', 65, 1);
INSERT INTO widgetCustomer (name, age, soItemID)
VALUES ('Kathy', 60, 4)
;
CREATE TRIGGER newWidgetSale AFTER INSERT ON widgetSale
BEGIN
UPDATE widgetCustomer
SET last_order_id = NEW.id
WHERE widgetCustomer.id = NEW.customer_id;
END
;
INSERT INTO widgetSale (item_id, item_name, customer_id, quan, price)
VALUES (1, 'Blue Brick', 3, 50, 9.95);
INSERT INTO widgetSale (item_id, item_name, customer_id, quan, price)
VALUES (2, 'Red Brick', 2, 30, 4.95);
INSERT INTO widgetSale (item_id, item_name, customer_id, quan, price)
VALUES (3, 'Black Brick', 1, 24, 9.95);
INSERT INTO widgetSale (item_id, item_name, customer_id, quan, price)
VALUES (4, 'Yellow Brick', 4, 30, 9.95)
;
CREATE TRIGGER SubtractQuan AFTER INSERT ON widgetOrderInfo FOR EACH ROW
BEGIN
UPDATE widgetSale
SET quan = (quan - New.qty)
WHERE customer_id = New.id;
END
;
INSERT INTO widgetOrderInfo (salesOrderID, cost, qty)
VALUES (283001, 9.95, 4);
INSERT INTO widgetOrderInfo (salesOrderID, cost, qty)
VALUES (283002, 4.95, 8);
INSERT INTO widgetOrderInfo (salesOrderID, cost, qty)
VALUES (283003, 9.95, 5);
INSERT INTO widgetOrderInfo (salesOrderID, cost, qty)
VALUES (283004, 9.95, 15)
;
UPDATE widgetOrderInfo
SET extendedcost = (cost * qty)
;
SELECT
CASE
WHEN qty BETWEEN 5 and 7 THEN 'parcel'
WHEN qty BETWEEN 8 and 14 THEN 'package'
WHEN qty BETWEEN 15 and 30 THEN 'box'
WHEN qty BETWEEN 31 and 99 THEN 'crate'
ELSE 'individually wrapped'
END
AS PackagingBasedOnPurchaseSize,
COUNT(*) qty FROM widgetOrderInfo
GROUP BY
CASE
WHEN qty BETWEEN 5 and 7 THEN 'parcel'
WHEN qty BETWEEN 8 and 14 THEN 'package'
WHEN qty BETWEEN 15 and 30 THEN 'box'
WHEN qty BETWEEN 31 and 99 THEN 'crate'
ELSE 'individually wrapped'
END
;
SELECT * FROM widgetSale;
SELECT * FROM widgetCustomer;
SELECT * FROM widgetOrderInfo;
I can provide a link to my whole database if need be.

In an UPDATE statement, you can directly access only the updated table itself.
To access other tables, you need a subquery.
To find order info rows associated with senior customers, you can either look up the corresponding customer row for each order info row with a correlated subquery:
UPDATE widgetOrderInfo
SET extendedcost = (cost * qty) * (0.80)
WHERE (SELECT IsASenior
FROM widgetCustomer
JOIN widgetSale ON widgetCustomer.id = widgetSale.customer_id
WHERE widgetSale.id = widgetOrderInfo.salesOrderID
) = 'Y';
Or get all senior customers first, and check which order infos are in that set:
UPDATE widgetOrderInfo
SET extendedcost = (cost * qty) * (0.80)
WHERE salesOrderID IN (SELECT widgetSale.id
FROM widgetCustomer
JOIN widgetSale ON widgetCustomer.id = widgetSale.customer_id
WHERE IsASenior = 'Y');

Related

How to retrieve SCOPE_IDENTITY of all inserts done in INSERT INTO [table] SELECT [col1, ...] [duplicate]

This question already has answers here:
SQL Server - Return value after INSERT
(14 answers)
Closed 7 months ago.
Suppose I have a temp table with some cols one of which I have dedicated to identity column of the inserted Invoice and the others for inserting Invoice data itself. Like the following table :
CREATE TABLE #InvoiceItems
(
RowNumber INT, -- Used for inserting new invoice
SaleID INT, -- Used for inserting new invoice
BuyerID INT, -- Used for inserting new invoice
InvoiceID INT -- Intended for PK of the invoice added after inserting it
);
I use something like the following for inserting data into Invoice table
INSERT INTO [Invoice]
SELECT [col1, ...]
FROM #InvoiceItems
How can I achieve to fill the InvoiceID column while inserting table data into Invoice table using temp table? I know about SCOPE_IDENTITY() function but it returns the last inserted PK only which does not really suit my need.
I could also use a while to do this one by one but since the number of data I'm planning to insert is immense, I feel like it's not going to be the most optimized option.
Thanks for the answers in advance.
To grab multiple IDENTITY values from INSERT INTO SELECT FROM OUTPUT clause could be used:
-- temp table
CREATE TABLE #temp(col VARCHAR(100));
INSERT INTO #temp(col) VALUES ('A'), ('B'), ('C');
--target table
CREATE TABLE tab(
id INT IDENTITY,
col VARCHAR(100)
);
Main insert:
INSERT INTO tab(col)
OUTPUT inserted.id, inserted.col
SELECT col
FROM #temp;
The output could be also Inserted into another table using OUTPUT INTO:
CREATE TABLE #temp_identity(id INT);
INSERT INTO tab(col)
OUTPUT inserted.id
INTO #temp_identity
SELECT col
FROM #temp;
SELECT * FROM #temp_identity;
db<>fiddle demo
CREATE TABLE #InvoiceItems(
RowNumber INT,
SaleID INT,
BuyerID INT,
InvoiceID INT
);
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 55, 77)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 56, 78)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 57, 79)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 58, 80)
INSERT INTO #InvoiceItems (RowNumber, SaleID, BuyerID) values (1, 59, 81)
DECLARE #Inserted table( RowNumber int,
SaleID INT,
BuyerID INT,
InvoiceID INT);
INSERT INTO dbo.[Invoice] (RowNumber, SaleID, BuyerID)
OUTPUT INSERTED.RowNumber, INSERTED.SaleID, INSERTED.BuyerID, INSERTED.InvoiceID
INTO #Inserted
SELECT RowNumber, SaleID, BuyerID
FROM #InvoiceItems
UPDATE ii
SET InvoiceID = ins.InvoiceID
FROM #InvoiceItems ii
JOIN #Inserted ins on ins.BuyerID = ii.BuyerID and ins.RowNumber = ii.RowNumber and ins.SaleID = ii.SaleID
SELECT * FROM #InvoiceItems

Update multiple tables in trigger where one of the tables is used for trigger activation

Let's say I have two tables called widgetCustomer and widgetSale. On an insert in widgetSale I want to add a timestamp to the widgetSale row and add the sale id as last_order_id to the widgetCustomer table.
I understand using AFTER INSERT ON will result in an error on trying to update the NEW row, hence we need to use BEFORE INSERT ON clause. Which brings forward a new issue that AUTO_INCREMENT has not yet generated a id for sale hence last_order_id would all be zero. There is a method to do this at MySQL/MariaDB TRIGGER but it seems to fail on my system (i.e., the last order ids are still zero).
As a work around I'm using two different triggers one before insert and one after insert. Although it does work I'm keen to learn if there is a possible flaws with the method above and is there a better way of doing this (both in terms of performance and data integrity).
My code is given below:
DROP TABLE IF EXISTS widgetSale;
DROP TABLE IF EXISTS widgetCustomer;
DROP TABLE IF EXISTS widgetLog;
CREATE TABLE widgetCustomer ( id integer primary key AUTO_INCREMENT, name TEXT, last_order_id INT, stamp TEXT );
CREATE TABLE widgetSale ( id integer primary key AUTO_INCREMENT, item_id INT, customer_id INTEGER, quan INT, price INT, stamp TEXT );
CREATE TABLE widgetLog ( id integer primary key AUTO_INCREMENT, stamp TEXT, event TEXT, username TEXT, tablename TEXT, table_id INT);
INSERT INTO widgetCustomer (name) VALUES ('Bob');
INSERT INTO widgetCustomer (name) VALUES ('Sally');
INSERT INTO widgetCustomer (name) VALUES ('Fred');
SELECT * FROM widgetCustomer;
CREATE TRIGGER stampSale BEFORE INSERT ON widgetSale
FOR EACH ROW BEGIN
SET NEW.stamp = CURRENT_TIMESTAMP();
END
CREATE TRIGGER stampOnRest AFTER INSERT ON widgetSale
FOR EACH ROW BEGIN
UPDATE widgetCustomer SET last_order_id = NEW.id, stamp = CURRENT_TIMESTAMP()
WHERE widgetCustomer.id = NEW.customer_id;
INSERT INTO widgetLog (stamp, event, username, tablename, table_id)
VALUES (CURRENT_TIMESTAMP(), 'INSERT', 'TRIGGER', 'widgetSale', NEW.id);
END
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (1, 3, 5, 1995);
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (2, 2, 3, 1495);
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (3, 1, 1, 2995);
SELECT * FROM widgetSale;
SELECT * FROM widgetCustomer;
SELECT * FROM widgetLog;
I'm using mariadb 10.6.* on Archlinux.

How do I create an SQLite trigger on a table based on data in other tables?

I have two tables -
part(partID, brand, price, size)
consists_of(customerID, partID, quantity, price, shipdate)
|
FK part.ID from part
Table part is never going to be changed/updated, but consists_of will be.
How do I add a [before insert?] trigger on consists_of that checks if consists_of.price for each entry is less than or equal consists_of.quantity * part.price for that particular consists_of.partID and raises an abort if it isn't so?
Or,
How do I add [after insert?] trigger on consists_of that does INSERT INTO consists_of(price) VALUES (...) where the value of consists_of.price is equal to consists_of.quantity * part.price for that consists_of.partID?
If I understand you correctly, you can select part.price in subqueries and calculate part.price * consists_of.quantitiy.
before insert
CREATE TABLE part(part_id INTEGER, price INTEGER);
CREATE TABLE consists_of(customer_id INTEGER, part_id INTEGER, quantity INTEGER, price INTEGER);
INSERT INTO part VALUES(10, 50);
INSERT INTO part VALUES (20, 1000);
CREATE TRIGGER IF NOT EXISTS raise_if_consists_of_price_too_expensive
AFTER INSERT ON consists_of
WHEN new.price > (SELECT part.price * new.quantity FROM part WHERE part.part_id = new.part_id)
BEGIN
SELECT RAISE (ABORT, 'too expensive.');
END;
-- OK
INSERT INTO consists_of(customer_id, part_id, quantity, price)
VALUES(10050, 20, 31, 100);
-- OK
INSERT INTO consists_of(customer_id, part_id, quantity, price)
VALUES(80030, 10, 9, 50 * 9);
-- Raise abort
INSERT INTO consists_of(customer_id, part_id, quantity, price)
VALUES(80099, 10, 9, 50 * 9 + 1);
after insert
CREATE TABLE part(part_id INTEGER, price INTEGER);
CREATE TABLE consists_of(customer_id INTEGER, part_id INTEGER, quantity INTEGER, price INTEGER);
INSERT INTO part VALUES(10, 50);
INSERT INTO part VALUES (20, 1000);
CREATE TRIGGER IF NOT EXISTS fill_consists_of_price
AFTER INSERT ON consists_of
BEGIN
UPDATE consists_of
SET
price = (
SELECT consists_of.quantity * part.price
FROM part
WHERE part.part_id = consists_of.part_id
)
WHERE customer_id = new.customer_id AND part_id = new.part_id
;
END;
INSERT INTO consists_of(customer_id, part_id ,quantity)
VALUES(10050, 20, 31);
INSERT INTO consists_of(customer_id, part_id ,quantity)
VALUES(80033, 10, 9);

I do not understand how the trigger works in this case. How the last order id equal 3, 2, 1

> CREATE TABLE widgetCustomer ( id INTEGER PRIMARY KEY, name TEXT,
> last_order_id INT ); CREATE TABLE widgetSale ( id INTEGER PRIMARY KEY,
> item_id INT, customer_id INT, quan INT, price INT );
>
> INSERT INTO widgetCustomer (name) VALUES ('Bob'); INSERT INTO
> widgetCustomer (name) VALUES ('Sally'); INSERT INTO widgetCustomer
> (name) VALUES ('Fred');
>
> SELECT * FROM widgetCustomer;
>
> CREATE TRIGGER newWidgetSale AFTER INSERT ON widgetSale
> BEGIN
> UPDATE widgetCustomer SET last_order_id = NEW.id WHERE widgetCustomer.id = NEW.customer_id;
> END ;
>
> INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (1,
> 3, 5, 1995); INSERT INTO widgetSale (item_id, customer_id, quan,
> price) VALUES (2, 2, 3, 1495); INSERT INTO widgetSale (item_id,
> customer_id, quan, price) VALUES (3, 1, 1, 2995); SELECT * FROM
> widgetSale; SELECT * FROM widgetCustomer;
The output is 3,2,1 in the last order ID column, but when I change the values to 2,3,1 the output will be 3,1,2. I don't understand why? What does NEW do as well?
Your query:
SELECT *
FROM widgetCustomer
has no ORDER BY. So, the results are in an arbitrary order. If you want the results in a particular order, you need an ORDER BY clause.
Remember: SQL tables represent unordered sets. There is no ordering, unless you explicitly provide one.

Reference table from subquery in Oracle

I have simplified my tables but essentially I have a table of accounts that have a cycle_no and end date. This end date is always set to the first of the month but I need to get the real end date by looking in the calendar details table. The real end date is the next date for this cycle_no.
To create the simplified tables and enter a few rows of data:
CREATE TABLE DATA_OWNER.ACCOUNT
(
ACCNO NUMBER(4),
CYCLE_NO NUMBER(4),
ENDDATE DATE
);
CREATE TABLE DATA_OWNER.CALENDAR_DETAILS
(
CALENDAR_DT DATE,
BILL_CYCL_NO NUMBER(4)
);
INSERT INTO calendar_Details
VALUES
('18/DEC/2017',
17);
INSERT INTO calendar_Details
VALUES
('23/DEC/2017',
20);
INSERT INTO calendar_Details
VALUES
('18/JAN/2018',
17);
INSERT INTO calendar_Details
VALUES
('23/JAN/2018',
20);
INSERT INTO calendar_Details
VALUES
('20/FEB/2018',
17);
INSERT INTO calendar_Details
VALUES
('21/FEB/2018',
20);
INSERT INTO account
VALUES
(1, 17, '01/DEC/2107');
INSERT INTO account
VALUES
(2, 20, '01/DEC/2107');
If we run this query though we get "ACC". "ENDDATE": invalid identifier:
SELECT accno, cycle_no, enddate, actual_date
FROM account acc
JOIN
(
SELECT MIN(calendar_dt) actual_date
FROM calendar_details cal
WHERE calendar_dt > acc.enddate
)
ON acc.cycle_no = cal.bill_cycl_no;
Can anyone give us some pointers on the best way to achieve this please?
You cannot refer to outer table references in a subquery in the FROM. Just use a correlated subquery:
SELECT accno, cycle_no, enddate,
(SELECT MIN(cal.calendar_dt) as actual_date
FROM calendar_details cal
WHERE cal.calendar_dt > acc.enddate AND acc.cycle_no = cal.bill_cycl_no
) as actual_date
FROM account acc;