Use ResultSet of SELECT...FOR UPDATE in MySQL Stored Procedure - mysql

I'm using MySql 5.7 trying to create a stored procedure that will update a set of rows in a transaction and return the rows that were updated. After creating the locks, I then update those rows, but can't figure out how to use the ids returned from the SELECT...FOR UPDATE statement. Instead I have to scan the table again in the update statement looking for the rows I just locked. Here's an example of my procedure.
DELIMITER //
CREATE PROCEDURE cleanUp()
BEGIN
START TRANSACTION;
SELECT * FROM t WHERE t.state = 'foobar' AND t.value < 10 FOR UPDATE;
UPDATE t SET t.state = 'fizzbuzz' WHERE t.state = 'foobar' AND t.value < 10;
COMMIT;
END //
I'd prefer to not have to scan the table twice for t.state = 'foobar' AND t.value < 10. I'd also like to guarantee that I only update the rows I just locked, not other rows that might have been changed to meet that criteria mid-transaction.
Is there a way to use the results of SELECT..FOR UPDATE in the UPDATE statement so I can update rows by id instead?
Note: I've tried loading the results into a temp table and using a cursor and both do not work.

MySQL can't declared variable as table, so you need o write the select in a FROM clause, which can for example do as IN clause like the query below
CREATE TABLe t(id int, state varchar(10), value int)
INSERT INTO t VALUEs(1,'foobar',1),(2,'foobar',1),(3,'foobar',1)
CREATE PROCEDURE cleanUp()
BEGIN
START TRANSACTION;
UPDATE t SET t.state = 'fizzbuzz' WHERE id IN (SELECT id FROM (SELECT id FROM t WHERE t.state = 'foobar' AND t.value < 10 FOR UPDATE) t1 );
COMMIT;
END
CALL cleanUp()
SELECT * FROM t
id | state | value
-: | :------- | ----:
1 | fizzbuzz | 1
2 | fizzbuzz | 1
3 | fizzbuzz | 1
db<>fiddle here

Related

Does Multiple Triggers for the Same Event & Action Time Impact OLD and NEW?

Let's say I have two BEFORE UPDATE triggers on the same table. We'll call them trigger A and trigger B. Order is enforced, so A will always be executed first, and B will always be executed second.
Will OLD and NEW reference the same values in the body of both A and B?
Or will the NEW values of A become the OLD values of B? Meaning B is referencing a new UPDATE statement, which is a product of A?
The OLD values reference the row before the UPDATE. That is, before any of the triggers execute. These never change during the triggers.
The NEW values reference the row with values you mean to change.
One trigger may modifies the NEW values. Then the subsequent trigger will see the modified values, still in the NEW row.
Demo:
mysql> create table mytable (id serial primary key, x int);
mysql> insert into mytable set x = 1;
mysql> delimiter $$
mysql> create trigger t1 before update on mytable
for each row begin set NEW.x = NEW.x + 1; end$$
mysql> create trigger t2 before update on mytable
for each row follows t1 begin set #x_old = OLD.x; set #x_new = NEW.x; end$$
mysql> delimiter ;
mysql> update mytable set x = 10;
mysql> select #x_old, #x_new;
+--------+--------+
| #x_old | #x_new |
+--------+--------+
| 1 | 11 |
+--------+--------+

MySQL Get row data from Update query with LIMIT via stored procedure

I have a query which updates multiple rows in a table with a variable LIMIT. I need to get data from the updated rows so I know which exact rows got affected. I wrote this simple procedure:
DELIMITER $$
CREATE PROCEDURE select_update(IN myId INT, IN myAttr VARCHAR(10), IN myAmount MEDIUMINT)
begin
SELECT data FROM mytable WHERE id IS NULL AND attr = myAttr LIMIT myAmount;
UPDATE mytable SET id = myId WHERE id IS NULL AND attr = myAttr LIMIT myAmount;
end$$
DELIMITER ;
Will this SELECT statement always return the exact same rows that the UPDATE statement affects? Is it possible for another user to execute a query while this procedure is running and thus to possibly change the affected rows between the SELECT and UPDATE?
Create a temporary table to hold the primary keys of the rows to be updated.
CREATE PROCEDURE select_update(IN myId INT, IN myAttr VARCHAR(10), IN myAmount MEDIUMINT)
begin
CREATE TEMPORARY TABLE temp_mytable AS
SELECT pk FROM mytable WHERE id IS NULL AND attr = myAttr LIMIT myamnt;
UPDATE mytable JOIN temp_mytable USING (pk)
SET mytable.id = myId;
SELECT mytable.data
FROM mytable JOIN temp_mytable USING (pk);
end$$

Error writing trigger statement

