Is there a way to update SQL in such a way, for example.
Master Table has a column called INVOICESCOUNT.
When an invoice is deleted successfully, then the INVOICESCOUNT is decreased.
For example, a SQL psuedo-code statement like this:
Delete From Invoices where INVOICE=500;
Update Customers SET INVOICECOUNT=INVOICECOUNT-1 WHERE Customer=1 (if prior statement returns 1 affected row);
I need it to be embedded within the same SQL statement instead of having the source code handling executing the 2 statements separately.
Thanks for any advice. Please also let me know if there is any minimal MySQL version requirement if there is such a solution.
UPDATE with more info: note that the list of Customers I present to the user can be very different each time, example, CustomerGroupID=? or CustomerCreated within a certain date, so the Customers query cannot be cached efficiently, as such I prefer to update the INVOICECOUNT (as it will be hit on many times in an hour by different users listing different groups of customers).
Better idea: instead have a VIEW that shows you Customer's invoice counts:
CREATE VIEW CustomersInfo AS
SELECT
CustomerId,
COUNT(*) AS InvoiceCount
FROM
Invoices
GROUP BY
CustomerId
;
Then you'd use it like so:
SELECT
c.CustomerId,
COALESCE( ci.InvoiceCount, 0 ) AS InvoiceCount
FROM
Customers AS c
LEFT OUTER JOIN CustomersInfo AS ci ON c.CustomerId = ci.CustomerId
(Don't use an INNER JOIN, otherwise Customers without any invoices won't be in the output).
You can use triggers for that
CREATE TABLE Invoices(INVOICE INT)
CREATE TABLe Customers(Customer int,INVOICECOUNT int)
INSERT INTO Customers VALUES (1,1)
CREATE TRIGGER del_after AFTER DELETE ON Invoices
FOR EACH ROW
Update Customers SET INVOICECOUNT=INVOICECOUNT-1 WHERE Customer=1
Delete From Invoices where INVOICE=500;
SELECT * FROM Customers
Customer | INVOICECOUNT
-------: | -----------:
1 | 1
INSERT INTO Invoices VALUES (400)
Delete From Invoices where INVOICE=500;
SELECT * FROM Customers
Customer | INVOICECOUNT
-------: | -----------:
1 | 1
INSERT INTO Invoices VALUES (500)
Delete From Invoices where INVOICE=500;
SELECT * FROM Customers
Customer | INVOICECOUNT
-------: | -----------:
1 | 0
db<>fiddle here
As was mentioned in the comments, you probably don't need to store the invoice count in a table column, however if you MUST have the invoice count column for any reason your best bet might be a stored procedure.
DELIMITER //
CREATE PROCEDURE removeinvoice
BEGIN
DELETE FROM invoices WHERE invoice=500;
UPDATE customers SET invoicecount = (SELECT COUNT(*) FROM invoices);
END//
DELIMITER ;
Then you just call that stored procedure.
CALL removeinvoice;
Related
There are two tables: orders, orders_history.
orders
________
id | status
orders_history
id | order_id | status | user_id
The orders_history contains history of all user's actions. At the same time orders.status contains the last status from orders_history.status.
I make these queries in transation:
transaction start
insert into orders_history...
$status = select status from order_history order by id desc limit 1;
update orders set status = $status where orders.id = id
My question is:
Should I use transaction and is it properly way to do that?
What if several transactions try to insert, update orders_history for the same order_id.
As suggested in the comments above you could use a trigger to update the orders table -
DELIMITER $$
CREATE TRIGGER `update_order_status` AFTER INSERT ON `orders_history`
FOR EACH ROW
UPDATE `orders` SET `status` = NEW.status WHERE id = NEW.order_id;
$$
DELIMITER ;
The better option would be to not store the redundant status in orders and just query for most recent status in orders_history.
SELECT orders.id, (SELECT status FROM orders_history oh WHERE orders.id = oh.order_id ORDER BY id DESC LIMIT 1) AS status
FROM orders
The design pattern I might use in this case is...
Table 1: History -- this is an audit trail of everything that has gone on. (Think: All the checks written and deposits made to a checking account.)
Table 2: Current -- this is the current status of the information. (Think, current account balance, status, etc.)
Whenever something happens (eg, a check clears):
START TRANSACTION;
INSERT INTO History ...;
UPDATE Current ...;
COMMIT;
In the case of a checking account, something different is needed if your account is overdrawn, so let's make the transaction more complex:
START TRANSACTION;
SELECT balance FROM Current WHERE acct = 123 FOR UPDATE;
if would be overdrawn then
email user
UPDATE Current SET status = 'overdrawn' acct = 123;
...
else
INSERT INTO History ...;
UPDATE Current ...;
endif
COMMIT;
I prefer to put the "business logic" clearly in one place, not hidden in a Trigger. (I might use a Trigger for monitoring or logging, but not for the main purpose of the tables.)
I have two table :
Table 1
Book's Name
Publications
Poem
A
Novel
A
Math
B
and Table 2
Publications
Number of Books
A
2
B
1
Now, I want to write a trigger that after deleting from Table 1 and Number of books = 0 , delete that row from table 2 and if Number of books not null update the value of the Number of books.
for Example :
1.delete from Table 1
where Bookname=poem
and
2.delete from Table 1
where Bookname=Math
Then Table 2 would be change like This
Publications
Number of Books
A
1
I don't have enough reputation to comment but the two commenters are correct, I would use a view instead of a table for Table 2. For example:
CREATE VIEW dbo.vwTable2
AS
SELECT Publications, COUNT(*) BooksNumber
FROM Table1
GROUP BY Publications
If it has to be a trigger this should do the trick. It's a bit hacky but that's the price you gotta pay when you want to solve a problem one way. I wrote this in mssql though so might need a bit of converting to mysql as I'm not well versed in it.
CREATE TRIGGER dbo.trUpdateTable2 ON dbo.Table1
AFTER INSERT
,UPDATE
,DELETE
AS
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE dbo.Table2
INSERT INTO dbo.Table2
SELECT Publications, COUNT(*) BooksNumber
FROM dbo.Table1
GROUP BY Publications
END;
I apologize for the title, I couldn't find a good way to word my question. I'm very new to SQL.
Essentially, I'm creating this table (the ids actually reference the students table, but I've simplified it here):
CREATE TABLE followers (student_id int not null,
followee_id int not null,
followsback boolean,
PRIMARY KEY(student_id, followee_id)
SET followsback = IF(SELECT from followers
WHERE student_id = followee_id AND
followee_id = student_id, 1, 0)
My problem lies with the IF statement. Say I ran this INSERT query:
INSERT into followers(student_id, followee_id) values(001,002)
This is supposed to store that student 001 is following student 002.
I need to select the followee (002) and check if they are following the student (001) back. To do this, I need to check the followers table for a user with student_id = followee_id (e.g student_id = 002), then check to see if that user (002) is following the original student_id (001).
The problem is that I don't know how to reference the student_id as specified within the INSERT query vs referencing the value within my SELECT query.
Then if the two students are following each other then I need to set followsback to 1.
Hopefully this makes sense, I am having a ridiculously hard time explaining this.
There's no syntax in MySQL's CREATE TABLE statement to do what you show. It could be done by a rare feature in the SQL specification called an "assertion"—except there is no SQL database on the market that implements this feature.
You can try to implement it as a trigger:
CREATE TRIGGER followback_ins BEFORE INSERT ON followers
FOR EACH ROW
SET NEW.followsback = EXISTS (
SELECT * from followers
WHERE student_id = NEW.followee_id AND followee_id = NEW.student_id);
But this has a problem. It only updates the followsback for the new record, not the original record.
mysql> insert into followers set student_id = 123, followee_id = 456;
mysql> insert into followers set student_id = 456, followee_id = 123;
mysql> select * from followers;
+------------+-------------+-------------+
| student_id | followee_id | followsback |
+------------+-------------+-------------+
| 123 | 456 | 0 |
| 456 | 123 | 1 |
+------------+-------------+-------------+
This is called an anomaly because when you try to store the same fact in two places, these two rows can contradict each other.
We could try to make a trigger that updates the original row too:
CREATE TRIGGER followback_ins AFTER INSERT ON followers
FOR EACH ROW
UPDATE followers AS f1 JOIN followers AS f2
ON f1.student_id=f2.followee_id AND f1.followee_id=f2.student_id
SET f1.followsback=true, f2.followsback=true;
But this is illegal. You can't update a table from a trigger on that same table (too much risk of infinite recursion).
ERROR 1442 (HY000): Can't update table 'followers' in stored function/trigger
because it is already used by statement which invoked this stored
function/trigger.
I'd suggest to forget about storing followsback at all. Instead, just store the following relationships as two rows, without a followsback column. If you want to know if they follow each other, you have to join two rows together:
SELECT COUNT(*)
FROM followers AS f1 JOIN followers AS f2
ON f1.student_id=f2.followee_id AND f1.followee_id=f2.student_id
WHERE f1.student_id = 123 AND f1.followee_id = 456.
This query will return 0 if there is no mutual following, and 2 if there is.
I have two tables:
Persons (PersonID, LastName, FirstName)
Orders (O_Id, OrderNo, P_Id)
Orders.P_Id should have values from Persons.PersonID.
I am trying to do an insert on Orders to insert the P_Id into orders but I want it to match a value in my Persons table and also my Orders table so I can link them up and allow the P_id to be linked to the order.
I have tried this below? not sure what the best way to do this would be?
INSERT INTO Orders (P_Id)
SELECT PersonID FROM Persons
WHERE PersonID='1'
UNION ALL
SELECT O_Id FROM Orders
WHERE O_Id ='1';
Edit:
I have tried UNION ALL but it doesn't add them on the same line here is my sql fiddle which shows what is happening:
http://sqlfiddle.com/#!9/30a71/1
anyone help?
I think you want to UPDATE (not INSERT):
UPDATE ORDERS SET
P_Id = 1
WHERE O_Id = 1;
See SQLFiddle
I think what you're actually trying to achieve is not through the form of an INSERT, but an UPDATE:
Try this:
UPDATE Orders
SET P_ID = (SELECT PersonID From Persons WHERE PersonID = 1)
WHERE O_ID = 1
You can't "INSERT" a value into an existing row with a INSERT command. For that reason the UPDATE command exists.
The INSERT is useful only when creating a row in the table. For all other scenarios, when you are trying to populate a column with data, into an existing row, then you should use UPDATE.
The above query is basically a re-write of what you have, but since you already know the value of PersonID which you want to populate the P_ID column with, then you can simplify the query to:
UPDATE Orders
SET P_Id = 1
WHERE O_Id = 1
I get the following error:
"Can't update table 'Product' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
CREATE TRIGGER `SalesDB`.`updateSum` AFTER UPDATE ON SalesDB.Product FOR EACH ROW
BEGIN
DECLARE totalStock INT;
SELECT SUM(stock)
INTO totalStock
FROM Product
WHERE Product.prodID= NEW.prodID;
UPDATE Product SET StockSum = totalStock
WHERE prodID = NEW.prodID;
END;
I understand that there is a recursive issue, but how do I do something like this? Is there a way to use triggers to do this kind of thing? Should this be done in a Stored Procedure instead, and how do I run the stroed procedure on a Update Product event?
I need to update the stock of a product and then go thru all rows that have the same prodID calculate the SUM and Update the SumTotal field of all the rows.
UPDATED Unfortunately you can't do this in MySql due to the limitations of triggers implementation.
Possible options are:
Create stored procedure and call it after each update on Product table
Store stockSum in a separate table (e.g. ProductStock), use triggers (INSERT, UPDATE) to update stockSum values, create a view on joined Product and ProductStock
Use MySql events or a cron job to update stockSum in Product table if some delay is acceptable
IMHO best approach is 2 separating Product and ProductStock tables:
ProductStock might look like this
CREATE TABLE ProductStock
(
prodID int NOT NULL PRIMARY KEY,
stockSum decimal(12, 3)
);
Now triggers
CREATE TRIGGER tg_product_update
AFTER UPDATE ON Product
FOR EACH ROW
CALL sp_updateProductStock(NEW.prodID);
CREATE TRIGGER tg_product_insert
AFTER INSERT ON Product
FOR EACH ROW
CALL sp_updateProductStock(NEW.prodID);
and a stored procedure
CREATE PROCEDURE sp_updateProductStock(IN ProductID INT)
REPLACE INTO ProductStock (prodID, stockSum)
SELECT ProductID,
(SELECT SUM(COALESCE(stock, 0))
FROM Product
WHERE prodID = ProductID);
And finally a view
CREATE VIEW vw_Product AS
SELECT p.id, p.prodID, ... , p.stock, s.stockSum
FROM Product p JOIN ProductStock s
ON p.prodID = s.prodID
Here is SQLFiddle example