MySQL syntax for UPDATE + CASE WHEN + EXISTS + SELECT MAX - mysql

EDIT: Working now, solution in the bottom of this post.
I have tried to create this query for hours now, without success:
UPDATE tasks
SET `Order`=
(
CASE WHEN
(
SELECT EXISTS
(
SELECT 1
FROM user_job_tasks ujt
WHERE ujt.JobID = :jobID AND ujt.TaskID = LAST_INSERT_ID()
)
)
THEN
(
SELECT `order` FROM
(
SELECT MAX(t.`Order`)+1 AS `Order`
FROM user_job_tasks ujt
LEFT OUTER JOIN tasks t ON ujt.TaskID = t.ID
WHERE ujt.JobID = :jobID
) AS temp
)
ELSE
(
1
)
END
)
WHERE ID = LAST_INSERT_ID()
Now, the point of this is to update tasks table's column Order where ID equals something (LAST_INSERT_ID() here).
If any records exist in user_job_tasks where jobID and taskID match the values, I want to set Order as the highest value + 1 of Order found in user_job_tasks where jobID matches the value.
If not, Order will be set 1.
I am only getting an error General error: 1093 You can't specify target table 'tasks' for update in FROM clause. I fail to find anything wrong in the query syntax.
Sorry for confusing explanation, perhaps I should just go to sleep.
Working solution:
UPDATE tasks
SET `Order`=
(
COALESCE
(
(
SELECT `Order` FROM
(
SELECT MAX(t.`Order`)+1 AS `Order`
FROM user_job_tasks ujt
LEFT OUTER JOIN tasks t ON ujt.TaskID = t.ID
WHERE ujt.JobID = :jobID
) AS temp
),
1
)
)
WHERE ID = LAST_INSERT_ID()

I am not 100% sure this does what you want, but I think it does:
UPDATE tasks
SET `Order`= coalesce((SELECT MAX(t.`Order`) + 1 AS `Order`
FROM user_job_tasks ujt JOIN
tasks t
ON ujt.TaskID = t.ID
WHERE ujt.JobID = :jobID
), 1)
WHERE ID = LAST_INSERT_ID();
This just looks for the maximum id (+ 1) for your records and then uses 1 if this doesn't exist.
EDIT:
MySQL doesn't like using the table being updated in a from clause. So the above is correctly stated using a second subquery:
UPDATE tasks
SET `Order`= coalesce((SELECT `Order`
FROM (SELECT MAX(t.`Order`) + 1 AS `Order`
FROM user_job_tasks ujt JOIN
tasks t
ON ujt.TaskID = t.ID
WHERE ujt.JobID = :jobID
)
), 1)
WHERE ID = LAST_INSERT_ID();
This limitation would also apply to your query.

Related

UPDATE - SELECT - MYSQL #1093 - You can't specify target table 'temp1' for update in FROM clause

I can't find solution to correct this big query, I always receive an error from database.
I have tre tables and I need to make changes on some attribute on some condition:
UPDATE o36t_orders as temp1,
mytable as temp2
SET
temp1.bonifico = 1,
temp2.ultimo = 1
WHERE
temp1.id_order IN (
SELECT
id_order
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
)
AND temp2.id IN (
SELECT
id
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
)
Since the subqueries of the 2 IN clauses are identical (except the retuned column), I think that you can do what you want by a straight inner join of the 2 tables and that subquery (returning both columns):
UPDATE o36t_orders temp1
INNER JOIN (
SELECT
id, id_order
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
) t ON t.id_order = temp1.id_order
INNER JOIN mytable temp2 ON temp2.id = t.id
SET
temp1.bonifico = 1,
temp2.ultimo = 1

Order by clause not behaving correctly after several joins

