MySQL subquery in where clause - mysql

I have a MySQL query which goes like this:
select
*,
(select meta_value
from nord_usermeta m
where meta_key = 'firm' and user_id = s.user_id
limit 1)
as firm,
(select meta_value f
rom nord_usermeta m
where meta_key = 'first_name' and user_id = s.user_id
limit 1)
as first_name,
(select meta_value
from nord_usermeta m
where meta_key = 'last_name' and user_id = s.user_id
limit 1)
as last_name
from nord_submissions s
order by created_at desc
Now I need to narrow it down with a where clause like this:
select
*,
(select meta_value
from nord_usermeta m
where meta_key = 'firm' and user_id = s.user_id
limit 1)
as firm,
(select meta_value
from nord_usermeta m
where meta_key = 'first_name' and user_id = s.user_id
limit 1)
as first_name,
(select meta_value
from nord_usermeta m
where meta_key = 'last_name' and user_id = s.user_id
limit 1)
as last_name
from nord_submissions s
where firm like '%DG%'
order by created_at desc
The problem obviously is in way i use subquery but i cant find a way to reference it in where clause.
Please help.

Why go so Long...try with SELF JOIN ( joining same tables in query )..see below (not tested)
SELECT ns.*,
um1.meta_value AS firmName,
um2.meta_value AS firstName,
um3.meta_value AS lastName
FROM nord_submission ns
INNER JOIN nord_usermeta um1 USING(id) AND meta_key = 'firm'
INNER JOIN nord_usermeta um2 USING(id) AND meta_key = 'first_name'
INNER JOIN nord_usermeta um3 USING(id) AND meta_key = 'last_name'
WHERE um1.meta_value LIKE '%DG%'
ORDER BY ns.created_at
Note: if both table have different column name , by which u refrence each other, then you can replace USING(id) with ns.user_id=um1.user_id and so on

If you're going to use keys rather than columns you'll have to let the application sort it out. Using so many sub-queries you'll see a performance hit.
SELECT *
FROM nord_submissions
INNER JOIN nord_usermeta
USING (user_id)
WHERE (meta_key='last_name' or meta_key='first_name' or (meta_key='firm' and meta_value LIKE '%DG%') )
ORDER BY created_at desc

Related

Need help in optimizing the query

