How to use both SELECT and UPDATE statements in one query? - mysql

I have this update query which works as well:
UPDATE tbname t CROSS JOIN ( SELECT related FROM tbname WHERE id = 5 ) x
SET AcceptedAnswer = ( id = 5 )
WHERE t.related = x.related
I also have two select statements which validates somethings. Actually I want to check these to conditions before updating:
Condition1:
(SELECT 1 FROM tbname
WHERE id = x.related AND
author_id = 29
)
Condition2:
(SELECT 1 FROM tbname
WHERE id = x.related AND
(
( amount IS NOT NULL AND
NOT EXISTS ( SELECT 1 FROM tbname
WHERE related = x.related AND
AcceptedAnswer = 1 )
) OR amount IS NULL
)
)
How can I combine those two conditions with that updating query?
Here is what I've tried so far but it doesn't work and throws this error:
UPDATE tbname CROSS JOIN ( SELECT related FROM tbname WHERE id = 5 ) x
SET AcceptedAnswer = ( id = 5 )
WHERE q.related = x.related
AND
(SELECT 1 FROM tbname
WHERE id = x.related AND
author_id = 29
) AND
(SELECT 1 FROM tbname
WHERE id = x.related AND
(
( amount IS NOT NULL AND
NOT EXISTS ( SELECT 1 FROM tbname
WHERE related = x.related AND
AcceptedAnswer = 1 )
) OR amount IS NULL
)
)
#1093 - You can't specify target table 'tbname' for update in FROM clause

Seems your update is equivalent to this
update tbname as a
inner join tbname as b on a.related = b.related and b.id = 5
set AcceptedAnswer = (id = 5)
your query seem set to true (1) the AccepetdAnswer of the row with id = 5 for the row that have acceppeted equalt to the accepted value of th row with id = 5 (false / 0) in the other case ..
for test use
select * from tbname as a
inner join tbname as b on a.related = b.related and b.id = 5
and (b.related = a.id and a.author_id = 29)
and (b.related = a.id and
(a.amont is not null and (a.related = b.related and a.AcceptedAnswer = 1)))

I'm not pretty sure what is the purpose of the SET clause (id =5)
anyway this way avoids the use of the cross join provided that you
don't use the table "x" to get something beyond the "related" items.
UPDATE tbname
SET
AcceptedAnswer = ( id = 5 )
WHERE
#THIS IS EQUIVALENT TO THE JOIN CLAUSE
id IN ( SELECT related FROM tbname WHERE id = 5 )
#THIS IS THE CONDITION 1 POINTING tnname
AND author_id = 29
#THIS IS THE CONDITION 2 POINTING tbname
AND (
( amount IS NOT NULL
AND NOT AcceptedAnswer = 1
) OR amount IS NOT NULL
)
;

Related

Select first row for each combination

I'm trying select the rows in which user_from exists as user_to in other row/s which have been created later.
This query gives me the rows.
SELECT A
FROM db.table A
LEFT JOIN db.table B
ON A.user_to = B.user_from
AND A.user_from = B.user_to
AND A.createdAt < B.createdAt
WHERE B.user_to IS NOT NULL AND B.user_from IS NOT NULL;
However, I want to get just the first row for each combination of user_to/user_from.
E.g. If there are some rows like:
user_to = 1, user_from = 2
user_to = 2, user_from = 1
user_to = 2, user_from = 1
user_to = 1, user_from = 2
I want to get just the first created one (defined like createdAt).
I've tried using GROUP BY user_from, but this exclude all other combinations with each user_from.
I think Use DISTINCT in the SELECT query .
SELECT DISTINCT A
FROM db.table A
LEFT JOIN db.table B
ON A.user_to = B.user_from
AND A.user_from = B.user_to
AND A.createdAt < B.createdAt
WHERE B.user_to IS NOT NULL AND B.user_from IS NOT NULL;
It seems I had earlier misunderstood the requirement.
Assuming no two 'createdAt' values are exactly the same, you could use this:
select a.* from
A a
where NOT EXISTS (SELECT *
FROM A b
WHERE (
(
(a.to_ = b.from_ AND a.from_ = b.to_)
OR
(a.to_ = b.to_ AND a.from_ = b.from_ AND a.c <> b.c)
)
AND
b.c < a.c
)
);

