MySQL doesn’t allow referring to a table that’s targeted for update in a FROM clause, which can be frustrating. There’s a better way than creating endless temporary tables, though. This article explains how to update a table while selecting from it in a subquery.
The problem
Suppose I want to update a table with data from a subquery that refers to the same table. I might want to do this for a variety of reasons, such as trying to populate a table with its own aggregate data (this would require assignment from a grouped subquery), updating one row from another row’s data without using non-standard syntax, and so on. Here’s a contrived example:
create table apples(variety char(10) primary key, price int);
insert into apples values('fuji', 5), ('gala', 6);
update apples
set price = (select price from apples where variety = 'gala')
where variety = 'fuji';
The error message is ERROR 1093 (HY000): You can't specify target table 'apples' for update in FROM clause. The MySQL manual mentions this at the bottom of the UPDATE documentation: “Currently, you cannot update a table and select from the same table in a subquery.”
It’s pretty easy to work around the problem in this contrived example, but there are times when it’s not possible to write the query without subqueries that refer to the update target. There is a workaround, though.
The workaround
Since MySQL materializes subqueries in the FROM clause (“derived tables”) as temporary tables, wrapping the subquery into another inner subquery in the FROM clause causes it to be executed and stored into a temporary table, then referenced implicitly in the outer subquery. The following statement will succeed:
update apples
set price = (
select price from (
select * from apples
) as x
where variety = 'gala')
where variety = 'fuji';
If you want to know more about how this works, read the relevant section in the MySQL Internals Manual.
Problems this trick doesn’t avoid
One common frustration this doesn’t solve is the issue of badly optimized queries in the IN() clause, which are rewritten as correlated subqueries, sometimes (usually?) causing terrible performance. Wrapping the subquery in another subquery doesn’t prevent the optimizer from rewriting it as a correlated subquery, though, unless I go to extremes. In any case it’s better to just rewrite such a query as a join.
Another thing it won’t do is allow a query to refer to a temporary table more than once. Neither of these issues is solvable with the “wrap it in a subquery” trick because they are created at query compile time, whereas the update issue I was able to solve above happens at query run time.
If you enjoyed this article, subscribe via feeds or e-mail to receive my articles regularly.
Update 2006-08-29 The queries I’ve given here are sloppy, performance-wise. 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.

