UPDATE Syntax with ORDER BY, LIMIT and Multiple Tables - mysql

Learning SQL, sorry if this is rudimentary. Trying to figure out a working UPDATE solution for the following pseudoish-code:
UPDATE tableA
SET tableA.col1 = '$var'
WHERE tableA.user_id = tableB.id
AND tableB.username = '$varName'
ORDER BY tableA.datetime DESC LIMIT 1
The above is more like SELECT syntax, but am basically trying to update a single column value in the latest row of tableA, where a username found in tableB.username (represented by $varName) is linked to its ID number in tableB.id, which exists as the id in tableA.user_id.
Hopefully, that makes sense. I'm guessing some kind of JOIN is necessary, but subqueries seem troublesome for UPDATE. I understand ORDER BY and LIMIT are off limits when multiple tables are involved in UPDATE... But I need the functionality. Is there a way around this?
A little confused, thanks in advance.

The solution is to nest ORDER BY and LIMIT in a FROM clause as part of a join. This let's you find the exact row to be updated (ta.id) first, then commit the update.
UPDATE tableA AS target
INNER JOIN (
SELECT ta.id
FROM tableA AS ta
INNER JOIN tableB AS tb ON tb.id = ta.user_id
WHERE tb.username = '$varName'
ORDER BY ta.datetime DESC
LIMIT 1) AS source ON source.id = target.id
SET col1 = '$var';
Hat tip to Baron Schwartz, a.k.a. Xaprb, for the excellent post on this exact topic:
http://www.xaprb.com/blog/2006/08/10/how-to-use-order-by-and-limit-on-multi-table-updates-in-mysql/

You can use following query syntax:
update work_to_do as target
inner join (
select w. client, work_unit
from work_to_do as w
inner join eligible_client as e on e.client = w.client
where processor = 0
order by priority desc
limit 10
) as source on source.client = target.client
and source.work_unit = target.work_unit
set processor = #process_id;
This works perfectly.

Related

MySQL Query limiting results by sub table