Here is my query:
SELECT DISTINCT `post_data`. * , pv.`seller_id` , pv.`islimited` , pv.`isquantity` , pv.`isslider`, `price`.`original_price` , `price`.`discount_percentage` , `timelimit`.`start_date` , `timelimit`.`expire_date` , `quantity`.`in_stock`, `currency`.`currency_symbol`, `seller`.`directory`, `post_to_cat`.`cat_id`, count(`sales`.`sales_id`) as sale FROM `post_view` AS pv
INNER JOIN `post_data` ON pv.`post_id` = `post_data`.`post_id` AND pv.`status` = 1
INNER JOIN `price` ON pv.`post_id` = `price`.`post_id`
INNER JOIN `currency` ON `price`.`currency_id` = `currency`.`currency_id`
INNER JOIN `seller` ON pv.`seller_id` = `seller`.`seller_id`
INNER JOIN `post_to_cat` ON `post_to_cat`.`cat_id` = 1 AND `post_to_cat`.`post_id` = `post_data`.`post_id`
LEFT JOIN `timelimit` ON ( CASE WHEN pv.`islimited` = 1 THEN `timelimit`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `quantity` ON ( CASE WHEN pv.`isquantity` = 1 THEN `quantity`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `sales` ON `sales`.`post_id` = pv.`post_id` AND `sales`.`status` = 1
WHERE pv.`status` = 1
ORDER BY pv.`post_id` DESC LIMIT 1
The ORDER BY DESC is not working, it just returns the first row from the table, but I want to get the highest post_id value row. What is the mistake I am making?
AS #Alex said in the comments you've got a LIMIT 1 at the end, you should probably bracket the last LEFT JOIN also for readability.
As #McAdam331 said we need data sample and sql fiddle to investigate what is wrong with you query. But at the moment I have some suggestions how to improve and debug your query.
First off all, what do I see the main and very left table in your query is post_view so all other tables should be LEFT JOIN if you want to get the max id. You should use INNER JOIN only if you think that other table could filter your main table somehow and order or result could be other table dependend. But in your case I see no reason to use INNER JOIN.
Second point is your very weird ON conditions:
LEFT JOIN `timelimit` ON ( CASE WHEN pv.`islimited` = 1 THEN `timelimit`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `quantity` ON ( CASE WHEN pv.`isquantity` = 1 THEN `quantity`.`post_id` ELSE -1 END ) = pv.`post_id`
I have converted them into another one
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`start_date` ELSE NULL END as start_date ,
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`expire_date` ELSE NULL END as expire_date,
CASE WHEN pv.`isquantity`=1 THEN `quantity`.`in_stock` ELSE NULL END as in_stock,
But I still don't like it. It seems very useless to me. And has no sense when I read CASE WHEN pv.islimited=1 THEN timelimit.start_date ELSE NULL END as start_date so if flag pv.islimited=0 you don't need start_date? Are you sure?
And the last thing I can suggest: try to use my or even your query. But add every table by step while debugging. So First query just:
SELECT
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`
FROM `post_view` AS pv
WHERE pv.`status` = 1
ORDER BY pv.`post_id` DESC
LIMIT 1
If it returns correct post_id add next table:
SELECT
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`,
`post_data`. *
FROM `post_view` AS pv
LEFT JOIN `post_data`
ON pv.`post_id` = `post_data`.`post_id`
WHERE pv.`status` = 1
AND `post_data`.`slug` = 'abc'
ORDER BY pv.`post_id` DESC
LIMIT 1
Check the result. And continue step by step.
Yes it takes time. But that is debugging process. It could be the fastest way to get that query done. :-)
SELECT `post_data`. * ,
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`, `price`.`original_price` , `price`.`discount_percentage` ,
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`start_date` ELSE NULL END as start_date ,
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`expire_date` ELSE NULL END as expire_date,
CASE WHEN pv.`isquantity`=1 THEN `quantity`.`in_stock` ELSE NULL END as in_stock,
`currency`.`currency_symbol`, `seller`.`directory`, `post_to_cat`.`cat_id`, count(`sales`.`sales_id`) as sale
FROM `post_view` AS pv
LEFT JOIN `post_data`
ON pv.`post_id` = `post_data`.`post_id`
LEFT JOIN `price`
ON pv.`post_id` = `price`.`post_id`
LEFT JOIN `currency`
ON `price`.`currency_id` = `currency`.`currency_id`
LEFT JOIN `seller`
ON pv.`seller_id` = `seller`.`seller_id`
LEFT JOIN `post_to_cat`
ON `post_to_cat`.`cat_id` = 1
AND `post_to_cat`.`post_id` = pv.`post_id`
LEFT JOIN `timelimit`
ON `timelimit`.`post_id` = pv.`post_id`
LEFT JOIN `quantity`
ON quantity`.`post_id` = pv.`post_id`
LEFT JOIN `sales`
ON `sales`.`post_id` = pv.`post_id`
AND `sales`.`status` = 1
WHERE pv.`status` = 1
AND `post_data`.`slug` = 'abc'
GROUP BY pv.`post_id`
ORDER BY pv.`post_id` DESC
LIMIT 1
EDIT 1 - last GROUP BY pv.post_id was added as per #McAdam331 notice about count() function without GROUP BY
I believe the issue here is mostly as a result of preforming aggregation (using the COUNT()) function, without any group by. Although, it seems like you don't necessarily need it because you want that count only for the post in question.
If you're trying to gather all of that information for a single post, I would adjust your WHERE clause to have a condition to only gather that information for the post with the largest ID.
Instead of ordering by ID and limiting by 1, use a subquery to get the largest id, like this:
...
WHERE pv.status = 1 AND post_data.slug = 'abc' AND pv.post_id = (SELECT MAX(post_id) FROM post_view);

Retrieve the last line in one to one relation with millions of rows

Something rare to happen is one to one where the second table can have millions of results for the first one. For example, I have a 'radcliente' table that has millions of 'radacct', but need to filter only with the last acct. The following are examples for better explanation:
This is criteria:
$criteria = new CDbCriteria();
$criteria->with = [
'acct', // slow because it will take millions of lines to have only the last
];
$criteria->together = true;
$clientes = Cliente::model()->findAll($criteria);
This is generated query by Yii (very slow, more then 40 seconds, it return millions of rows to use only one in AR):
SELECT
`t`.`id` AS `t0_c0`,
-- ...
`t`.`spc_serasa` AS `t0_c56`,
`acct`.`radacctid` AS `t1_c0`,
-- ...
`acct`.`cliente_id` AS `t1_c27`
FROM
`radcliente` `t`
LEFT OUTER JOIN `radacct` `acct` ON (`acct`.`cliente_id`=`t`.`id`)
ORDER BY
radacctid DESC
After apply my solution limit join to one row (is this fast! 200ms-):
SELECT
`t`.`id` AS `t0_c0`,
..
`t`.`spc_serasa` AS `t0_c56`,
`acct`.`radacctid` AS `t1_c0`,
-- ...
`acct`.`cliente_id` AS `t1_c27`
FROM
`radcliente` `t`
LEFT OUTER JOIN `radacct` `acct` ON (
acct.radacctid = (
SELECT radacctid
FROM `radacct` `acct`
WHERE (acct.cliente_id = t.id)
ORDER BY radacctid DESC
LIMIT 1
)
)
This is the generated query by CActiveDataProvider to total item count with my solution of limit join to one (slow, 10 seconds to count):
SELECT
COUNT(*)
FROM (
SELECT
`t`.`id` AS `t0_c0`,
-- ...
`t`.`spc_serasa` AS `t0_c56`,
`endereco_instalacao`.`id` AS `t1_c0`,
`telefones`.`id` AS `t2_c0`,
`telefones`.`telefone` AS `t2_c3`,
`emails`.`id` AS `t3_c0`,
`emails`.`email` AS `t3_c3`,
`metodo_cobranca`.`id` AS `t4_c0`,
`acct`.`radacctid` AS `t5_c0`,
`acct`.`framedipaddress` AS `t5_c22`
FROM
`radcliente` `t`
LEFT OUTER JOIN `radcliente_endereco_instalacao` `endereco_instalacao` ON (
endereco_instalacao.id = (
SELECT id
FROM `radcliente_endereco_instalacao` `endereco_instalacao`
WHERE (
endereco_instalacao.cliente_id = t.id
)
LIMIT 1
)
)
LEFT OUTER JOIN `radcliente_telefone` `telefones` ON (`telefones`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_email` `emails` ON (`emails`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radmetodo_cobranca` `metodo_cobranca` ON (
metodo_cobranca.id = (
SELECT id
FROM `radmetodo_cobranca` `metodo_cobranca`
WHERE (metodo_cobranca.cliente_id = t.id)
AND (metodo_cobranca.arquivo = 'nao')
ORDER BY metodo_cobranca.id DESC
LIMIT 1
)
)
LEFT OUTER JOIN `radacct` `acct` ON (
acct.radacctid = (
SELECT radacctid
FROM `radacct` `acct`
WHERE (acct.cliente_id = t.id)
ORDER BY radacctid DESC
LIMIT 1
)
)
GROUP BY t.id
) sq
But the problem is in the count generated by CActiveDataProvider (about 10 seconds to return the result) would have a way to optimize without having to lose the relationship (because I need to filter by a relationship in the future)?
UPDATE
Thank you for your response. I've been doing some tests and noticed that is slow in all cases, the table 'radacct' exacerbates the problem by its size, which should not therefore limit the 1 in the subquery. Follow the models and the link to access the system, if you need to authenticate is:
To access:
http://177.86.111.30/dev2/teste
username: help
password: 1
To download models and schema of radcliente and radacct: http://177.86.111.30/files.zip
Instead of ON id = ( SELECT ... LIMIT 1 ) try adding another JOIN (not LEFT JOIN):
JOIN ( SELECT ... LIMIT 1 ) x ON ...
The fear I have with your code is that it will be evaluating that subquery repeatedly, whenever it needs to check the ON clause. My rewrite will cause the subquery to happen only once.
Your query looks like a "correlated" subquery, so you would need to rephrase it to be non-correlated, if possible.

update query with subselects from the same table

I'm trying to create a query to update the rank field of all the records in the table, based on values from the same table.
I managed to get a working SELECT query that calculates the rank, but I'm having a hard time converting it to an UPDATE. This is the query:
SELECT
((views_count + comments_count) * (172800 / elapsed)) AS rank
FROM (
SELECT
p.views_count,
(UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(p.createdAt)) AS elapsed,
(SELECT COUNT(*) FROM `comments` AS c WHERE c.photo_id = p.id) AS comments_count
FROM `photos` AS p
) AS m
How can I implant it in an UPDATE query, to update each record's rank value?
Thanks! :)
EDIT:
My DB structure:
[Photo]
id
user_id
views_count
rank
createdAt
[Comment]
id
photo_id
content
Just a guess, but something like this should work:
UPDATE `photos`
FROM (
SELECT id
, (views_count + comments_count) * (172800 / elapsed) AS rank
FROM (
SELECT p.id
, p.views_count
, ( UNIX_TIMESTAMP(NOW())
- UNIX_TIMESTAMP(p.createdAt)) AS elapsed
, (SELECT COUNT(*)
FROM `comments` AS c
WHERE c.photo_id = p.id) AS comments_count
FROM `photos` AS p
) AS m
) AS z
SET rank=z.rank
WHERE `photos`.id=z.id
You should write your new ranks to a temporary table and use that to update the original table. try
CREATE TEMPORARY TABLE tmp_tbl AS (
SELECT ((views_count + comments_count) * (172800 / elapsed)) AS rank, m.id
FROM (SELECT
p.views_count,
(UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(p.createdAt)) AS elapsed,
(SELECT COUNT(*) FROM `comments` AS c WHERE c.photo_id = p.id) AS comments_count
FROM `photos` AS p
) AS m);
and then
UPDATE photos, tmp_tbl SET photos.rank = tmp_tbl.rank WHERE photos.id=tmp_tbl.id;
and finally
DROP TABLE tmp_tbl;
See this simliar stackoverflow question

Update mysql rows with value extracted from counting another table

I have the following query:
SELECT int_intrebari.id, COUNT( id_raspuns ) AS nr_raspunsuri
FROM int_intrebari, int_raspunsuri
WHERE int_intrebari.id = int_raspunsuri.id
GROUP BY id
Is it possible to update first table with nr_raspunsuri from the query, without writing a foreach statement?
You can UPDATE with JOIN like so:
UPDATE int_intrebari i1
INNER JOIN
(
SELECT id, COUNT( id_raspuns ) AS nr_raspunsuri
FROM int_intrebari
GROUP BY id
) i2 ON i1.id = i2.id
SET i1.nr_raspunsuri = i2.nr_raspunsuri
You can do it like -
update int_intrebari left join int_raspunsuri on int_intrebari.id =int_raspunsuri.id
set int_intrebari.column_to_update = int_raspunsuri.column_from_update_second_table
UPDATE
(SELECT int_intrebari.id, COUNT( id_raspuns) AS nr_raspunsuri
FROM int_intrebari, int_raspunsuri
WHERE int_intrebari.id = int_raspunsuri.id
GROUP BY id) t1,
int_raspunsuri t2
SET
t2.nr_raspunsuri=t1.nr_raspunsuri
WHERE
t1.id=t2.id