663

I have a table story_category in my database with corrupt entries. The next query returns the corrupt entries:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);

I tried to delete them executing:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category 
      INNER JOIN story_category ON category_id=category.id);

But I get the next error:

#1093 - You can't specify target table 'story_category' for update in FROM clause

How can I overcome this?

shA.t
  • 15,232
  • 5
  • 47
  • 95
Sergio del Amo
  • 71,609
  • 66
  • 148
  • 177

16 Answers16

797

Update: This answer covers the general error classification. For a more specific answer about how to best handle the OP's exact query, please see other answers to this question

In MySQL, you can't modify the same table which you use in the SELECT part.
This behaviour is documented at: http://dev.mysql.com/doc/refman/5.6/en/update.html

Maybe you can just join the table to itself

If the logic is simple enough to re-shape the query, lose the subquery and join the table to itself, employing appropriate selection criteria. This will cause MySQL to see the table as two different things, allowing destructive changes to go ahead.

UPDATE tbl AS a
INNER JOIN tbl AS b ON ....
SET a.col = b.col

Alternatively, try nesting the subquery deeper into a from clause ...

If you absolutely need the subquery, there's a workaround, but it's ugly for several reasons, including performance:

UPDATE tbl SET col = (
  SELECT ... FROM (SELECT.... FROM) AS x);

The nested subquery in the FROM clause creates an implicit temporary table, so it doesn't count as the same table you're updating.

... but watch out for the query optimiser

However, beware that from MySQL 5.7.6 and onward, the optimiser may optimise out the subquery, and still give you the error. Luckily, the optimizer_switch variable can be used to switch off this behaviour; although I couldn't recommend doing this as anything more than a short term fix, or for small one-off tasks.

SET optimizer_switch = 'derived_merge=off';

Thanks to Peter V. Mørch for this advice in the comments.

Example technique was from Baron Schwartz, originally published at Nabble, paraphrased and extended here.

Community
  • 1
  • 1
Cheekysoft
  • 32,898
  • 19
  • 70
  • 85
  • 1
    Upvoted this answer because I had to delete items and could not get info from another table, had to subquery from same table. Since this is what pops up on top while googling for the error I got this would be the best fit answer for me and a lot of people trying to update while subquerieing from the same table. – HMR Dec 12 '12 at 03:36
  • 2
    @Cheekysoft, Why not save the values into variables instead? – Pacerier Feb 24 '15 at 03:47
  • 1
    @PeterV.Mørch Following http://mysqlserverteam.com/derived-tables-in-mysql-5-7, in case certain operations are carried out, merging cannot happen. E.g. provide the derived dummy table with a LIMIT (to inifity) and the error will never occur. That's pretty hackish though and there is still the risk that future versions of MySQL will support merging queries with LIMIT after all. – user2180613 Aug 18 '16 at 18:59
  • 1
    Can you, please, provide a full example about this workaround? UPDATE tbl SET col = ( SELECT ... FROM (SELECT.... FROM) AS x); i'm still getting errors – JoelBonetR Sep 10 '18 at 09:56
  • INNER JOIN Work for me :) – hassanrazadev Jan 30 '20 at 11:16
  • @Cheekysoft could you please help me to find out where I'm doing wrong?

    "update tmp_beneficiary as a inner join tmp_beneficiary b on a.id = b.id set a.photo = b.photo where a.id in (5, 13, 84) and b.id = 1;"
    – sh6210 Jan 29 '21 at 06:41
376

NexusRex provided a very good solution for deleting with join from the same table.

If you do this:

DELETE FROM story_category
WHERE category_id NOT IN (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
)

you are going to get an error.

But if you wrap the condition in one more select:

DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
    ) AS c
)

it would do the right thing!!

Explanation: The query optimizer does a derived merge optimization for the first query (which causes it to fail with the error), but the second query doesn't qualify for the derived merge optimization. Hence the optimizer is forced to execute the subquery first.

Positive Navid
  • 1,468
  • 2
  • 21
  • 34
