优化查询 - Select 来自同一子查询的 COUNT 和 SUM
Optimize Query - Select COUNT and SUM from the same subquery
我试图在 Whosebug 上找到解决方案,也许我的措辞有误。
我有一个查询需要很长时间才能执行。我相信有一些简单的方法可以改进它。例如,我两次使用相同的子查询来显示两个不同的列(sum 和 count),但在尝试自行解决时遇到了几个错误。
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;
您的查询存在嵌套的相关子查询问题。相关子查询对 SQL 性能有很大影响。在这种情况下,您想尝试将连接与聚合结合使用 (group by
)。
我不明白你的数据库模式的复杂性,所以我不太清楚你为什么将 firstName
等的子查询限制为 1 条记录。如果您提供更多详细信息,我可以编辑答案以适应这一点。
我建议您尝试这样的操作:
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
根据 @O.Jones 的评论,考虑使用 LEFT JOINS 而不是 INNER JOINS,以便查询仍然 returns 结果 meta_value
缺失。
@Timur,根据您在第一个答案的评论中提出的问题;你必须做这样的事情。您仍然需要联接,但不需要那么多 wp_usermeta
table。您仍然需要加入 wp_postmeta
两次,因为您无法在加入“_customer_user”meta_key
的同时检索“_order_total”meta_key
在单个连接中。
只是对 MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value END)
逻辑的注释;它相当于 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;
我不是 100% 确定逻辑。让我知道是否需要调整
这里引入了子查询来最小化使用单个 table 的多次查询,并从 GROUP BY 子句中删除一些列以进行优化。如果需要特定用户/少数用户或任何搜索条件,请在每个子查询中使用 WHERE 子句。如果子查询 returns 没有数据但需要用户信息,则使用 LEFT JOIN 而不是 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;
我试图在 Whosebug 上找到解决方案,也许我的措辞有误。
我有一个查询需要很长时间才能执行。我相信有一些简单的方法可以改进它。例如,我两次使用相同的子查询来显示两个不同的列(sum 和 count),但在尝试自行解决时遇到了几个错误。
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;
您的查询存在嵌套的相关子查询问题。相关子查询对 SQL 性能有很大影响。在这种情况下,您想尝试将连接与聚合结合使用 (group by
)。
我不明白你的数据库模式的复杂性,所以我不太清楚你为什么将 firstName
等的子查询限制为 1 条记录。如果您提供更多详细信息,我可以编辑答案以适应这一点。
我建议您尝试这样的操作:
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
根据 @O.Jones 的评论,考虑使用 LEFT JOINS 而不是 INNER JOINS,以便查询仍然 returns 结果 meta_value
缺失。
@Timur,根据您在第一个答案的评论中提出的问题;你必须做这样的事情。您仍然需要联接,但不需要那么多 wp_usermeta
table。您仍然需要加入 wp_postmeta
两次,因为您无法在加入“_customer_user”meta_key
的同时检索“_order_total”meta_key
在单个连接中。
只是对 MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value END)
逻辑的注释;它相当于 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;
我不是 100% 确定逻辑。让我知道是否需要调整
这里引入了子查询来最小化使用单个 table 的多次查询,并从 GROUP BY 子句中删除一些列以进行优化。如果需要特定用户/少数用户或任何搜索条件,请在每个子查询中使用 WHERE 子句。如果子查询 returns 没有数据但需要用户信息,则使用 LEFT JOIN 而不是 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;