The way you phrased the query looks good to me for MySQL 5.7. But the OR
in the subquery is a performance killer.
I would recommend the following index, so the correlated subquery executed quickly:
phone_call(device_user_id, customer_id, id)
You might try and switch the first two columns in the index to see if one version or another has better effect.
Another thing you could try is change the subquery to use a sort and a row limiting clause rather than aggregation (with the same above index in place). There is guarantee it will improve things, but it is worth trying:
LEFT JOIN phone_call pc ON pc.id = (
SELECT pc2.id
FROM phone_call pc2
WHERE
pc2.device_user_id = c.device_user_id
OR pc2.customer_id = c.customer_id
ORDER BY pc2.id
LIMIT 1
)
Finally, another idea is to split the subquery into two to avoid the OR
:
LEFT JOIN phone_call pc ON pc.id = (
SELECT MAX(id)
FROM (
SELECT MAX(pc2.id)
FROM phone_call pc2
WHERE pc2.device_user_id = c.device_user_id
UNION ALL
SELECT MAX(pc3.id)
FROM phone_call pc3
WHERE pc3.customer_id = c.customer_id
) t
)
Or without intermediate aggregation:
LEFT JOIN phone_call pc ON pc.id = (
SELECT MAX(id)
FROM (
SELECT pc2.id
FROM phone_call pc2
WHERE pc2.device_user_id = c.device_user_id
UNION ALL
SELECT pc3.id
FROM phone_call pc3
WHERE pc3.customer_id = c.customer_id
) t
)
For the last two queries, you would need two indexes:
phone_call(device_user_id, id)
phone_call(customer_id, id)
EDIT
The above solutions using union all
require MySQL 8.0 - in earlier versions, they fail because the subqueries are too deeply nested to reference columns from the outer query. So, another alternative is IN
:
LEFT JOIN phone_call pc ON pc.id IN (
SELECT pc2.id
FROM phone_call pc2
WHERE pc2.device_user_id = c.device_user_id
UNION ALL
SELECT pc3.id
FROM phone_call pc3
WHERE pc3.customer_id = c.customer_id
)
This can also be phase with EXISTS
- which I like better, because the predicates explicitly match the indexes definition, so it should be an easy decision for MySQL to use them:
LEFT JOIN phone_call pc ON EXISTS (
SELECT 1
FROM phone_call pc2
WHERE pc2.device_user_id = c.device_user_id AND pc2.id = pc.id
UNION ALL
SELECT 1
FROM phone_call pc3
WHERE pc3.customer_id = c.customer_id AND pc3.id = pc.id
)
Again, this works under the assumption that you have the two following, multicolumn indexes:
phone_call(device_user_id, id)
phone_call(customer_id, id)
You can create the indexes as follow:
create index idx_phone_call_device_user on phone_call(device_user_id, id);
create index idx_phone_call_customer on phone_call(customer_id, id);