Yehor
  • 4,323
  • 3
  • 16
  • 26
  • 8
    Maybe it's because I'm in a bind today, but this was the easiest answer, even if it's maybe not the "best" one. – Tyler V. Sep 05 '14 at 23:13
  • 4
    This worked great, thanks! So what's the logic here? If it's nested one more level then it will be executed before the outer part? And if it's not nested then mySQL tries to run it after the delete has a lock on the table? – Flat Cat Dec 14 '14 at 16:40
  • 59
    This error and solution make no logical sense...but it works. Sometimes I wonder what drugs the MySQL devs are on... – Cerin Dec 16 '15 at 17:01
  • 1
    Agree with @Cerin.. this is completely absurd, but it works. – FastTrack Jun 28 '17 at 15:44
  • @ekonoval Thanks you for the solution but It does not make the minimum sense for me, looks like you is fooling MySQL and he accept it, lol – deFreitas Jul 24 '17 at 16:59
  • @Cerin the worst thing is that it's also happen at oracle, lol – deFreitas Jul 24 '17 at 17:02
  • well, I just consider it as some kind of closure, so it barely makes some sense for me =) – Yehor Jul 24 '17 at 17:07
  • The query optimizer does a [_derived merge optimization_](https://mariadb.com/kb/en/library/derived-table-merge-optimization/) for the first query (which causes it to fail with the error), but the second query doesn't qualify for the _derived merge optimization_ hence the optimizer is forced to execute the subquery first. **Neat solution!** – ADTC Dec 29 '17 at 19:16
  • I don't understand why people tend to over-complicate simple things, look at DanDarc's anwer – user10089632 Jan 21 '18 at 07:42
  • Thank, great solution! – Toni Bünter Jul 07 '18 at 09:17
  • I already had the statement wrapped in a second select statement, but when I added a third one it worked. Perhaps this proves that Oracle have a sense of humour?? – Liam Jun 23 '20 at 12:41
  • I'm confused, why did you post this answer when NexusRex already did, and how is it more upvoted than NexusRex's original answer? – Shardj Sep 10 '20 at 11:06
  • even though i think it doesn't make sense, I'm sure something about it does actually make sense, though our non-db-developer brains simply can't compute still this is the best (most detailed?) explanation so far – fholzer Apr 06 '21 at 18:02
110

The inner join in your sub-query is unnecessary. It looks like you want to delete the entries in story_category where the category_id is not in the category table.

Do this:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category);

Instead of that:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN
         story_category ON category_id=category.id);
shA.t
  • 15,232
  • 5
  • 47
  • 95
103

Recently i had to update records in the same table i did it like below:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
Andy
  • 39,764
  • 8
  • 53
  • 80
Pratik Khadloya
  • 11,325
  • 10
  • 72
  • 94
  • 11
    Can't this just be written as `UPDATE skills SET type='Development' WHERE type='Programming';` ? This doesn't seem to be answering the original question. – lilbyrdie May 30 '14 at 15:23
  • 1
    Seems like overkill, @lilbyrdie is correct - it could only be `UPDATE skills SET type='Development' WHERE type='Programming';`. I do not understand why so many people is not thinking about what they do... – shadyyx Dec 05 '14 at 10:00
  • 1
    this is the best answer here, IMHO. It's syntax is easy to understand, you can re-use your previous statement and it's not limited to some super specific case. – Steffen Winkler Sep 07 '15 at 14:28
  • 4
    Even if the example case is questionable, the principle is the best to understand and deploy in real-world scenarios. The shown query works because implicit join in table list creates a temporary table for a subquery, thus providing stable results and avoiding clash between retrieval and modification of the same table. – AnrDaemon Apr 26 '17 at 13:11
  • 2
    regardless of what anyone might say about this being overkill, it still answers the question in terms of the title. The error which the OP mentioned is also encountered when a update is attempted using the same table in a nested query – smac89 Jan 09 '18 at 23:40
  • @AnrDaemon, very usefull features. What about optimization in accessing the temporary table created ? Does it still use the indexes of the original table ? – Marco S. Jul 21 '18 at 11:52
  • @lilbyrdie, Doing as you have shown in your example will throw `Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences -> SQL Editor and reconnect.` And it's the best approach I found without Inner Join. Thank you Pratik. – learner Feb 16 '19 at 17:19
  • For me, it says: "A new statement was found but no delimiter between it and the previous one. (near select)". It seems it doesn't like to see a SELECT there. – mayid Jan 31 '20 at 21:11