Unfortunately it does’t work in my case. I tried the following query in MySQL 4.1.11 and get the same error again:
UPDATE `phpads_banners` SET `compiledlimitation` = (
SELECT `compiledlimitation`
FROM (SELECT * FROM `phpads_banners`) AS b
WHERE `bannerid` = 33 )
WHERE `bannerid` = 34
#1093 - You can’t specify target table ‘phpads_banners’ for update in FROM clause
Hmm, that’s not working for me either on 4.1.21. I’m not sure why — maybe I tested the technique in the article on 5.x. I’d guess at the reason, but I’d likely be wrong. However, here’s a workaround:
update phpads_banners as a inner join ( select * from phpads_banners where bannerid = 33 ) as b on a.bannerid=34 and b.bannerid = 33 set a.compiledlimitation = b.compiledlimitation;Thx, this works. I think some infos about the version number tested with is always usefull ;-)
PS: Nice site. I already added it to my feedreader for coming back later…
Thanks a lot for this hint. It is a lifesaver.
great!!
Hi,
How can I use this workaround with the following statement?
UPDATE webartCategories AS cat SET level=level+1 WHERE cat.parent=111 OR EXISTS( SELECT 1 FROM webartCategories AS cat1, webartCategories AS cat2 WHERE (cat.parent=cat1.id AND cat1.parent=111) OR (cat.parent=cat1.id AND cat1.parent=cat2.id AND cat2.parent=111))This handles items in a hierarchical tree.
Any help would be greatly appreciated. Thanks.
Workaround w/ Lots of Cols
I have 10 columns that need to be updated via a subquery to the updated table.
Your workaround could work with an additional workaround: having the inner SELECT concatenenate all the selected columns into one, followed by a query that updates the 10 columns by extracting the values from the concatenated column.
To continue the apples analogy with apples:
update apples set concated_cols = ( select CONCAT(price,'-',quality,'-',color) from ( select * from apples ) as x where variety = 'gala') where variety = 'fuji';Then, a 2nd query distibutes the entries of concatenated_cols to the appropriate “real” columns.
BUT, is there a better way?
-Chas
P.S., my kudos on this site also.
Lukas, you hit one one of the major reasons RDBMS vendors provide the ability to update from a JOIN. Purists angrily say a searched update statement is the only right way to do it, but if you have to update more than one column, the purist’s way to do it looks like this:
update apples set price = ( select price from ( select * from apples ) as x where variety = 'gala'), color = ( select color from ( select * from apples ) as x where variety = 'gala'), where variety = 'fuji';In other words, every value you want to update requires a subquery to search for the new value. You’re going to need some additional logic there too, such as CASE statements to avoid setting columns to NULL in case the subquery returns nothing.
Joining values into a string and then splitting them apart doesn’t work around this. Generally, if it feels like an ugly sin, it is probably not the best way. (Even the subquery technique I’m demonstrating on this page is an ugly sin, only forced upon our heads by MySQL peculiarities. Not that I’m complaining. You will find ugly hacks on every platform.)
Thanks for writing in!
Hi Fijit,
Thanks for writing in. This statement is pretty complex. Let me see if I can understand it. You want to increase the row’s
levelif it is either a) a child of 111, b) a grandchild of 111, c) a great-grandchild of 111. In other words, if you have the following table it should update the last three rows:You can’t use the subquery trick in this case because your subquery is correlated, i.e. it refers to the outer aliased table ‘cat’ in the subquery. MySQL doesn’t allow using a subquery in the
FROMclause in a correlated subquery because it builds a temporary table for it. So far so good — you know all this.The only way I can think of is to write a query that will accept the value 111 and return every row that’s a descendent, up to three levels deep, then nest that query into a
FROMclause and join against it. Here’s one possible query:I used
LEFTjoins so the join would succeed even if there aren’t three levels of descendents. Now you have all three values in a single row, which isn’t really ideal: you can’t put it into anIN()clause because it wants a single column, and what if there are multiple rows in the result?. You could mess around with this and get it to work a couple different ways, depending on your schema and the results. Here’s one:update webartCategories as cat inner join ( [copy/paste query above] ) as x on id in (id1, id2, id3) set level = level + 1This might perform badly, depending on your schema and how much data you have. (See my articles about profiling queries for how to really find out). Here’s another way to do this:
select id from webartCategories where parent = 111 union all select cat2.id from webartCategories as cat1 inner join webartCategories as cat2 on cat2.parent = cat1.id where cat1.parent = 111 union all select cat3.id from webartCategories as cat1 inner join webartCategories as cat2 on cat2.parent = cat1.id inner join webartCategories as cat3 on cat3.parent = cat2.id where cat1.parent = 111 +-----+ | id | +-----+ | 112 | | 113 | | 114 | +-----+Now at least you have a single column and unlimited rows, which is a lot better to work with relationally. (Notice I’m using
INNERjoins now). You could join against this in a subquery:update webartCategories as cat inner join ( [copy/paste query above] ) as x on cat.id = x.id set level = level + 1Again, whether that performs well depends on your data.
Recursive queries suck. You’re only simulating recursion, three levels deep. The official SQL standard does have some means to write recursive queries, at least some of which are supported in MS SQL Server 2005, but MySQL doesn’t support any of them. It might be a better idea to repeatedly issue a query that updates children, then if it matched any rows update grandchildren, and so on. If you do this, at least you can auto-generate queries that recurse as deeply as needed, and your code can stop as soon as no more rows are matched. It’s a poor man’s recursive query.
I am using Myqsl and working on parent children database where depth is unlimited ( not fixed .. not unlimited actually) and recursion sucks… i use php for recursion .. is there any way around to make this all working through Mysql query.. means go up till sponsor/parentId <> 0 or go down till leftId <>0 or RightID<>0 or ….
help is welcome with a great thanks in advance if there any way around
There are several ways to handle hierarchical data, such as nested sets etc. Joe Celko’s book about it is said to be a classic, though I have not found time to read it myself! There’s a great explanation of nested sets in Pro MySQL.
This works for me fine in MySQL 5.0.24, but not on 4.1.11. Just wanted to check that others have the same issue, or is there something I’m missing?
Cheers!
Ant.
Hey,
It looks like you have the solution for my needs but I really have no idea how to implement what you’re talking about in my code… as I don’t think I fully understand it. If you could take a look at this and let me know how to do it or give me more examples so I can figure it out, that would be great :):
UPDATE rehabs SET phone2 = ( SELECT t1.phone
FROM rehabs t1, rehabs t2
WHERE t1.name = t2.name
AND t1.address1 = t2.address1
AND t1.address2 = t2.address2
AND t1.city = t2.city
AND t1.zip = t2.zip
AND t1.url = t2.url
AND t1.phone t2.phone
AND t1.rehabID t2.rehabID )
WHERE EXISTS (
SELECT t1.phone
FROM rehabs t1, rehabs t2
WHERE t1.name = t2.name
AND t1.address1 = t2.address1
AND t1.address2 = t2.address2
AND t1.city = t2.city
AND t1.zip = t2.zip
AND t1.url = t2.url
AND t1.phone t2.phone
AND t1.rehabID
It looks like you can just rewrite that as a self-join instead of using subqueries. This will probably work much better:
This worked for me.
I needed to update a number with the max value of a column in the same table.
update inscricoes
set dorsal = (
select dorsal from (
select max(dorsal) 1 as dorsal from inscricoes
) as dorsal)
where id = ‘$id’
Do you see any problem that may arise from this approach? (with concurrency maybe?)
Anyway … thanks for sharing! ;)
This works great even for delete statements. I had a table with a linked id to another table but didn’t create a cascade foreign key yet and had some entries from the top table deleted. So to add a foreign key, I had to delete all the entries from the linked table that had nothing linking to it so I did this:
DELETE FROM tbldetails WHERE ID IN (SELECT ID FROM (SELECT tbldetails.ID FROM tbldetails LEFT JOIN tbltop ON tbltop.ID=tbldetails.topid WHERE top IS NULL) AS X);
(I replaced the actual names that would make more sense to what I’m talking about, hopefully). Although I would think this is a common thing and wonder if there’s an easier way to do that.
What’s a good book on this topic?
On how to understand how MySQL works? The book I helped write, when it is published, will be the best. http://www.oreilly.com/catalog/9780596101718/ Note that that’s the second edition: the first edition is not as detailed.
Otherwise, I think Pro MySQL is a very good book, written by my friends Jay Pipes and Mike Kruckenberg. http://www.apress.com/book/view/159059505X
(^__^)
(_ _)
(^__^)
(_ _)
thank you x
For a credit / debit/ balance table, this is an example:
INSERT INTO account set credit = 100, balance =ifnull((select balance from (select ((sum(credit)-sum(debit))) as balance from account) as x),0) 100
For instance, If you run twice the query, the table will be:
| credit | debit | balance
100 0 100
100 0 200