MySQL group by kills the query performance

I have MySQL query currently selecting and joining 13 tables and finally grouping ~60k rows. The query without grouping takes ~0ms but with grouping the query time increases to ~1.7sec. The field, which is used for grouping is primary field and is indexed. Where could be the issue?
I know group by without aggregate is considered invalid query and bad practise but I need distinct base table rows and can not use DISTINCT syntax.
The query itself looks like this:
SELECT `table_a`.*
FROM `table_a`
LEFT JOIN `table_b`
ON `table_b`.`invoice` = `table_a`.`id`
LEFT JOIN `table_c` AS `r1`
ON `r1`.`invoice_1` = `table_a`.`id`
LEFT JOIN `table_c` AS `r2`
ON `r2`.`invoice_2` = `table_a`.`id`
LEFT JOIN `table_a` AS `i1`
ON `i1`.`id` = `r1`.`invoice_2`
LEFT JOIN `table_a` AS `i2`
ON `i2`.`id` = `r2`.`invoice_1`
JOIN `table_d` AS `_u0`
ON `_u0`.`id` = 1
LEFT JOIN `table_e` AS `_ug0`
ON `_ug0`.`user` = `_u0`.`id`
JOIN `table_f` AS `_p0`
ON ( `_p0`.`enabled` = 1
AND ( ( `_p0`.`role` < 2
AND `_p0`.`who` IS NULL )
OR ( `_p0`.`role` = 2
AND ( `_p0`.`who` = '0'
OR `_p0`.`who` = `_u0`.`id` ) )
OR ( `_p0`.`role` = 3
AND ( `_p0`.`who` = '0'
OR `_p0`.`who` = `_ug0`.`group` ) ) ) )
AND ( `_p0`.`action` = '*'
OR `_p0`.`action` = 'read' )
AND ( `_p0`.`related_table` = '*'
OR `_p0`.`related_table` = 'table_name' )
JOIN `table_a` AS `_e0`
ON ( ( `_p0`.`related_id` = 0
OR `_p0`.`related_id` = `_e0`.`id`
OR `_p0`.`related_user` = `_e0`.`user`
OR `_p0`.`related_group` = `_e0`.`group` )
OR ( `_p0`.`role` = 0
AND `_e0`.`user` = `_u0`.`id` )
OR ( `_p0`.`role` = 1
AND `_e0`.`group` = `_ug0`.`group` ) )
AND `_e0`.`id` = `table_a`.`id`
JOIN `table_d` AS `_u1`
ON `_u1`.`id` = 1
LEFT JOIN `table_e` AS `_ug1`
ON `_ug1`.`user` = `_u1`.`id`
JOIN `table_f` AS `_p1`
ON ( `_p1`.`enabled` = 1
AND ( ( `_p1`.`role` < 2
AND `_p1`.`who` IS NULL )
OR ( `_p1`.`role` = 2
AND ( `_p1`.`who` = '0'
OR `_p1`.`who` = `_u1`.`id` ) )
OR ( `_p1`.`role` = 3
AND ( `_p1`.`who` = '0'
OR `_p1`.`who` = `_ug1`.`group` ) ) ) )
AND ( `_p1`.`action` = '*'
OR `_p1`.`action` = 'read' )
AND ( `_p1`.`related_table` = '*'
OR `_p1`.`related_table` = 'table_name' )
JOIN `table_g` AS `_e1`
ON ( ( `_p1`.`related_id` = 0
OR `_p1`.`related_id` = `_e1`.`id`
OR `_p1`.`related_user` = `_e1`.`user`
OR `_p1`.`related_group` = `_e1`.`group` )
OR ( `_p1`.`role` = 0
AND `_e1`.`user` = `_u1`.`id` )
OR ( `_p1`.`role` = 1
AND `_e1`.`group` = `_ug1`.`group` ) )
AND `_e1`.`id` = `table_a`.`company`
WHERE `table_a`.`date_deleted` IS NULL
AND `table_a`.`company` = 4
AND `table_a`.`type` = 1
AND `table_a`.`date_composed` >= '2016-05-04 14:43:55'
GROUP BY `table_a`.`id`
The ORs kill performance.
This composite index may help: INDEX(company, type, date_deleted, date_composed).
LEFT JOIN table_b ON table_b.invoice = table_a.id seems to do absolutely nothing other than slow down the processing. No fields of table_b are used or SELECTed. Since it is a LEFT join, it does not limit the output. Etc. Get rid if it, or justify it.
Ditto for other joins.
What happens with JOIN and GROUP BY: First, all the joins are performed; this explodes the number of rows in the intermediate 'table'. Then the GROUP BY implodes the set of rows.
One technique for avoiding this explode-implode sluggishness is to do
SELECT ...,
( SELECT ... ) AS ...,
...
instead of a JOIN or LEFT JOIN. However, that works only if there is zero or one row in the subquery. Usually this is beneficial when an aggregate (such as SUM) can be moved into the subquery.
For further discussion, please include SHOW CREATE TABLE.

