优化查询 - 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;