How to optimise below sql query? - mysql

What I'm doing here is getting the reservations and the user which are duplicate. Here reservation ticket can not be printed twice. If a user prints a ticket tracker table updates with that record. If a user prints the same ticket twice it's marked as a duplicate. What subquery does here is return some reservation ids which are marked as duplicates.
SELECT t1.id AS res_id,
t1.tx_id,
t1.tx_date,
t1.bx_date,
t1.method,
t1.theater_id,
t1.showtime_id,
t1.category_id,
t1.amount,
t1.fname,
t1.status,
t1.mobile,
u.username,
t2.*
FROM `reservation` AS t1
INNER JOIN
( SELECT *
FROM `tracker`
WHERE reservation_id IN
( SELECT reservation_id
FROM `tracker`
GROUP BY reservation_id HAVING ( METHOD = 1
AND TYPE = 0
AND COUNT(*) > 1 )
OR ( METHOD = 1
AND TYPE = 1
AND COUNT(*) > 1 )
OR ( METHOD = 2
AND TYPE = 2
AND COUNT(*) > 0 )
OR ( METHOD = 3
AND TYPE = 0
AND COUNT(*) > 0 )
OR ( METHOD = 3
AND TYPE = 1
AND COUNT(*) > 1 )
OR ( METHOD = 3
AND TYPE = 3
AND COUNT(*) > 0 )) ) AS t2 ON t1.id = t2.reservation_id
INNER JOIN `users` AS u ON u.id = t2.user_id
WHERE t2.resolved = 0
AND t2.duplicate = 1
ORDER BY t2.issue_date DESC, t1.id DESC
EXPLAIN Command of the above query.
What should I do? If I'm index which keys should I use? How can I decide which keys to index? I know subquery slows me down What procedures should I follow to eliminate the slowness?

In MySQL, exists subqueries are often faster than in subqueries. You might try:
SELECT t1.id AS res_id, t1.tx_id, t1.tx_date, t1.bx_date,t1.method, t1.theater_id, t1.showtime_id,
t1.category_id, t1.amount, t1.fname, t1.status, t1.mobile, u.username, t2.*
FROM `reservation` t1 INNER JOIN
(SELECT *
FROM `tracker` t
WHERE EXISTS (SELECT 1
FROM `tracker` t3
where t3.reservation_id = t.reservation_id
GROUP BY reservation_id
HAVING (METHOD = 1 AND TYPE = 0 AND COUNT(*) > 1) OR
(METHOD = 1 AND TYPE = 1 AND COUNT(*) > 1) OR
(METHOD = 2 AND TYPE = 2 AND COUNT(*) > 0) OR
(METHOD = 3 AND TYPE = 0 AND COUNT(*) > 0) OR
(METHOD = 3 AND TYPE = 1 AND COUNT(*) > 1) OR
(METHOD = 3 AND TYPE = 3 AND COUNT(*) > 0)
)
) t2
ON t1.id = t2.reservation_id INNER JOIN
`users` AS u ON u.id = t2.user_id
WHERE t2.resolved = 0 AND t2.duplicate = 1
ORDER BY t2.issue_date DESC, t1.id DESC
I notice the subquery is using Hidden Columns in the having clause. It may not be doing what you expect. Normally, the query would include method and type in the group by clause or have an expression such as max(Method).

Related

How to acces outer column in mysql subquery?

i have query
SELECT t1.account AS main, t1.id, tmp.* FROM account.account t1 LEFT JOIN
(
SELECT
tmp1.account,
tmp1.lot - tmp2.lot AS nett
FROM
( SELECT account, SUM( lot ) AS lot
FROM orders WHERE market_date = "2020-07-20"
AND `transaction` = 1 AND account = t1.id ) AS
tmp1
LEFT JOIN ( SELECT account, SUM( lot ) AS lot
FROM orders WHERE market_date = "2020-07-20"
AND `transaction` = 2 AND account = t1.id ) AS
tmp2 ON tmp1.account = tmp2.account
) tmp ON tmp.account = t1.id;
and the result is
1054 - Unknown column 't1.id' in 'where clause', Time: 0.001000s
how to access t1.id current subquery select ?
You may try just using a join to a single level subquery on orders:
SELECT
a.account AS main,
a.id,
COALESCE(o.nett, 0) AS nett
FROM account a
LEFT JOIN
(
SELECT
account,
SUM(CASE WHEN `transaction` = 1 THEN lot END) -
SUM(CASE WHEN `transaction` = 2 THEN lot END) AS nett
FROM orders
WHERE market_date = '2020-07-20'
GROUP BY account
) o
ON o.account = a.account;

Sort the table in sequential conditions?