I would like to limit the size of a table to X rows (I'll use 5 for example). When the limit is reached, I want to copy the oldest row to another table, then delete it. I currently have:
CREATE TRIGGER LimitRows BEFORE INSERT ON MyTable
FOR EACH ROW BEGIN
IF (SELECT COUNT(*) FROM MyTable) >= 5 THEN
INSERT INTO HistoryTable
SELECT *
FROM MyTable A
WHERE vhID = A.min(vhID);
DELETE FROM MyTable
WHERE vhID = min(vhID);
END IF;
END;
Currently, I get the error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 8
How do I write this trigger correctly? Also, how can I modify to cut the table down to 5 rows if it starts out at something like 100 rows?
You need to change the delimiter first
delimiter |
CREATE TRIGGER LimitRows BEFORE INSERT ON MyTable
FOR EACH ROW BEGIN
IF (SELECT COUNT(*) FROM MyTable) >= 5 THEN
INSERT INTO HistoryTable
SELECT *
FROM MyTable A
WHERE vhID = A.min(vhID);
DELETE FROM MyTable
WHERE vhID = min(vhID);
END IF;
END
|
delimiter ;
Otherwise the trigger definition would end at the first ; which would make it incomplete.

UPDATE or INSERT on the same query without unique value

Hello Im trying to find a way the UPDATE or INSERT data with one query.
I have a table with like this:
+------+-------------+
| User | action_type |
+------+-------------+
| Jon | 1 |
| Kate | 2 |
| Jon | 4 |
+------+-------------+
I want to insert new value for Jon only if there is no values of Jon.
If I have values of Jon I want to update all the rows with Jon.
Ive read about INSERT ... ON DUPLICATE KEY UPDATE but I dont have unique values.
Thanks for helping.
you can count the entries for jon. If exists update else insert.
if you want to implement only using sql you can use an stored procedure
something like this:
CREATE PROCEDURE insertorupdate (IN name VARCHAR(20))
BEGIN
DECLARE numJon INT;
SELECT COUNT(*) INTO numJon FROM table WHERE User=name;
IF numJon > 0 THEN
// UPDATE ;
ELSE
// INSERT
END IF;
END
Then you can call you Store Procedure:
CALL insertorupdate('John');
If you can do it from your app you can call the same thing but separatelly. Do a select count, test if count if grater than 0 and then do the insert or the update on DB
Don't count like in user468891's answer. It might become a performance issue. Instead just check if an entry exists. As soon as an entry is found, the query returns true, count continues to find all records.
DELIMITER $$
CREATE PROCEDURE insertorupdate (IN name VARCHAR(20))
BEGIN
IF (EXISTS(SELECT 1 FROM table WHERE User = name)) THEN
// UPDATE...
ELSE
// INSERT...
END IF;
END $$
DELIMITER ;

How can I select out parameters in a MySQL procedure as table columns?

Environment: MySQL 5.1, Linux
I have a stored procedure that computes several values from a single input value. The values are returned as OUT parameters. I would like to call this procedure for every row set and have the values appear as columns in the result set. The values have a complex relationship such that distinct functions for each value is not easily constructed.
The question: How can I get OUT parameters to show up as columns in a table?
Here's what I have so far:
DELIMITER $_$
DROP PROCEDURE IF EXISTS in_out;
CREATE PROCEDURE in_out (
IN s TEXT,
OUT op TEXT,
OUT opn INT,
OUT opd TEXT,
OUT len INT
)
BEGIN
SET op = 'del';
SET opn = 1;
SET opd = substr(s,4);
SET len = LENGTH(SUBSTR(s,4));
END
$_$
DELIMITER ;
Then:
mysql> call in_out('delACT',#op,#opn,#opd,#len);
Query OK, 0 rows affected (0.00 sec)
mysql> select #op,#opn,#opd,#len;
+------+------+------+------+
| #op | #opn | #opd | #len |
+------+------+------+------+
| snv | 1 | ACT | 3 |
+------+------+------+------+
1 row in set (0.00 sec)
So far so good, but I can't figure out how to call this procedure for every row and return the results in the result set. I want is something like this:
dream> select mycol,in_out(mycol) from mytable
+---------+------+------+------+------+
| mycol | #op | #opn | #opd | #len |
+---------+------+------+------+------+
| delACT | del | 1 | ACT | 3 |
+---------+------+------+------+------+
Thanks!
You confuse the stored procedures and stored functions:
stored function will be return a value, the results can be used in
expressions (like COS() and other mysql built-in functions).
stored procedure need use CALL , is an independent operation, can not
be used in expressions.
If you want to "select mycol,in_out(mycol) from mytable",you must:
CREATE FUNCTION in_out( ...
This appears to be a trick question: one can't create table relations out of function/procedure results in MySQL. I ended up refactoring into separate functions (as suggested by MichaƂ). I had been hoping for a MySQL equivalent to PostgreSQL's table functions (http://goo.gl/77QVE).
I'd recommend to prepare data in stored procedure for each possible value in:
select distinct mycol
from mytable
where <... condition that you would use anyway in final result ...>
where mycol is your parameter for stored procedure save it to temporary table and than join to this values.
-- way the temp table may look in your sp
create temporary table tmptable (
mycol text
op text,
opn int,
opd text,
len int
)
after that use join:
select m.mycol, t.op, t.opn, t.opd, t.len
from mytable m
join tmptable t on m.mycol = t.mycol
where <... condition that you would use anyway in final result ...>
Bit different question, are you absolutely sure that there is no different way to process your final result than using a stored procedure?