I'm really struggling with this query and I hope somebody can help.
I am querying across multiple tables to get the dataset that I require. The following query is an anonymised version:
SELECT main_table.id,
sub_table_1.field_1,
main_table.field_1,
main_table.field_2,
main_table.field_3,
main_table.field_4,
main_table.field_5,
main_table.field_6,
main_table.field_7,
sub_table_2.field_1,
sub_table_2.field_2,
sub_table_2.field_3,
sub_table_3.field_1,
sub_table_4.field_1,
sub_table_4.field_2
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
WHERE sub_table_4.field_1 = '' AND sub_table_4.field_2 = '0' AND sub_table_2.field_1 != ''
The query works, the problem I have is sub_table_1 has a revision number (int 11). Currently I get duplicate records with different revision numbers and different versions of sub_table_1.field_1 which is to be expected, but I want to limit the result set to only include results limited by the latest revision number, giving me only the latest sub_table_1_field_1 and I really can not figure it out!
Can anybody lend me a hand?
Many Thanks.
It's always important to remember that a JOIN can be on a subquery as well as a table. You could build a subquery that returns the results you want to see then, once you've got the data you want, join it in the parent query.
It's hard to 'tailor' an answer that's specific to you problem, as it's too obfuscated (as you admit) to know what the data and tables really look like, but as an example:
Say table1 has four fields: id, revision_no, name and stuff. You want to return a distinct list of name values, with their latest version of stuff (which, we'll pretend varies by revision). You could do this in isolation as:
select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no;
(Note: see fiddle at the end)
That would return each individual name with the latest revision of stuff.
Once you've got that nailed down, you could then swap out
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
....with....
INNER JOIN (select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no) sub_table_1
ON sub_table_1.id = main_table.id
...which would allow a join with a recordset that is more tailored to that which you want to join (again, don't get hung up on the actual query I've used, it's just there to demonstrate the method).
There may well be more elegant ways to achieve this, but it's sometimes good to start with a simple solution that's easier to replicate, then simplify it once you've got the general understanding of the what and why nailed down.
Hope that helps - as I say, it's as specific as I could offer without having an idea of the real data you're using.
(for the sake of reference, here is a fiddle with a working version of the above example query)
In your case where you only need one column from the table, make this a subquery in your select clause instead of than a join. You get the latest revision by ordering by revision number descending and limiting the result to one row.
SELECT
main_table.id,
(
select sub_table_1.field_1
from sub_table_1
where sub_table_1.id = main_table.id
order by revision_number desc
limit 1
) as sub_table_1_field_1,
main_table.field_1,
...
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
WHERE sub_table_4.field_1 = ''
AND sub_table_4.field_2 = '0'
AND sub_table_2.field_1 != '';

MySQL combination of view, subquery and left join produces a strange result

Update 1
I discover when it does the wrong behaviour. If the view is composed by two tables, only the fields in the first table has values inside the subquery. I don't know why, but if I change the JOIN order, it works. As soon as I try to match another field with the second table it returns NULL again.
Update 2
I've created a working example here: http://sqlfiddle.com/#!2/d4eb97/1
Update 3
The same example works in a newer MySQL version (5.6.6) so maybe there is a bug in the 5.5 - http://sqlfiddle.com/#!9/4e140/2
I've a schema in which I ended doing a SQL like this:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
WHERE tableA.user = view.user
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
What I'm doing here is simple:
Select two fields from view
view is a MySQL view, not a real table.
The second field is a subquery of:
2.1 The field user of the table tableA
2.2 Left join with the table tableB with the relational field
There are no rows in tableB yet
2.3 Only where the the tableA user is the same as in the view
2.4 Limit 1, just for this example
Limit results to user = 1
The strange thing here is that in some situations the field b_user is NULL, but the data is ok.
I can make three changes to make it works:
fix 1
Put the user id manually make it works
SELECT view.user,
(
SELECT tableA.user
FROM tableA
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
WHERE tableA.user = 1
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
fix 2
Remove the left join also make it works:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
WHERE tableA.user = view.user
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
fix 3
Another option is not to use the MySQL view:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
WHERE tableA.user = view_table_a.user
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
LIMIT 1
) as b_user
FROM view_table_a INNER JOIN view_table_b ON condition
WHERE table_a.user = 1
I'm not being able to reproduce this recreating a new database schema manually, it only happens in my current setup, which I cannot expose here due to security reasons.
Why the subquery return NULL values? I need to make the first query works since I can't use any of the three fixes.
Why have the subquery in the first place? I like subqueries, they are very handy things of have around. But they shouldn't be used if they don't have to be. Queries can get complicated enough with no help from us.
You are looking for a particular user from the main table (the fact that it is really a view is irrelevant) then using the same User value to join with TableA and then optionally joining to TableB using the ID value associated with that user:
select rs.Origin, a.Origin as Same_Origin
from requests_status rs
join assignments a
on a.employee = rs.employee
and a.origin = rs.origin
left join assignments_author aa
on aa.assignment = a.id
where rs.employee = 1;
Then I noticed that in your fiddles, you create the assignments_author table but never populate it. But that doesn't really matter because you left join to it. But you don't use any data from that table. So in actuality, you don't need that table in your query at all. Thus the equivalent query would be:
select rs.Origin, a.Origin as Same_Origin
from requests_status rs
join assignments a
on a.employee = rs.employee
and a.origin = rs.origin
where rs.employee = 1;
I don't know why you get a NULL in one but not the other. But since the query above returns the same answer in both fiddles and it is the expected results, my work here is finished.
I assume this is a bug, maybe this one (http://bugs.mysql.com/bug.php?id=52051) because the query fails in MySQL 5.5 (http://sqlfiddle.com/#!2/d4eb97/1) but works in 5.6 (http://sqlfiddle.com/#!9/4e140/2)

How to optimize this complected query?

While working with following query on mysql, Its getting locked,
SELECT event_list.*
FROM event_list
INNER JOIN members
ON members.profilenam=event_list.even_loc
WHERE (even_own IN (SELECT frd_id
FROM network
WHERE mem_id='911'
GROUP BY frd_id)
OR even_own = '911' )
AND event_list.even_active = 'y'
GROUP BY event_list.even_id
ORDER BY event_list.even_stat ASC
The Inner query inside IN constraint has many frd_id, So because of that above query is slooow..., So please help.
Thanks.
Try this:
SELECT el.*
FROM event_list el
INNER JOIN members m ON m.profilenam = el.even_loc
WHERE el.even_active = 'y' AND
(el.even_own = 911 OR EXISTS (SELECT 1 FROM network n WHERE n.mem_id=911 AND n.frd_id = el.even_own))
GROUP BY el.even_id
ORDER BY el.even_stat ASC
You don't need the GROUP BY on the inner query, that will be making the database engine do a lot of unneeded work.
If you put even_own = '911' before the select from network, then if even_own IS 911 then it will not have to do the subquery.
Also why do you have a group by on the subquery?
Also run explain plan top find out what is taking the time.
This might work better:
( SELECT e.*
FROM event_list AS e
INNER JOIN members AS m ON m.profilenam = e.even_loc
JOIN network AS n ON e.even_own = n.frd_id
WHERE n.mem_id = '911'
AND e.even_active = 'y'
ORDER BY e.even_stat ASC )
UNION DISTINCT
( SELECT e.*
FROM event_list AS e
INNER JOIN members AS m ON m.profilenam = e.even_loc
WHERE e.even_own = '911'
AND e.even_active = 'y' )
ORDER BY e.even_stat ASC
Since I don't know whether the JOINs one-to-many (or what), I threw in DISTINCT to avoid dups. There may be a better way, or it may be unnecessary (that is, UNION ALL).
Notice how I avoid two things that are performance killers:
OR -- turned into UNION
IN (SELECT...) -- turned into JOIN.
I made aliases to cut down on the clutter. I moved the ORDER BY outside the UNION (and added parens to make it work right).

mysql UPDATE needs to be ORDERED BY data in other tables

Here's an SQL statement (actually two statements) that works -- it's taking a series of matching rows and adding a delivery_number which increments for each row:
SELECT #i:=0;
UPDATE pipeline_deliveries AS d
SET d.delivery_number = #i:=#i+1
WHERE d.pipelineID = 11
ORDER BY d.setup_time;
But now, the client no longer wants them ordered by setup_time. They needed to be ordered according to departure time, which is a field in another table. I can't figure out how to do this.
The MySQL docs, as well as this answer, suggest that in version 4.0 and up (we're running MySQL 5.0) I should be able to do this:
SELECT #i:=0;
UPDATE pipeline_deliveries AS d RIGHT JOIN pipeline_routesXdeliveryID AS rXd
ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
LEFT JOIN pipeline_routes AS r
ON rXd.pipeline_routeID = r.pipeline_routeID
SET d.delivery_number = #i:=#i+1
WHERE d.pipelineID = 11
ORDER BY r.departure_time,d.pipeline_deliveryID;
but I get the error #1221 - Incorrect usage of UPDATE and ORDER BY.
So what's the correct usage?
You can't mix UPDATE joining 2 (or more) tables and ORDER BY.
You can bypass the limitation, with something like this:
UPDATE
pipeline_deliveries AS upd
JOIN
( SELECT t.pipeline_deliveryID,
#i := #i+1 AS row_number
FROM
( SELECT #i:=0 ) AS dummy
CROSS JOIN
( SELECT d.pipeline_deliveryID
FROM
pipeline_deliveries AS d
JOIN
pipeline_routesXdeliveryID AS rXd
ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
LEFT JOIN
pipeline_routes AS r
ON rXd.pipeline_routeID = r.pipeline_routeID
WHERE
d.pipelineID = 11
ORDER BY
r.departure_time, d.pipeline_deliveryID
) AS t
) AS tmp
ON tmp.pipeline_deliveryID = upd.pipeline_deliveryID
SET
upd.delivery_number = tmp.row_number ;
The above uses two features of MySQL, user defined variables and ordering inside a derived table. Because the latter is not standard SQL, it may very well break in a feature release of MySQL (when the optimizer is clever enough to figure out that ordering inside a derived table is useless unless there is a LIMIT clause). In fact the query would do exactly that in the latest versions of MariaDB (5.3 and 5.5). It would run as if the ORDER BY was not there and the results would not be the expected. See a related question at MariaDB site: GROUP BY trick has been optimized away.
The same may very well happen in any future release of main-strean MySQL (maybe in 5.6, anyone care to test this?) that will improve the optimizer code.
So, it's better to write this in standard SQL. The best would be window functions which haven't been implemented yet. But you could also use a self-join, which will be not very bad regarding efficiency, as long as you are dealing with a small subset of rows to be affected by the update.
UPDATE
pipeline_deliveries AS upd
JOIN
( SELECT t1.pipeline_deliveryID
, COUNT(*) AS row_number
FROM
( SELECT d.pipeline_deliveryID
, r.departure_time
FROM
pipeline_deliveries AS d
JOIN
pipeline_routesXdeliveryID AS rXd
ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
LEFT JOIN
pipeline_routes AS r
ON rXd.pipeline_routeID = r.pipeline_routeID
WHERE
d.pipelineID = 11
) AS t1
JOIN
( SELECT d.pipeline_deliveryID
, r.departure_time
FROM
pipeline_deliveries AS d
JOIN
pipeline_routesXdeliveryID AS rXd
ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
LEFT JOIN
pipeline_routes AS r
ON rXd.pipeline_routeID = r.pipeline_routeID
WHERE
d.pipelineID = 11
) AS t2
ON t2.departure_time < t2.departure_time
OR t2.departure_time = t2.departure_time
AND t2.pipeline_deliveryID <= t1.pipeline_deliveryID
OR t1.departure_time IS NULL
AND ( t2.departure_time IS NOT NULL
OR t2.departure_time IS NULL
AND t2.pipeline_deliveryID <= t1.pipeline_deliveryID
)
GROUP BY
t1.pipeline_deliveryID
) AS tmp
ON tmp.pipeline_deliveryID = upd.pipeline_deliveryID
SET
upd.delivery_number = tmp.row_number ;
Based on this documentation
For the multiple-table syntax, UPDATE updates rows in each table named
in table_references that satisfy the conditions. In this case, ORDER
BY and LIMIT cannot be used.
Without knowing too much about MySQL you could open up a cursor and process this row by row, or by passing it back to the client code (PHP,Java, etc) that you maintain to handle this processing.
After more digging:
To eliminate the badly optimized subquery, you need to rewrite the
subquery as a join, but how can you do that and retain the LIMIT and
ORDER BY? One way is to find the rows to be updated in a subquery in
the FROM clause, so the LIMIT and ORDER BY can be nested inside the
subquery. In this way work_to_do is joined against the ten
highest-priority unclaimed rows of itself. Normally you can’t
self-join the update target in a multi-table UPDATE, but since it’s
within a subquery in the FROM clause, it works in this case.
update work_to_do as target
inner join (
select w. client, work_unit
from work_to_do as w
inner join eligible_client as e on e.client = w.client
where processor = 0
order by priority desc
limit 10
) as source on source.client = target.client
and source.work_unit = target.work_unit
set processor = #process_id;
There is one downside: the rows are not locked in primary key order.
This may help explain the occasional deadlock we get on this table
The hard way:-
ALTER TABLE eav_attribute_option
ADD temp_value TEXT NOT NULL
AFTER sort_order;
UPDATE eav_attribute_option o
JOIN eav_attribute_option_value ov ON o.option_id=ov.option_id
SET o.temp_value = ov.value
WHERE o.attribute_id=90;
SET #x = 0;
UPDATE eav_attribute_option
SET sort_order = (#x:=#x+1)
WHERE attribute_id=90
ORDER BY temp_value ASC;
ALTER TABLE eav_attribute_option
DROP temp_value;

error query update mysql

I have an query:
update customers_training_malaysia
set
period_id = (select b.id
from customers_training_malaysia a,training_schedules_malaysia b
where a.sch_code=b.sch_code order by a,id)
where
sch_code = (select b.sch_code
from customers_training_malaysia a,training_schedules_malaysia b
where a.sch_code = b.sch_code order by a.id)
I tried to run following query update, but I get only the error
more than one row returned by a subquery used as an expression
What shall I do to correct the sql query?
You must get all your subqueries in
set [field_name] = ([subquery])
and check that this queries returning only one record in results.
That is reason for the error - multiple results in your subqueries
Try this:
update customers_training_malaysia
set
period_id = b.id
where
sch_code = (select b.sch_code
from customers_training_malaysia a,training_schedules_malaysia b
where a.sch_code = b.sch_code order by a.id)
order by a,id)
Is this a typo?
**","** used it should be a.id
And Add from the other answer Below it might be returning more than one result, because of order by condition i assumed that!