403

I have a simple mysql table:

CREATE TABLE IF NOT EXISTS `pers` (
  `persID` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(35) NOT NULL,
  `gehalt` int(11) NOT NULL,
  `chefID` int(11) DEFAULT NULL,
  PRIMARY KEY (`persID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

INSERT INTO `pers` (`persID`, `name`, `gehalt`, `chefID`) VALUES
(1, 'blb', 1000, 3),
(2, 'as', 1000, 3),
(3, 'chef', 1040, NULL);

I tried to run following update, but I get only the error 1093:

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE (P.chefID IS NOT NULL 
OR gehalt < 
(SELECT (
    SELECT MAX(gehalt * 1.05) 
    FROM pers MA 
    WHERE MA.chefID = MA.chefID) 
    AS _pers
))

I searched for the error and found from mysql following page http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html, but it doesn't help me.

What shall I do to correct the sql query?

OMG Ponies
  • 300,587
  • 73
  • 490
  • 482
CSchulz
  • 10,102
  • 9
  • 54
  • 107
  • 1
    Possible duplicate of [MySQL Error 1093 - Can't specify target table for update in FROM clause](http://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause) – Steve Chambers Mar 16 '17 at 13:12

11 Answers11

817

The problem is that MySQL, for whatever inane reason, doesn't allow you to write queries like this:

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)

That is, if you're doing an UPDATE/INSERT/DELETE on a table, you can't reference that table in an inner query (you can however reference a field from that outer table...)


The solution is to replace the instance of myTable in the sub-query with (SELECT * FROM myTable), like this

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)

This apparently causes the necessary fields to be implicitly copied into a temporary table, so it's allowed.

I found this solution here. A note from that article:

You don’t want to just SELECT * FROM table in the subquery in real life; I just wanted to keep the examples simple. In reality, you should only be selecting the columns you need in that innermost query, and adding a good WHERE clause to limit the results, too.

BlueRaja - Danny Pflughoeft
  • 75,675
  • 28
  • 177
  • 259
  • 11
    I don't think the reason is inane. Think about the semantics. Either MySQL has to keep a copy of the table before the update started, or the inner query might use data that has already been updated by the query as it's in progress. Neither of these side-effects is necessarily desirable, so the safest bet is to force you to specify what will happen using an extra table. – siride Mar 09 '13 at 20:54
  • 43
    @siride: Other databases, such as MSSQL or Oracle, don't have this arbitrary restriction – BlueRaja - Danny Pflughoeft Mar 13 '13 at 19:14
  • 4
    @BlueRaja-DannyPflughoeft: it's not arbitrary. It's reasonable design decision based on the costs of the alternatives. The other DB systems chose to deal with those costs anyway. But those systems don't, e.g., let you include non-aggregated columns in SELECT lists when you use GROUP BY, and MySQL does. I'd argue that MySQL is in the wrong here, and I might say the same of the other DBMSs for UPDATE statements. – siride Mar 13 '13 at 20:50
  • 1
    You'll have to talk to @CSchulz about that :) – BlueRaja - Danny Pflughoeft Oct 18 '13 at 22:22
  • OMG! YOU SAVED MY DAY! THANK YOU SO MUCH! This same thing happened to me on a DELETE query in a stored procedure. – Ariel Jan 31 '14 at 05:58
  • Answer by @DarkSide is better than this and should be marked as the accepted answer - he's doing the same as this but avoiding the hassle of doing `INNER JOIN`s, which are not needed and also were not in the original question. – Pere Jan 15 '15 at 15:43
  • 37
    @siride From a relational algebra point of view, `T` and `(SELECT * FROM T)` are completely equivalent. They are the same relation. Therefore this is an arbitrary, inane restriction. More specifically, it's a workaround to coerce MySQL into doing something that it clearly can do, but for some reason it cannot parse in its simpler form. – Tobia Jan 19 '15 at 14:22
  • @Tobia: you missed the explanation for why the second version works and it has nothing to do with academic relational algebra and everything to do with implementation (which is required in the real world where things can't just be theorized to happen but must actually happen). If there's a temporary table, then the inconsistency issues are avoided. MySQL could choose to always use a temporary table, or to keep track of which rows have been updated to avoid including an updated value in the subquery, but that's extra complexity and the designers chose to avoid it. It's a reasonable trade off. – siride Jan 19 '15 at 15:52
  • 2
    @Tobia: for the record, SQL Server correctly spools the table data in all cases, so it does not have this limitation. – siride Jan 19 '15 at 15:53
  • I can confirm that this works as expected and is the most sane way to solve the issue. Thanks! – Dr. Gianluigi Zane Zanettini Jul 13 '15 at 20:58
  • when running h2 databases, even in mySQL compatability, they won't pick up on this issue. – EdgeCaseBerg Aug 11 '15 at 16:35
  • 3
    Beware, that from [MySQL 5.7.6 on](http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html), the optimizer may optimize the sub-query away and still give you the error, unless you `SET optimizer_switch = 'derived_merge=off';` :-( – Peter V. Mørch Nov 06 '15 at 15:35
  • 2
    For no reason than it's quite the coincidence, I was sitting on this page reading your answer when you edited the answer at 2016-02-18 20:18:50Z. It hasn't been edited since 2013. Whoa. – Roadrunner-EX Feb 18 '16 at 20:35
  • 4
    In my case the accepted solution didn't work because my table was simply too large. The query never completed. Apparently this is taking too many internal resources. Instead I created a View with the inner query and used it for the data selection, which worked absolutely fine. `DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);` Also I recommend running `OPTIMIZE TABLE t;` afterwards to reduce the size of the table. – CodeX Oct 13 '16 at 11:36
  • Will this be atomic? Between select and insert, you may have another query changing the value you selected and you may end up with strange results. You would need to read lock the table when doing this, no? – Evren Yurtesen Jun 29 '17 at 15:00
  • @EvrenYurtesen: No, it's a single query, which should be atomic in any transactional database _(though note that, notably, MySQL using MyISAM is not transactional. You should use InnoDB instead)_ – BlueRaja - Danny Pflughoeft Jun 29 '17 at 15:36
  • @BlueRaja-DannyPflughoeft Do you have link to documentation for this? Because the UPDATE query may be atomic, but if 2 threads run updates with subqueries, I am not convinced if the subqueries will not run simultaneously. – Evren Yurtesen Jun 29 '17 at 16:57
  • 1
    @EvrenYurtesen: See the documentation on [transaction isolation levels](https://dev.mysql.com/doc/refman/5.5/en/innodb-transaction-isolation-levels.html). If the DB could not handle that situation correctly, it would not satisfy the isolation-principle of [ACID](https://en.wikipedia.org/wiki/ACID). I think you're vastly underestimating how complex and powerful databases are. – BlueRaja - Danny Pflughoeft Jun 29 '17 at 18:30
  • Actually the documentation you pointed out says opposite. It says, by default mysql supports repeatable read and once the transaction starts, it won't see the updates made by another thread. So, you will have a problem if you start two update statements at the same time, which updates the columns selected within subquery. Because you expect one query to update the fields, second query to use updated fields. But they will both use same old fields because they both started transaction at the same time. – Evren Yurtesen Jun 29 '17 at 21:34
  • This results in an absolutely incredible performance much perfect wow (thank you for saving my day btw) – Yonn Trimoreau Nov 07 '17 at 09:25
  • @CodeX solution can be creating a VIEW – jave.web Feb 12 '18 at 18:52
  • The problem is that if the table is big, doing a (select * from table) versus the regular join and WHERE clause, this will return the entire table, and THEN apply your filter. This is a HUGE deal when making a big application. Under MSSQL Server, I would have no problems. But I'm on MySQL now. This makes me really mad... – Mathieu Turcotte Sep 06 '18 at 15:19
  • Thanks for explaining why a copy of the table is need. However, the query performance in mysql is very poor that it executes very slowly. – Louis55 Jan 11 '19 at 06:38
  • MariaDB doesn't have such restriction too - MySQL is really insane. – Michael Tsang Apr 27 '20 at 02:21
53

You can make this in three steps:

CREATE TABLE test2 AS
SELECT PersId 
FROM pers p
WHERE (
  chefID IS NOT NULL 
  OR gehalt < (
    SELECT MAX (
      gehalt * 1.05
    )
    FROM pers MA
    WHERE MA.chefID = p.chefID
  )
)

...

UPDATE pers P
SET P.gehalt = P.gehalt * 1.05
WHERE PersId
IN (
  SELECT PersId
  FROM test2
)
DROP TABLE test2;

or

UPDATE Pers P, (
  SELECT PersId
  FROM pers p
  WHERE (
   chefID IS NOT NULL 
   OR gehalt < (
     SELECT MAX (
       gehalt * 1.05
     )
     FROM pers MA
     WHERE MA.chefID = p.chefID
   )
 )
) t
SET P.gehalt = P.gehalt * 1.05
WHERE p.PersId = t.PersId
JJD
  • 44,755
  • 49
  • 183
  • 309
Michael Pakhantsov
  • 23,229
  • 5
  • 56
  • 58
  • 16
    Well yeah, most subqueries can be rewritten as multiple steps with `CREATE TABLE` statements - I hope the author was aware of this. However, is this the only solution? Or can the query be rewritten with subqueries or joins? And why (not) do that? – Konerak Dec 13 '10 at 13:49
  • I think you have a capitalization error in your second solution. Shouldn't `UPDATE Pers P` read `UPDATE pers P`? – ubiquibacon Dec 19 '12 at 19:26
  • 2
    Tried this solution and for a large number of entries in temporary/second table the query can be very slow; try to create temporary/second table with an index/primary key [see http://dev.mysql.com/doc/refman/5.1/en/create-table-select.html ] – Alex Feb 11 '13 at 09:25
  • As @Konerak states, this isn't really the best answer. The answer from BlueRaja below seems best to me. Upvotes seem to agree. – ShatyUT Jul 09 '14 at 19:47
  • @Konerak, Doesn't `CREATE TABLE AS SELECT` give horrible performance? – Pacerier Feb 24 '15 at 03:36
  • Pacerier, why are you asking me? :-) – Konerak Feb 24 '15 at 07:39
  • @Michael, Wouldn't `create table as select` give horrible performance? – Pacerier Mar 08 '15 at 23:58
27

In Mysql, you can not update one table by subquery the same table.

You can separate the query in two parts, or do

 UPDATE TABLE_A AS A
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SET field2 = ? 
Yuantao
  • 2,584
  • 1
  • 15
  • 17
  • 5
    `SELECT ... SET`? I've never heard about this. – Serge S. May 23 '12 at 19:24
  • @grisson Thanks for the clarification. Now I get why my IN clause doesn't work - I was targeting the same table. – Anthony Sep 04 '12 at 02:43
  • 2
    ...this doesn't seem to actually work. It's still giving me the same error. – BlueRaja - Danny Pflughoeft Jan 13 '13 at 00:05
  • 2
    this answer actually does the more correct and efficient thing, which is using `AS B` on the second reference to `TABLE_A`. the answer in the most-upvoted example could be simplified using `AS T` instead of the potentially inefficient `FROM (SELECT * FROM myTable) AS something`, which fortunately the query optimizer typically eliminates but might not always do so. – natbro Jun 17 '13 at 14:33
24

Make a temporary table (tempP) from a subquery

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE P.persID IN (
    SELECT tempP.tempId
    FROM (
        SELECT persID as tempId
        FROM pers P
        WHERE
            P.chefID IS NOT NULL OR gehalt < 
                (SELECT (
                    SELECT MAX(gehalt * 1.05) 
                    FROM pers MA 
                    WHERE MA.chefID = MA.chefID) 
                    AS _pers
                )
    ) AS tempP
)

I've introduced a separate name (alias) and give a new name to 'persID' column for temporary table

Budda
  • 16,990
  • 33
  • 113
  • 197
18

It's quite simple. For example, instead of writing:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);

you should write

INSERT INTO x (id, parent_id, code)
VALUES (
    NULL,
    (SELECT t.id FROM (SELECT id, code FROM x) t WHERE t.code='AAA'),
    'BBB'
);

or similar.

DarkSide
  • 3,456
  • 1
  • 23
  • 31
15

The Approach posted by BlueRaja is slow I modified it as I was using to delete duplicates from the table. In case it helps anyone with large tables Original Query

delete from table where id not in (select min(id) from table group by field 2)

This is taking more time:

DELETE FROM table where ID NOT IN(
  SELECT MIN(t.Id) from (select Id,field2 from table) AS t GROUP BY field2)

Faster Solution

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
  • 627
  • 5
  • 15
7

Just as reference, you can also use Mysql Variables to save temporary results, e.g.:

SET @v1 := (SELECT ... );
UPDATE ... SET ... WHERE x=@v1;

https://dev.mysql.com/doc/refman/5.7/en/user-variables.html

Filippo Mazza
  • 3,862
  • 3
  • 20
  • 23
  • 1
    this is good to know in general, but it doesn't work for updating/deleting multiple rows `ERROR 1242 (21000): Subquery returns more than 1 row` – lewis Aug 10 '20 at 12:58
4

MariaDB has lifted this starting from 10.3.x (both for DELETE and UPDATE):

UPDATE - Statements With the Same Source and Target

From MariaDB 10.3.2, UPDATE statements may have the same source and target.

Until MariaDB 10.3.1, the following UPDATE statement would not work:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);
  ERROR 1093 (HY000): Table 't1' is specified twice, 
  both as a target for 'UPDATE' and as a separate source for data

From MariaDB 10.3.2, the statement executes successfully:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);

DELETE - Same Source and Target Table

Until MariaDB 10.3.1, deleting from a table with the same source and target was not possible. From MariaDB 10.3.1, this is now possible. For example:

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);

DBFiddle MariaDB 10.2 - Error

DBFiddle MariaDB 10.3 - Success

Lukasz Szozda
  • 120,610
  • 18
  • 161
  • 197
3

If you are trying to read fieldA from tableA and save it on fieldB on the same table, when fieldc = fieldd you might want consider this.

UPDATE tableA,
    tableA AS tableA_1 
SET 
    tableA.fieldB= tableA_1.filedA
WHERE
    (((tableA.conditionFild) = 'condition')
        AND ((tableA.fieldc) = tableA_1.fieldd));

Above code copies the value from fieldA to fieldB when condition-field met your condition. this also works in ADO (e.g access )

source: tried myself

Krish
  • 5,754
  • 1
  • 12
  • 34
3

MySQL doesn't allow selecting from a table and update in the same table at the same time. But there is always a workaround :)

This doesn't work >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;

But this works >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
Hari Das
  • 7,849
  • 6
  • 46
  • 53
0

Other workarounds include using SELECT DISTINCT or LIMIT in the subquery, although these are not as explicit in their effect on materialization. this worked for me

as mentioned in MySql Doc

PITU
  • 143
  • 1
  • 1
  • 12