SELECT
u.ID,
u.display_name as name,
u.user_email as email,
u.user_registered as registered,
(
select
meta_value
from
wp_usermeta
where
user_id = u.ID
and meta_key = 'mobileno'
limit
1
) as mobileno,
(
select
meta_value
from
wp_usermeta
where
user_id = u.ID
and meta_key = 'referral_id'
limit
1
) as referral_id,
(
SELECT
COUNT(meta_value) AS total_ref
FROM
wp_usermeta
WHERE
meta_key = 'ambassador_ref_id'
AND meta_value = referral_id
) as total_ref,
wc.task_no,
wc.status,
wc.uploaded_date,
wc.reject_reason
FROM
wp_users u,
wp_ca_tasks wc
WHERE
u.ID = wc.user_id
GROUP BY
wc.user_id,
wc.task_no;
In the above code, if we remove the block
(
SELECT
COUNT(meta_value) AS total_ref
FROM
wp_usermeta
WHERE
meta_key = 'ambassador_ref_id'
AND meta_value = referral_id
) as total_ref
the code executes a bit faster. But if we add that block, it basically gets stuck in Loading...
Currently using MySQL 5.7.
How can I optimize the above block of code to make the execution faster?
Ah, the notorious WordPress meta-table slowdown.
Change the comma-joins (FROM a,b WHERE a.ID = b.user_id) to proper JOINs.
Eliminate your dependent subqueries and replace them with JOINed subqueries.
A quicker query might look like this.
SELECT
u.ID,
u.display_name as name,
u.user_email as email,
u.user_registered as registered,
/* from the joined tables
mobilno.meta_value as mobileno,
referral_id.meta_value as referral_id,
counts.total_ref
wc.task_no,
wc.status,
wc.uploaded_date,
wc.reject_reason
FROM
wp_users u
JOIN wp_ca_tasks wc ON u.ID = wc.user_id
LEFT JOIN wp_usermeta mobilno ON mobilno.user_id = u.ID
AND meta_key = 'mobilno'
LEFT JOIN wp_usermeta referral_id ON referral_id.user_id = u.ID
AND meta_key = 'referral_id'
LEFT JOIN (
SELECT COUNT(*) total_ref,
meta_value referral_id
FROM wp_postmeta
WHERE meta_key = 'ambassador_ref_id'
GROUP BY meta_value)
) counts ON counts.referral_id = referral_id.meta_value
GROUP BY wc.user_id, wc.task_no;
The trick is to avoid repeating the queries buried in the SELECT statement over and over. LEFT JOINing them helps.
And, your WordPress tables need better indexes. Look at this. https://wordpress.org/plugins/index-wp-mysql-for-speed/
In addition to what O.Jones says, wc needs
INDEX(user_id, task_no)
However, the GROUP BY probably violates "only_full_group_by". That is, for a given user_id and task_no, you will get random values for other columns fetched from wp_ca_tasks.

Optimize Query - Select COUNT and SUM from the same subquery

I tried to find the solution to this on Stackoverflow, maybe my wording is wrong.
I have a query which takes to long to execute. I am sure there are simple ways to improve it. For example, I use the same sub-query twice for displaying two different columns (sum and count) but encountered several errors while trying to solve it on my own.
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'first_name' limit 1) as firstName,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'last_name' limit 1) as lastName,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'billing_phone' limit 1) as billingPhone,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'shipping_phone' limit 1) as shippingPhone,
(SELECT COUNT(meta_value) from wp_postmeta WHERE meta_key = '_order_total' and post_id IN (select post_id from wp_postmeta where meta_value = u.ID and meta_key = '_customer_user')) as orderCount,
(SELECT SUM(meta_value) from wp_postmeta WHERE meta_key = '_order_total' and post_id IN (select post_id from wp_postmeta where meta_value = u.ID and meta_key = '_customer_user')) as moneySpent
FROM wp_users u;
The problem with your query is that you have nested correlated sub queries. Correlated sub queries are a huge hit to SQL performance. In this case you want to try use joins with aggregation (group by).
I don't understand the intricacies of the schema your database, so I'm not quite sure why you're limiting the sub queries for firstName etc to 1 record. I can edit the answer to accommodate that if you give more detail.
I would suggest you try something like this:
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
mfn.meta_value as firstName,
mln.meta_value as lastName,
mbp.meta_value as billingPhone,
msp.meta_value as shippingPhone,
COUNT(pval.meta_value) as orderCount,
SUM(pval.meta_value) as moneySpent
FROM wp_users u
JOIN wp_usermeta as mfn ON u.ID = mfn.user_id AND mfn.meta_key = 'first_name'
JOIN wp_usermeta as mln ON u.ID = mln.user_id AND mln.meta_key = 'last_name'
JOIN wp_usermeta as mbp ON u.ID = mbp.user_id AND mbp.meta_key = 'billing_phone'
JOIN wp_usermeta as msp ON u.ID = msp.user_id AND msp.meta_key = 'shipping_phone'
JOIN wp_postmeta as p ON p.meta_value = u.ID AND p.meta_key = '_customer_user'
JOIN wp_postmeta as pval ON pval.post_id = p.post_id AND pval.meta_key = '_order_total'
GROUP BY u.ID,
u.user_email,
u.user_login,
u.user_registered,
mfn.meta_value,
mln.meta_value,
mbp.meta_value,
msp.meta_value
As per #O.Jones's comment, consider using LEFT JOINS instead of INNER JOINS so that the query still returns results where a meta_value is missing.
#Timur, as per your question in the comments of the first answer; you'd have to do something like this. You still need joins but you don't need as many for the wp_usermeta table. You will still need to join to wp_postmeta twice, because you can't do a join on the '_customer_user' meta_key and simultaneously retrieve the '_order_total' meta_key in a single join.
Just a note on the MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value END) logic; it's the equivalent to MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value ELSE NULL END).
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
MAX(CASE WHEN um.meta_key = 'first_name' THEN um.meta_value END) AS firstName,
MAX(CASE WHEN um.meta_key = 'last_name' THEN um.meta_value END) AS lastName,
MAX(CASE WHEN um.meta_key = 'billing_phone' THEN um.meta_value END) AS billingPhone,
MAX(CASE WHEN um.meta_key = 'shipping_phone' THEN um.meta_value END) AS shippingPhone,
COUNT(pval.meta_value) AS orderCount,
SUM(pval.meta_value) AS moneySpent
FROM wp_users AS u
LEFT JOIN wp_usermeta AS um ON u.ID = um.user_id
LEFT JOIN wp_postmeta AS pm ON u.ID = pm.meta_value AND pm.meta_key = '_customer_user'
LEFT JOIN wp_postmeta AS pval ON pm.post_id = pval.post_id AND pval.meta_key = '_order_total'
WHERE (um.meta_key IN ('first_name', 'last_name', 'billing_phone', 'shipping_phone')
OR um.meta_key IS NULL)
GROUP BY u.ID,
u.user_email,
u.user_login,
u.user_registered;
I'm not 100% sure on the logic. Let me know if it needs tweaking
Here subquery has been introduced for minimizing multiple times using a single table and removing few column from GROUP BY clause for optimization purpose. Use WHERE clause in every subquery if need a particular user/ few users or any searching condition. If subquery returns no data but needed user info then use LEFT JOIN instead of INNER JOIN.
-- MySQL
SELECT u.ID
, u.user_email AS mail
, u.user_login AS userName
, u.user_registered AS signUpDate
, t.firstName
, t.lastName
, t.billingPhone
, t.shippingPhone
, r.orderCount
, r.moneySpent
FROM wp_users u
INNER JOIN (SELECT user_id
, MAX(CASE WHEN meta_key = 'first_name' THEN meta_value END) firstName
, MAX(CASE WHEN meta_key = 'last_name' THEN meta_value END) lastName
, MAX(CASE WHEN meta_key = 'billing_phone' THEN meta_value END) billingPhone
, MAX(CASE WHEN meta_key = 'shipping_phone' THEN meta_value END) shippingPhone
FROM wp_usermeta
WHERE meta_key IN ('first_name', 'last_name', 'billing_phone', 'shipping_phone')
GROUP BY user_id) t
ON u.ID = t.user_id
INNER JOIN (SELECT p.meta_value
, COUNT(pval.meta_value) as orderCount
, SUM(pval.meta_value) as moneySpent
FROM wp_postmeta as p
INNER JOIN wp_postmeta as pval
ON p.post_id = pval.post_id
AND p.meta_key = '_customer_user'
AND pval.meta_key = '_order_total'
GROUP BY p.meta_value) r
ON u.ID = r.meta_value;

Filter users using by two different user meta

I have a user meta field called country (string) and another one called is_blocked (binary). What I need is returns the number of non-blocked users per country selecting the user registered date too.
With this query, I managed to get the values without considering the is_blocked field:
SELECT meta_value AS 'country', COUNT(meta_value) AS 'total'
FROM wp_usermeta INNER JOIN
wp_users
ON wp_usermeta.user_id = wp_users.ID
WHERE meta_key = 'user_country' AND
meta_value <> '' AND
DATE_FORMAT(wp_users.user_registered, '%Y-%m') = '2021-01'
GROUP BY meta_value
ORDER BY total DESC
But I can't just select only non-bloked users. What I try:
SELECT meta_value AS 'country', COUNT(meta_value) AS 'total'
FROM wp_usermeta INNER JOIN
wp_users
ON wp_usermeta.user_id = wp_users.ID
WHERE meta_key = 'user_country' AND
meta_value <> '' AND
DATE_FORMAT(wp_users.user_registered, '%Y-%m') = '2021-01' AND
(wp_usermeta.meta_key = 'user_blocked' AND
CAST(wp_usermeta.meta_value AS BINARY) != '1'
)
GROUP BY meta_value
ORDER BY total DESC
You need another join or exists:
SELECT umc.meta_value AS country, COUNT(umb.user_id) AS total
FROM wp_users u INNER JOIN
wp_usermeta umc
ON umc.user_id = u.ID AND
umc.meta_key = 'user_country' AND
umc.meta_value <> '' LEFT JOIN
wp_usermeta umb
ON umb.user_id = u.ID AND
umb.meta_key = 'user_blocked' AND
CAST(umb.meta_value AS BINARY) <> '1'
WHERE DATE_FORMAT(u.user_registered, '%Y-%m') = '2021-01'
GROUP BY umc.meta_value
ORDER BY total DESC;
This should give you 0 values, if you have countries with no unblocked users. This also assumes that the two meta keys are not repeated for a single user.
You can use conditional aggregation in the table wp_usermeta to get each user and country which meet your conditions:
SELECT user_id,
MAX(CASE WHEN meta_key = 'user_country' THEN meta_value END) country
FROM wp_usermeta
GROUP BY user_id
HAVING country <> ''
AND MAX(CASE WHEN meta_key = 'user_blocked' THEN meta_value END) + 0 <> 1
and then join it to the table wp_users:
SELECT m.country, COUNT(*) total
FROM wp_users u
INNER JOIN (
SELECT user_id,
MAX(CASE WHEN meta_key = 'user_country' THEN meta_value END) country
FROM wp_usermeta
GROUP BY user_id
HAVING country <> ''
AND MAX(CASE WHEN meta_key = 'user_blocked' THEN meta_value END) + 0 <> 1
) m ON m.user_id = u.ID
WHERE DATE_FORMAT(u.user_registered, '%Y-%m') = '2021-01'
GROUP BY m.country
ORDER BY total DESC

SELECT with SELECT within IF statement with saving state

I try to found some solution to avoid third SELECT, but I don't even know, how google it.
My try (working, but looks ugly):
SELECT
u.*,
IF(
(SELECT meta_value from usermeta WHERE user_id = u.ID AND meta_key = 'first_name') = '',
'John',
(SELECT meta_value from usermeta WHERE user_id = u.ID AND meta_key = 'first_name')
) as first_name,
(SELECT meta_value from usermeta WHERE user_id = u.ID AND meta_key = 'reputation') as reputation
FROM wp_users AS u
And I'd like something like this:
SELECT
u.*,
IF(
(SELECT meta_value from usermeta WHERE user_id = u.ID AND meta_key = 'first_name') = '',
'John',
SOME_STATE0
) as first_name,
(SELECT meta_value from usermeta WHERE user_id = u.ID AND meta_key = 'reputation') as reputation
FROM wp_users AS u
Join the tables rather than using a subquery.
SELECT u.*, IF(meta_value = '' OR meta_value IS NULL, 'John', meta_value) AS first_name
FROM wp_users AS u
LEFT JOIN usermeta AS m ON m.user_id = u.id AND m.meta_key = 'first_name'

Reduce sql Query execution time from 3.349s

This query is working perfectly but uses 3.3493s to execute, please help me check through it to see if there is a way to better optimize it.
SELECT u.ID,
(SELECT meta_value FROM wpcg_usermeta WHERE meta_key = 'first_name' AND user_id = u.ID ) AS firstname,
(SELECT meta_value FROM wpcg_usermeta WHERE meta_key = 'last_name' AND user_id = u.ID ) AS lastname,
(SELECT meta_value FROM wpcg_usermeta WHERE meta_key = 'user_avatar_thumb' AND user_id = u.ID ) AS avatarurl,
(SELECT COUNT(m.user_id) FROM wpcg_usermeta AS m , wpcg_users AS uz WHERE m.user_id = uz.ID AND m.meta_key = 'user_parent' AND m.meta_value = u.ID AND u.user_registered BETWEEN DATE_SUB(NOW(), INTERVAL 7 DAY) AND NOW()) AS referral_users
FROM wpcg_users AS u
WHERE (SELECT COUNT( user_id ) FROM wpcg_usermeta WHERE meta_key = 'user_parent' AND meta_value = u.ID ) > 0
ORDER BY referral_users DESC LIMIT 15 OFFSET 0
Consider restructuring your query
Select col1, col2, col3, ...
FROM wpcg_users AS u
JOIN wpcg_usermeta m on u.ID = m.user_id
where meta_key in ('first_name', 'last_name', ...)
You have a number of subqueries which can be eliminated through re-structuring the query which can improve performance. When you have a sub query like this in the select it is run for every row.
First of all, provide meta of used table and type of database- columns, indexes, type of tables and etc (its look like as MySql, but its may be false). Try split this request on some simple requests. Then try to get statistics about each requests.
Also try reduce calls to some table, doing nested sql-c.
Sorry for my English. I’m studying it at now)