49

If you can't do

UPDATE table SET a=value WHERE x IN
    (SELECT x FROM table WHERE condition);

because it is the same table, you can trick and do :

UPDATE table SET a=value WHERE x IN
    (SELECT * FROM (SELECT x FROM table WHERE condition) as t)

[update or delete or whatever]

Sequoya
  • 735
  • 6
  • 15
38
DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category INNER JOIN story_category ON category_id=category.id
    ) AS c
)
NexusRex
  • 1,992
  • 16
  • 14
  • 3
    Can you explain why this works, why just nesting one more level works? This question has been asked already as a comment to @EkoNoval's question but nobody responded. Maybe you can help. – Akshay Arora Jan 27 '16 at 13:00
  • @AkshayArora, Go through the part under the heading 'Maybe you can just join the table to itself' in @Cheekysoft's answer. He said `UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col` - this would work as a different alias for the same table is used here . Similarly in @NexusRex's answer the first `SELECT` query acts as a derived table into which `story_category` is used for the second time. So the error mentioned in the OP should not take place here, right ? – Istiaque Ahmed Nov 09 '17 at 10:00
14

This is what I did for updating a Priority column value by 1 if it is >=1 in a table and in its WHERE clause using a subquery on same table to make sure that at least one row contains Priority=1 (because that was the condition to be checked while performing update) :


UPDATE My_Table
SET Priority=Priority + 1
WHERE Priority >= 1
AND (SELECT TRUE FROM (SELECT * FROM My_Table WHERE Priority=1 LIMIT 1) as t);

I know it's a bit ugly but it does works fine.

sactiw
  • 20,109
  • 4
  • 35
  • 28
  • 1
    @anonymous_reviewer: In case of giving [-1] or even [+1] to someone's comment please also mention why have you given it. Thanks!!! – sactiw Dec 20 '10 at 16:42
  • 1
    -1 because this is incorrect. You can't modify the same table that you use in the SELECT statement. – Chris Jan 27 '11 at 20:10
  • 1
    @Chris I have verified it on MySQL and it works just fine for me so I would request you to please verify it at your end and then claim it to be correct or incorrect. Thanks!!! – sactiw Feb 18 '11 at 15:03
  • 1
    At the bottom of this page it says 'Currently, you cannot update a table and select from the same table in a subquery.' - and I have experienced this to be true on many occasions. http://dev.mysql.com/doc/refman/5.0/en/update.html – Chris Feb 19 '11 at 00:08
  • 19
    @Chris I know that, but there is a workaround for that and which is exactly what I have tried to show with my 'UPDATE' query and believe me it works just fine. I don't think you have really tried to verify my query at all. – sactiw Feb 21 '11 at 13:39
  • This works fine. It's the same general structure as the accepted answer. – Jeff Davis Dec 20 '12 at 14:58
  • Unique answer here I used to get working in the case of the `select` returning multiple items and the `where` matches a `IN`. But, in fact, @NexuxRex is the complete example for my case. – Thiago Macedo May 20 '14 at 19:24
  • Works fine for my needs - seems a good way to bypass the error in question. – Mhluzi Bhaka Nov 02 '16 at 01:28
  • It works very well! It's a so simple solution! Thanks – user2342558 Sep 23 '19 at 07:39
9

For the specific query the OP is trying to achieve, the ideal and most efficient way to do this is NOT to use a subquery at all.

Here are the LEFT JOIN versions of the OP's two queries:

SELECT s.* 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;

Note: DELETE s restricts delete operations to the story_category table.
Documentation

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Will B.
  • 14,243
  • 4
  • 56
  • 62