Query for updating a table value based on the total of a column found in multiple tables

I'm trying to write a query to sum all the prc values from different tables and UPDATE it to main_trans.tot.
ex. tot value for TR01 should be 30 adding all TR01 prcs from two tables.
Table main_trans:
| id | tot |
|TR01| 30 |
|TR02| 5 |
TABLE sub_trans_a:
| id | prc |
|TR01| 10 |
|TR01| 10 |
TABLE sub_trans_b:
| id | prc |
|TR01| 10 |
|TR02| 5 |
I don't know how to write it so that it would automatically update all rows based on their id. So far, my query does the work if i specifically write the value of the id column:
UPDATE main_trans SET tot =
(SELECT SUM(prc) FROM sub_trans_a WHERE id = 'TR01')
+ (SELECT SUM(prc) FROM sub_trans_b WHERE id = 'TR01')
WHERE id = 'TR01'
You can use a join in your update query with a union set
UPDATE main_trans m
join
(SELECT id,SUM(prc) prc
FROM (
SELECT id,SUM(prc) prc FROM sub_trans_a WHERE id = 'TR01'
union all
SELECT id,SUM(prc) prc FROM sub_trans_b WHERE id = 'TR01'
) t1
) t
on(t.id = m.id)
SET m.tot = t.prc
WHERE m.id = 'TR01'
Also if you have same structure for sub_trans_a and sub_trans_a so why 2 tables why not just a single table or with a single column for the type as type a or type b
See Demo
Or if you want to update your whole main_trans table without providing id values you can do so by adding a group by in query
UPDATE main_trans m
join
(SELECT id,SUM(prc) prc
FROM (
SELECT id,SUM(prc) prc FROM sub_trans_a group by id
union all
SELECT id,SUM(prc) prc FROM sub_trans_b group by id
) t1 group by id
) t
on(t.id = m.id)
SET m.tot = t.prc
See Demo 2
Edit a good suggestion by Andomar you can simplify inner query as
UPDATE main_trans m
join
(SELECT id,SUM(prc) prc
FROM (
SELECT id,prc FROM sub_trans_a
union all
SELECT id,prc FROM sub_trans_b
) t1 WHERE id = 'TR01'
) t
on(t.id = m.id)
SET m.tot = t.prc
WHERE m.id = 'TR01'
If you want to do the update for all at the same time, just use correlated subqueries:
UPDATE main_trans mt
SET tot = ( (SELECT SUM(prc) FROM sub_trans_a a WHERE a.id = mt.id) +
(SELECT SUM(prc) FROM sub_trans_b b WHERE b.id = mt.id)
);
If one or both tables may not have values, then the result might be NULL. You can fix this using COALESCE():
UPDATE main_trans mt
SET tot = ( COALESCE((SELECT SUM(prc) FROM sub_trans_a a WHERE a.id = mt.id), 0) +
COALESCE((SELECT SUM(prc) FROM sub_trans_b b WHERE b.id = mt.id), 0)
);

MySQL UNION: Which method is quickest

