Table Values() Constructor for Updating Multiple Rows - mysql

I just moved an app from a local instance where I used Postgres to a Google Compute Engine virtual instance where I'm using Google Cloud SQL, built on MySQL.
Ever since the move, this SQL query is no longer working:
"UPDATE events e SET e.photographers = up.photographers FROM (VALUES "+ value_pairs +") AS up(id, photographers) WHERE up.id = e.id"
where value_pairs = (1,2)
Here's the exact error I'm seeing:
error running query { [Error: ER_PARSE_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 'FROM (VALUES (1,2)) AS up(id, photographers) WHERE up.id = e.id' at line 1]
The output seems correct ... anyone see what I'm doing wrong?
Update / Solution
Should have clarified, value_pairs can grow to be multiple values, i.e. ((4,2), (6,1), (10,3), ...)
Due to the relatively simple nature of this query, I ended up going with an INSERT query using ON DUPLICATE KEY clause:
("INSERT INTO events (id,photographers) VALUES "+ value_pairs + "ON DUPLICATE KEY UPDATE photographers=VALUES(photographers)"

You should be able to replace (VALUES "+ value_pairs +") AS up(id, photographers) with something like this:
mysql> (SELECT 1 AS photographers, 2 AS id) UNION (SELECT 3, 4) UNION (SELECT 5, 6);
+---------------+----+
| photographers | id |
+---------------+----+
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
+---------------+----+
3 rows in set (0.00 sec)
mysql>

You could create a temporary table to run your query in MySQL:
create temporary table src ...
insert into src values ...
And then run your update using src. It's not as pretty as the anonymous temporary table that you're currently using, but it'll work.
Another approach is to use a giant case statement:
update events
set photographers = case id
when 1 then 2
...
end
where id in (1, ...)

Use simple update:
UPDATE events
SET photographers = 2
WHERE id = 1;
MySql doesn't support the non-standard syntax of PostgreSql.
If multiple values are needed, a multitable update syntax might be used:
http://dev.mysql.com/doc/refman/5.7/en/update.html
UPDATE [LOW_PRIORITY] [IGNORE] table_references
SET col_name1={expr1|DEFAULT} [, col_name2={expr2|DEFAULT}] ...
[WHERE where_condition]
One option is to insert values to a temporary table - as #Denis wrote - then perform an update:
UPDATE table t, temporary_table tmp
SET t.photographers = tmp.photographers
WHERE t.id = tmp.id
Another option is - as #Razvan Musaloiu-E. wrote - build a dynamic query with union:
UPDATE table t, (
SELECT 1 AS id, 2 AS photographers
UNION
SELECT 5, 7
UNION
SELECT 15, 67
UNION
.....
.....
.....
.....
UNION
SELECT 234, 567
) AS tmp
SET t.photographers = tmp.photographers
WHERE t.id = tmp.id

Related

Mysql Error 1093

I am trying to insert a new record in a table Points using data queried from the very same table but I get the following error
#1093 - You can't specify target table 'Points' for update in FROM clause
Here is query:
insert into Points (`userID`,`restaurantID`,`franchiseID`,`points`)
values (16,5,1,((SELECT
FORMAT(SUM(itemPrice)/10,0)
FROM
Orders left join Menu using(menuID)
WHERE
logID = 701)+
(SELECT
SUM(points)
FROM
Points
WHERE `userID` = 16 AND `franchiseID`=1)))
I am not so skilled in MySQL so I was wondering if there was a workaround this problem I have. Thanks in advance
You can store the temporary result in a user variable:
SET #sum_val := (SELECT
FORMAT(SUM(itemPrice)/10,0)
FROM
Orders left join Menu using(menuID)
WHERE
logID = 701)+
(SELECT
SUM(points)
FROM
Points
WHERE `userID` = 16 AND `franchiseID`=1));
insert into Points (`userID`,`restaurantID`,`franchiseID`,`points`)
values (16,5,1,#sum_val);
Looks like MySQL doesn't let you do that. I set up a contrived example and got your same results:
create table blah (a bigint not null primary key auto_increment, b varchar(6));
insert into blah (b) values ('junk');
select * from blah;
+---+------+
| a | b |
+---+------+
| 1 | junk |
+---+------+
insert into blah (b) values ((select b from blah where a = 1));
ERROR 1093 (HY000): You can't specify target table 'blah' for update in FROM clause
Sorry, buddy! From the MySQL docs (http://dev.mysql.com/doc/refman/5.7/en/subqueries.html):
In MySQL, you cannot modify a table and select from the same table in a subquery. This applies to statements such as DELETE, INSERT, REPLACE, UPDATE, and (because subqueries can be used in the SET clause) LOAD DATA INFILE.
Sasha Pachev's answer is a good suggestion, but Giorgos Betsos suggested an elegant workaround in a comment on this answer:
insert into blah (b) values ((select b from (select b from blah where a = 1) as t));
select * from blah;
+---+------+
| a | b |
+---+------+
| 1 | junk |
| 2 | junk |
+---+------+
By adding another layer of subquery with an alias, it looks like MySQL creates a temporary table under the covers, thereby working around the limitation. (Unfortunately my installed version of MySQL will not EXPLAIN inserts, but EXPLAINing the nested subquery shows a derived table)

Inconsistent/Strange behavior of MySQL when using the group by clause and count() function

Due to a bug(?) in MySQL the COUNT() function along with the GROUP BY clause can cause MySQL to leak out db details like the following -
mysql> select count(*), floor(rand()*2)x from users group by x;
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'
Sensitive details can be revealed here with a well crafted query. This
is unexpected behavior, maybe a bug?
mysql> select count(*), floor(rand()*2)x from users group by x;
+----------+---+
| count(*) | x |
+----------+---+
| 8 | 0 |
| 5 | 1 |
+----------+---+
2 rows in set (0.00 sec) <-- Sometimes the query runs without any errors(Expected behavior)
Does anyone know what exactly causes the MySQL error.
The test bed that I am using is this excellent resource - https://github.com/Audi-1/sqli-labs
This looks to be a reported (and old!) bug: http://bugs.mysql.com/bug.php?id=58081
Description: A GROUP BY query returns this error under certain
circumstances:
Duplicate entry '107374182410737418241' for key 'group_key'
'group_key' is not a real column name in the table. It looks like a
name for the grouping column in the temporary table.
How to repeat: set names latin1; drop table if exists t1; create table
t1(a int) engine=myisam; insert into t1 values (0),(0),(1),(0),(0);
select count(*) from t1, t1 t2 group by insert('', t2.a,
t1.a,(##global.max_binlog_size));
ERROR 1062 (23000): Duplicate entry '107374182410737418241' for key
'group_key'
Comments indicate a suggested work around is to increase the available heap and temp table size:
The workaround i found is to increase the size of the tmp_table:
SET SESSION max_heap_table_size=536870912; SET SESSION
tmp_table_size=536870912;
now my request work !
Or to check your available disk space

Multiple Insert statements into MySQL Case

I'm trying to select a value (id) from a MySQL table and use it in a update statement - all in a MySQL query. The select is composed of 2 parts: if the id exists, it is returned; if not, 2 inserts are done and the id is returned.
I have the following query:
SELECT
(CASE a.id WHEN '' THEN (
DELIMITER //
INSERT INTO xxxx (item_id, date_created, date_modified) VALUES (3313, NOW(), NOW())//
INSERT INTO yyyy (item_id, locale_id, value, date_created, date_modified) VALUES(LAST_INSERT_ID(), 2, TRIM(SUBSTRING_INDEX('some text: 250 x 46 x 584', ':', 1)), NOW(), NOW())//
SELECT c.id FROM xxxx c JOIN yyyy d WHERE c.item_id=3313 AND d.value='some text' LIMIT 1
) ELSE a.id END
) AS trans_ref_id
FROM xxxx a JOIN yyyy b ON a.id = b.item_id
WHERE b.value='some text'
When i run it, i get the following error:
SQL Error (1064): 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 ')
ELSE a.id
END
)
as trans_ref_id
FROM xxxx' at line 2
Am I having the wrong approach here? Where is this error coming from?
You cannot do this with SQL. You need to use cursors. The manual has an example for you to look at. Probably best to put this in a stored procedure.
Your creative attempt does not conform to the SELECT+INSERT syntax, which is:
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]

Mysql: Create inline table within select statement?

Is there a way in MySql to create an inline table to use for join?
Something like:
SELECT LONG [1,2,3] as ID, VARCHAR(1) ['a','b','c'] as CONTENT
that would output
| ID | CONTENT |
| LONG | VARCHAR(1)|
+------+-----------+
| 1 | 'a' |
| 2 | 'b' |
| 3 | 'c' |
and that I could use in a join like this:
SELECT
MyTable.*,
MyInlineTable.CONTENT
FROM
MyTable
JOIN
(SELECT LONG [1,2,3] as ID, VARCHAR(1) ['a','b','c'] as CONTENT MyInlineTable)
ON MyTable.ID = MyInlineTable.ID
I realize that I can do
SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c'
But that seems pretty evil
I don't want to do a stored procedure because potentially a,b,c can change at every query and the size of the data as well. Also a stored procedure needs to be saved in the database, and I don't want to have to modify the database just for that.
View is the same thing.
What I am really looking for is something that does SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c' with a nicer syntax.
The only ways i can remember now is using UNION or creating a TEMPORARY TABLE and inserting those values into it. Does it suit you?
TEMPORARY_TABLE (tested and it works):
Creation:
CREATE TEMPORARY TABLE MyInlineTable (id LONG, content VARCHAR(1) );
INSERT INTO MyInlineTable VALUES
(1, 'a'),
(2, 'b'),
(3, 'c');
Usage:
SELECT
MyTable.*,
MyInlineTable.CONTENT
FROM
MyTable
JOIN
SELECT * FROM MyInlineTable;
ON MyTable.ID = MyInlineTable.ID
TEMPORARY_TABLES lifetime (reference):
Temporary tables are automatically dropped when they go out of scope, unless they have already been explicitly dropped using DROP TABLE:
.
All other local temporary tables are dropped automatically at the end of the current session.
.
Global temporary tables are automatically dropped when the session that created the table ends and all other tasks have stopped referencing them. The association between a task and a table is maintained only for the life of a single Transact-SQL statement. This means that a global temporary table is dropped at the completion of the last Transact-SQL statement that was actively referencing the table when the creating session ended.`
What I am really looking for is something that does SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c' with a nicer syntax.
Yes, it is possible with ROW CONSTRUCTOR introduced in MySQL 8.0.19:
VALUES ROW (1,'a'), ROW(2,'b'), ROW(3,'c')
and with JOIN:
SELECT *
FROM tab
JOIN (VALUES ROW (1,'a'), ROW(2,'b'), ROW(3,'c') ) sub(id, content)
ON tab.id = sub.id;
db<>fiddle demo
Yes. Do with stored procedures or views.

Find MySQL row identified by number in a warning message

The MySQL "show warnings" output identifies problematic rows by number. What's the best way to quickly see all the data for such a row?
For example, after running an update statement the result indicates "1 warning" and running show warnings gives a message like this: "Data truncated for column 'person' at row 65278". How can I select exactly that row?
Here is a concrete example exploring the limit solution:
create table test1 (
id mediumint,
value varchar(2)
);
insert into test1 (id, value) values
(11, "a"),
(12, "b"),
(13, "c"),
(14, "d"),
(15, "ee"),
(16, "ff");
update test1 set value = concat(value, "X") where id % 2 = 1;
show warnings;
That results in this warning output:
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1265 | Data truncated for column 'value' at row 5 |
+---------+------+--------------------------------------------+
To get just that row 5 I can do this:
select * from test1 limit 4,1;
resulting in this:
+------+-------+
| id | value |
+------+-------+
| 15 | ee |
+------+-------+
So it seems that the limit offset (4) must be one less than the row number, and the row number given in the warning is for the source table of the update without regard to the where clause.
As far as I'm aware, the only way to select those rows is to just SELECT them using the criteria from your original UPDATE query:
mysql> UPDATE foo SET bar = "bar" WHERE baz = "baz";
mysql> SHOW WARNINGS;
...
Message: Data truncated for column 'X' at row 420
...
mysql> SELECT * FROM foo WHERE baz = "baz" LIMIT 420,1;
Obviously, this doesn't work if you've modified one or more of the columns that were part of your original query.
LIMIT x,y returns y number of rows after row x, based on the order of the resultset from your select query. However, if you look closely at what I just said, you'll notice that without an ORDER BY clause, you've got no way to guarantee the position of the row(s) you're trying to get.
You might want to add an autoincrement field to your insert or perhaps a trigger that fires before each insert, then use that index to ensure the order of the results to limit by.
Not to raise this question from the dead, but I'll add one more method of finding the source of warning data that can be helpful in certain cases.
If you are importing a complete dataset from one table into another and receive a truncation warning on a specific field you can run a query joining the two tables on an ID value and then filter by records where the field in question doesn't match. Obviously this only will work if you are importing from a separate table and still have access to the unmodified source table.
So if the field in question is testfield and your import query looks like this:
INSERT INTO newtable (
id,
field1,
field2,
testfield
)
SELECT
id,
field1,
field2,
testfield
FROM oldtable;
The diagnostic query could look something like this:
SELECT newtable.testfield, oldtable.testfield
FROM newtable
INNER JOIN oldtable ON newtable.id = oldtable.id
WHERE newtable.testfield != oldtable.testfield;
This has the added advantage that the order of records in either table doesn't matter.