updating two table with one single insert-SQL - mysql

Is it possible to make the following two queries to one single query?
update customers set customer_name = 'John' where customer_id=1;
update purchases set state='Accepted' where customer_id=1;
customer (table)
customer_id(PK)
customer_name
purchases (table)
customer_id(FK)
product
state
Thanks

You can execute them in a single transaction:
START TRANSACTION;
update customers set customer_name = 'John' where customer_id=1;
update purchases set state='Accepted' where customer_id=1;
COMMIT;
If something fail inside the transaction all changes are rolled back

Related

How to update related table MYSQL?

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.)

MySQL update a master field only if condition is true

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;

Procedure delete from different table

I am trying to create a procedure that will delete everything related to the cust_id. When I enter the cust_id when I execute the procedure it deletes everything from the customer and invoice tables but not from the line table and I don't know why. This is what I have
BEGIN
DELETE FROM CUSTOMER WHERE CUS_CODE = cust_id;
DELETE FROM INVOICE WHERE INVOICE.CUS_CODE = cust_id;
DELETE FROM LINE WHERE EXISTS (SELECT * FROM INVOICE WHERE
INVOICE.INV_NUMBER = LINE.INV_NUMBER AND invoice.CUS_CODE = cust_id);
END
If you delete all records related to a customer from the invoices table first, then the exists() operator cannot return true for any of the records in the line table.
The easiest solution is to reverse the order of the deletion and delete from the line table first.
A slightly better solution is to use a multi-table delete syntax a delete the matching records from all 3 tables with a single statement:
delete invoice, customer, line
from customer
left join invoice on customer.cus_code=invoice.cus_code
left join line on invoice.inv_number=line.inv_number
where customer.cus_code=cust_id
But, the best would be to use foreign keys with the on delete cascade option set and do not use stored proc at all.
However, in most countries you are not allowed to delete invoices just like that, so you should really check your business requirements before implementing such deletion operation.

Inserting running total

Problem to insert running total in MySQL transactional database. need your help for solutions and opinion. Table structure of my table is,
create table `wtacct` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ACCOUNT_NO` varchar(16),
`AMOUNT` float(16,2),
`BALANCE` float(16,2)
);
[Please note other fields have been removed to make it simple example]
I am doing Transaction as,
Dr 10 USD from account 1001 and
Cr 10 USD to account 2002
Insert query
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, 100), ('', 2002, 10, 5000);
I want the Balance as,
BALANCE of Account no 1001 = Last transaction Balance of account 1001 - 10.
My solutions and limitations
Solution 1
In insert statement put sub query in balance field:
select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
Limitation: Mysql does not support same table select query (wtacct) where inserting the data (wtacct).
Solution 2
Using insert into select statement
insert into wtacct select '' ID, 1001 ACCOUNT_NO, -10 AMOUNT, (BALANCE-10) BALANCE where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
Limitation: For first transaction there is no record in wtacct for the account 1001 so select query will not return any record for first transaction.
Solution 3
Taking balance in variable and use it in insert statement.
select #balance1001 :=balance from wtacct
where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)
select #balance2002 :=balance from wtacct
where account_no=2002 and id in(select max(id) from wtacct where account_no=2002)
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, #balance1001-10), ('', 2002, 10, #balance2002+10);
Limitation: there is a chance to be change the balance in time between select and insert query execution. also its costly, 3 query execution required.
Solution 4
Insert and then update Balance
INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE)
VALUES ('', 1001, -10, 0);
UPDATE wtacct set balance = (ifnull(Select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where id <last_insert_id() and account_no=1001),0) -10)
where id =last_insert_id() and account_no=1001
........
Limitation: query is costly. its required 4 (two insert and 2 update) query execution. note last_insert_id() is php function
Solution 5
Using a trigger on insert statement. In the trigger, the balance will be updated calculating last transaction value and insert amount.
Limitation: Trigger not support transaction behavior and may fail.
Please give your solution and opinion on the above solutions. Please note in the above example their may be some syntax error/error. Please ignore them.
A big limitation I didn't see listed is a potential race condition, where two rows are being inserted into the table at the same time. There's a chance that the two inserts will both get the current "balance" from the same previous row.
One question: do you also have a separate "current balance" table that keeps a single value of the current "balance" for each account? Or are you only relying on the "balance" from the previous transaction.
Personally, I would track the current balance on a separate "account balance" table. And I would use BEFORE INSERT/UPDATE triggers to maintain the value in that row, and use that to return the current balance for the account.
For example, I would define a trigger like this which gets fired when a row is inserted into `wtacct` table:
CREATE TRIGGER wtacct_bi
BEFORE INSERT ON wtacct
FOR EACH ROW
BEGIN
IF NEW.amount IS NULL THEN
SET NEW.amount = 0;
END IF
;
UPDATE acct a
SET a.balance = (#new_balance := a.balance + NEW.amount)
WHERE a.account_no = NEW.account_no
;
SET NEW.balance = #new_balance
;
END$$
The setup for that trigger...
CREATE TABLE acct
( account_no VARCHAR(16) NOT NULL PRIMARY KEY
, balance DECIMAL(20,2) NOT NULL DEFAULT 0
) ENGINE=InnoDB
;
CREATE TABLE wtacct
( id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT
, account_no VARCHAR(16) NOT NULL COMMENT 'FK ref acct.account_no'
, amount DECIMAL(20,2) NOT NULL
, balance DECIMAL(20,2) NOT NULL
, FOREIGN KEY FK_wtacct_acct (account_no) REFERENCES acct (account_no)
ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB
;
My reason for using a separate "current balance" table is that there is only one row for the given account_no, and that row retains the current balance of the account.
The UPDATE statement in the trigger should obtain an exclusive lock on the row being updated. And that exclusive lock prevents any other UPDATE statement from simultaneously updating the same row. The execution of the UPDATE statement will add the `amount` from the current transaction row being inserted to the current balance.
If we were using Oracle or PostgreSQL, we could use a RETURNING clause to get the value that was assigned to the \'balance\' column.
In MySQL we can do a wonky workaround, using a user-defined variable. The new value we are going to assign to the column is first assigned to the user_defined variable, and then that is assigned to the column.
And we can assign the value of the user-defined variable to the `balance` column of the row being inserted into `wtacct`.
The purpose of this approach is to make the retrieval and update of the current balance in a single statement, to avoid any race conditions.
The UPDATE statement locates the row, obtains an exclusive (X) lock on the row, retrieves the current balance (value from the \'balance\' column), calculates the new current balance, and assigns it back to the \'balance\' column. Then continues to hold the lock until the transaction completes.
Once the trigger completes, the INSERT statement (which initially fired the trigger) proceeds, attempting to insert the new row into `wtacct`. If that fails, then all of the changes made by the INSERT statement and execution of the trigger are rolled back, keeping everything consistent.
Once a COMMIT or ROLLBACK is issued by the session, the exclusive (X) lock held on the row(s) in `acct` are released, and other sessions can obtain locks on that row in `acct`.
I have done it using Store Procedure for MySql
CREATE DEFINER=`root`#`%` PROCEDURE `example_add`(IN dr Int, IN cr Int)
BEGIN
DECLARE LID int;
Declare Balance decimal(16,2);
INSERT INTO example (Debit,Credit)
VALUES (dr, cr);
SET LID = LAST_INSERT_ID();
SET Balance = (select SUM(Debit) - SUM(Credit) as Balance from example);
UPDATE Example SET Balance = Balance WHERE ID = LID;
END
Use it example_add(10,0) or example_add(0,15) then select and see the result.

double select within a insert sql

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