Normally i go for smallest code but im currently building a payment gateway which could potentially handle millions of connections a day so every tweak in code to help performance I build in, however i have a question that can only really be answered by the mysql gurus..
with a union, is it best to filter each side of the union with where clauses, or to collate the records and then do the filtering on the union set..
which one is quickest:
1)
select * from (
select * from payment_routes
where channel_id is null
and currency_id != v_currency_id
and card_id = v_card_id
and enabled = 1
union
select * from payment_routes
where channel_id = p_channel_id
and currency_id = v_currency_id
and card_id = v_card_id
and enabled = 1
) t1
join gateways g on t1.gateway_id = g.id
where t1.currency_id = v_currency_id
;
2) or is this smaller code, as quick/quicker (but not slower)
select * from (
select * from payment_routes
where channel_id is null
and currency_id != v_currency_id
union
select * from payment_routes
where channel_id = p_channel_id
and currency_id = v_currency_id
) t1
join gateways g on t1.gateway_id = g.id
where t1.currency_id = v_currency_id
and t1.card_id = v_card_id
and t1.enabled = 1
;
logically, i would say (1) is quicker as each union set is filtered so less records need to be combined.
UPDATE
I found a better way to write this without a union
select r.*, g.`name` 'gateway_name'
from payment_routes r
join gateways g on r.gateway_id = g.id
where (
(channel_id is null and currency_id != v_sell_currency_id)
or
(channel_id = p_channel_id and currency_id = v_sell_currency_id)
)
and card_id = v_card_id
and currency_id = v_pay_currency_id
and enabled = 1

When doing a UNION in mysql how can I do a where on the results

Hi I am doing a union over several tables. It's a little long but works!
(SELECT user_id,added_date,group_id,'joined',0,0,'' FROM group_members WHERE status = 1)
UNION
(SELECT user_id,added_date,object_id,'made a comment',0,0,'' FROM comments WHERE object_type = 11 AND status = 1)
UNION
(SELECT user_id,added_date,group_id,'made the event',1,group_calendar_id,title FROM group_calendars WHERE status = 1)
UNION
(SELECT comments.user_id,comments.added_date,group_calendars.group_id,'made a comment on the event',1,group_calendar_id,'' FROM group_calendars
INNER JOIN comments ON group_calendars.group_calendar_id = comments.object_id WHERE group_calendars.status = 1 AND comments.status = 1 AND object_type = 10
)
UNION
(SELECT user_id,pd.added_date,pd.object_id,'uploaded a photo',2,pd.photo_data_id,
(SELECT varchar_val FROM photo_data WHERE data_id = 1 AND photo_data.photo_id = photos.photos_id AND object_type = 3 AND object_id = pd.object_id)
FROM photo_data pd
INNER JOIN photos ON photos.photos_id = pd.photo_id
WHERE photos.photo_status = 1 AND pd.status = 1 AND pd.data_id = 0 AND pd.object_type = 3
)
UNION
(SELECT cp.user_id,cp.added_date,cp.object_id,'made a comment on the photo',2,pd.photo_data_id,
(SELECT varchar_val FROM photo_data WHERE data_id = 1 AND photo_data.photo_id = photos.photos_id AND object_type = 3 AND object_id = pd.object_id)
FROM comments cp
INNER JOIN photo_data pd ON pd.photo_data_id = cp.object_id
INNER JOIN photos ON photos.photos_id = pd.photo_id
WHERE cp.object_type = 8 AND cp.status = 1 AND pd.status = 1 AND pd.data_id = 0 AND photos.photo_status = 1 AND pd.object_type = 3
)
UNION
(SELECT user_id,added_date,group_id,'made a topic',3,forum_topic_id,title FROM forum_topics WHERE forum_categories_id = ".GROUP_FORUM_CATEGORY." AND group_id > 0 AND status = 1)
UNION
(SELECT forum_comments.user_id,forum_comments.added_date,group_id,'made a comment on the topic',3,forum_comments.forum_topic_id,title FROM forum_comments
INNER JOIN forum_topics ON forum_comments.forum_topic_id = forum_topics.forum_topic_id
WHERE forum_topics.forum_categories_id = 16 AND forum_topics.group_id > 0 AND forum_topics.status = 1 AND forum_comments.status = 1
)
This gets all the activity from a set of groups. My question is at the end I want to make sure that the group is active.
So at the end want to do something like WHERE (SELECT COUNT(1) FROM groups g WHERE g.group_id = group_id AND status = 1) = 1
Is there any way of doing that?
i'd suggest to store it to a view or temporary table and query the view then. i know you will have two calls then, but it's actually faster in mysql that way.