Doin
  • 6,230
  • 3
  • 31
  • 31
  • 1
    Surprised this doesn't have more up-votes. It should also be noted that the multi-table syntax also works with `UPDATE` statements and joined sub-queries. Allowing you to perform `LEFT JOIN ( SELECT ... )` as opposed to `WHERE IN( SELECT ... )`, making the implementation useful across many use-cases. – Will B. Apr 09 '20 at 19:16
8

The simplest way to do this is use a table alias when you are referring parent query table inside the sub query.

Example :

insert into xxx_tab (trans_id) values ((select max(trans_id)+1 from xxx_tab));

Change it to:

insert into xxx_tab (trans_id) values ((select max(P.trans_id)+1 from xxx_tab P));
S.Roshanth
  • 1,457
  • 3
  • 23
  • 33
5

You could insert the desired rows' ids into a temp table and then delete all the rows that are found in that table.

which may be what @Cheekysoft meant by doing it in two steps.

YonahW
  • 14,450
  • 7
  • 38
  • 45
3

According to the Mysql UPDATE Syntax linked by @CheekySoft, it says right at the bottom.

Currently, you cannot update a table and select from the same table in a subquery.

I guess you are deleting from store_category while still selecting from it in the union.

briankip
  • 2,138
  • 2
  • 21
  • 24
2

Try to save result of Select statement in separate variable and then use that for delete query.

2

try this

DELETE FROM story_category 
WHERE category_id NOT IN (
SELECT DISTINCT category.id 
FROM (SELECT * FROM STORY_CATEGORY) sc;
Sameer Khanal
  • 903
  • 10
  • 9
2

If something does not work, when coming thru the front-door, then take the back-door:

drop table if exists apples;
create table if not exists apples(variety char(10) primary key, price int);

insert into apples values('fuji', 5), ('gala', 6);

drop table if exists apples_new;
create table if not exists apples_new like apples;
insert into apples_new select * from apples;

update apples_new
    set price = (select price from apples where variety = 'gala')
    where variety = 'fuji';
rename table apples to apples_orig;
rename table apples_new to apples;
drop table apples_orig;

It's fast. The bigger the data, the better.

Tom Schaefer
  • 849
  • 1
  • 7
  • 16
  • 11
    And you've just lost all your foreign keys, and maybe you have some cascading deletes, too. – Walf Dec 09 '14 at 06:48
1

how about this query hope it helps

DELETE FROM story_category LEFT JOIN (SELECT category.id FROM category) cat ON story_category.id = cat.id WHERE cat.id IS NULL
  • The result shows : ' #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'LEFT JOIN (SELECT categories.id FROM categories) cat ON story_category.id = cat.' at line 1 ' – Istiaque Ahmed Nov 09 '17 at 10:19
  • When using multi-table delete you must specify the affected tables. `DELETE story_category FROM ...` however, the joined sub-query is not necessary in this context and can be performed using `LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL`, Note the join criteria in the answer incorrectly references `story_category.id = cat.id` – Will B. Apr 09 '20 at 19:27
0

As far as concerns, you want to delete rows in story_category that do not exist in category.

Here is your original query to identify the rows to delete:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id
);

Combining NOT IN with a subquery that JOINs the original table seems unecessarily convoluted. This can be expressed in a more straight-forward manner with not exists and a correlated subquery:

select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id);

Now it is easy to turn this to a delete statement:

delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);    

This quer would run on any MySQL version, as well as in most other databases that I know.

Demo on DB Fiddle:

-- set-up
create table story_category(category_id int);
create table category (id int);
insert into story_category values (1), (2), (3), (4), (5);
insert into category values (4), (5), (6), (7);

-- your original query to identify offending rows
SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);
| category_id |
| ----------: |
|           1 |
|           2 |
|           3 |
-- a functionally-equivalent, simpler query for this
select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id)
| category_id |
| ----------: |
|           1 |
|           2 |
|           3 |
-- the delete query
delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);

-- outcome
select * from story_category;
| category_id |
| ----------: |
|           4 |
|           5 |
GMB
  • 188,822
  • 23
  • 52
  • 100