I have a users table with 5 columns, id, age, is_premium, is_male, is_customer where id is the primary key.
First statement I do is. This statement has the potential of returning 0 rows:
SELECT * FROM users
WHERE is_premium = 1 AND
is_name = 0 AND
is_customer = 0
Then ONLY from the rows I got from the above query, I want to find the person with the largest age.
SELECT * FROM <from the above query>
WHERE id = (SELECT MAX(ID) <from the above query>)
Question: How do make these 2 separate SQL statements into a single statement and what is the most efficient way of doing this?
why not directly:
SELECT *
FROM users
WHERE is_premium = 1
AND is_name = 0
AND is_customer = 0
ORDER BY age DESC, id ASC
LIMIT 1
for mysql version 8 and above you can also use common table expressions (CTE):
WITH D AS (
SELECT * FROM users
WHERE is_premium = 1
AND is_name = 0
AND is_customer = 0
)
SELECT *
FROM D
WHERE AGE = (SELECT MAX(AGE) FROM D)
ORDER BY ID
LIMIT 1
Assuming you have a primary key column called id, just move the query in a sub-query:
SELECT *
FROM users
WHERE id = (
SELECT id
FROM users
WHERE is_premium = 1 AND is_name = 0 AND is_customer = 0
ORDER BY age DESC
LIMIT 1
)
suppose you will have multiple users with same max() age
select * from users t1
inner join (
select max(age) maxage from users
where is_premium=1 and is_name=0 and is_customer=0) t2
on t2.maxage = t1.age
where is_premium=1 and is_name=0 and is_customer=0
or if you don't want to repeat your conditions.
select * from users t1
inner join(
select
(select max(t.age) from users t where t.id = t2.id) as maxage,
t2.id
from users t2
where is_premium=1 and is_name=0 and is_customer=0) t3 on t3.id = t1.id

How to use 'group by' while having a sub query in MySQL?

I have the following query:
SELECT c.text1, sum(c.num1), sum(c.num2), sum(c.num3),
(SELECT count(id) FROM table2 WHERE type = 1 AND txt = c.text1 AND spec_id = c.sp_id)
FROM table1 as c
WHERE c.type = 1
GROUP BY c.text1, c.sp_id
Is there a workaround to loose the c.sp_id from the GroupBy clause somehow? I know that if I remove it MySQL will return an error.
Or is there a way to group the results of this query by c.text1 only?
If I understand the problem correctly, you need to do two separate aggregations. This is one version of the query:
SELECT c.text1, c.sum1, c.sum2, c.sum3, t2.cnt
FROM (SELECT c.text1, sum(c.num1) as sum1, sum(c.num2) as cum2, sum(c.num3) as sum3
FROM table1 c
GROUP BY c.text1
) c LEFT JOIN
(SELECT txt, count(*) as cnt
FROM table2 t2
WHERE t2.type = 1 AND
EXISTS (SELECT 1
FROM table1 c2
WHERE t.txt = c2.txt AND c2.type = 1 AND
t.spec_id = c2.sp_id
)
) t2
ON t2.txt = c.text1
WHERE c.type = 1;

Limit on multiple table union

I've this mysql query with a limit on each select, but I want a limit of 1 on the complete query too. I've tried to build a Select around the whole query too, but it still goes through all the tables, even if there's a result in the first one so the duration is the same. I want the query to stop as soon as it gets a result. Is there any solution for this?
(SELECT count(*) FROM table1 i JOIN table1item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table2 i JOIN table2item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table3 i JOIN table3item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table4 i JOIN table4item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table5 i JOIN table5item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table6 i JOIN table6item it where it.columnId = 2 LIMIT 1)union
(SELECT count(*) FROM table7 i JOIN table7item it where it.columnId = 2 LIMIT 1)
Add UNION ALL to be sure you'll get results from all table if they are equal.
(SELECT count(*) FROM table1 i JOIN table1item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table2 i JOIN table2item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table3 i JOIN table3item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table4 i JOIN table4item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table5 i JOIN table5item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table6 i JOIN table6item it where it.columnId = 2) UNION ALL
(SELECT count(*) FROM table7 i JOIN table7item it where it.columnId = 2)
LIMIT 1
A workmate found a solution which really reduces the query time (0.25 instead of 0.75 in my test case):
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else (
SELECT
case
when count(*) > 0
then 1
else 0
end as result
FROM table1 i JOIN table1item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table2 i JOIN table2item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table3 i JOIN table3item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table4 i JOIN table4item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table5 i JOIN table5item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table6 i JOIN table6item it where it.fooId = 1 LIMIT 1
)
end as result
FROM table7 i JOIN table7item it where it.fooId = 1 LIMIT 1

Where IN to JOIN how?

Writing down this using JOIN ..how?
because this is very slow..
SELECT *
FROM table1
WHERE ID IN (SELECT ID
FROM table1
GROUP BY ID
HAVING COUNT(*) = 2
AND MAX(awaiting) = 1)
AND awaiting = 1
so, how can I write?
Here is the join version:
SELECT t1.*
FROM table1 t1 join
(SELECT ID
FROM table1
GROUP BY ID
HAVING COUNT(*) = 2 AND MAX(awaiting) = 1
) tsum
on t1.id = tsum.id
WHERE t1.awaiting = 1
SELECT t1.*
FROM table1 AS t1
INNER JOIN
(
SELECT ID
FROM table1
GROUP BY ID
HAVING COUNT(*) = 2
AND MAX(awaiting) = 1
) AS t2 ON t1.ID = t1.ID AND t1.awaiting = t2.awaiting
WHERE t1.awaiting = 1;
I guess awaiting is 0 or 1. If so in your inner query MAX(awaiting) = 1 is redundant because of WHERE statement awaiting = 1
Also in this case you can use the following query.
SELECT *
FROM table1 as T1
WHERE
awaiting = 1
AND
(SELECT count(*) FROM table1 WHERE ID=T1.ID)=2