sum() 在具有多个连接的 MySQL 查询中无法正常工作(group by 无法按预期工作)

sum() does not work properly in MySQL query with multiple joins (group by does not work as expected)

我有 table 个订单、网络和用户,我需要获得 订单总数 订单总金额 对于每个用户,用户计数在每个用户拥有的同一网络中。

SQL摆弄示例数据: http://sqlfiddle.com/#!9/dcbeea/1

users.userid、orders.id - 唯一主键。

在此示例中,检查用户 #24 行:total_orderstotal_revenue、[= 的值37=]network_users 不正确。

用户 #24 的当前结果: total_orders:6,total_revenue:350,network_users:6。

用户 #24 的预期结果: total_orders:3,total_revenue:175,network_users:2.

这是SQL请求:

SELECT u.*,
   count(o.id) AS total_orders,
   sum(o.total) AS total_revenue,
   count(un.userid) as network_users /* Users count in same network */
FROM users u
LEFT JOIN orders o ON o.userid=u.userid
LEFT JOIN users am ON u.ownerid = am.userid
LEFT JOIN users bdr ON u.bdrid = bdr.userid
LEFT JOIN networks n ON u.networkid = n.networkid
LEFT JOIN users un ON n.networkid=un.networkid
GROUP BY u.userid
ORDER BY u.userid DESC;

问题 1: total_orderstotal_revenue 这里 return 不正确的值(比它应该的多,看起来它加起来很少次因为网络 table 加入)。

我可以通过添加 distinct - count(distinct(o.id)) AS total_orders 来修复 total_orders,但这不起作用对于总和,因为我不能仅通过不同的 ID 来设置总和,因为我看到无法在 SQL.

中设置它

您可以在 SQLFiddle 示例中看到问题 - 用户 #24 应该有 total_revenue = 175,但是您看到它计算为 350。据我所知,这是因为两个不同的用户关联用户 #24 拥有的同一网络(网络 #1)。

问题 2: count(un.userid) as network_users - 如果我不添加 count(disctinct(un.userid)) as [=,这将无法正常工作72=]。没有 'distinct' 这会显示我所看到的总体网络计数(而不是网络中与当前用户具有相同网络 ID 的总体用户数)。在 SQL 示例中,用户 ID #24 的 'network_users' 应该是 2(因为该网络中只有 2 个用户),但是我在结果中看到 6。

问题: 如何更改 SQL 请求以获得正确的预期数学结果?

一种方法:计算子select中的network_users(SELECT子句中的子查询)

SELECT u.userid, 
   count(o.id) AS total_orders,
   coalesce(sum(o.total), 0) AS total_revenue,
   (
     SELECT count(*)
     FROM users un     
     WHERE un.networkid = u.networkid
   ) as network_users
FROM users u
LEFT JOIN orders o ON o.userid=u.userid
GROUP BY u.userid, u.networkid
ORDER BY u.userid DESC;

结果:

| userid | total_orders | total_revenue | network_users |
| ------ | ------------ | ------------- | ------------- |
| 40     | 1            | 75            | 1             |
| 37     | 0            | 0             | 2             |
| 33     | 0            | 0             | 1             |
| 24     | 3            | 175           | 2             |

View on DB Fiddle

另一种方式:在派生的table中执行"preaggregation"(FROM子句中的子查询)

SELECT u.userid,
  count(o.id) AS total_orders,
  coalesce(sum(o.total), 0) AS total_revenue,
  u.network_users
FROM (
  SELECT u.userid, count(un.userid) as network_users
  FROM users u
  LEFT JOIN users un ON un.networkid = u.networkid
  GROUP BY u.userid
) u
LEFT JOIN orders o ON o.userid=u.userid
GROUP BY u.userid
ORDER BY u.userid DESC;

结果:

| userid | network_users | total_orders | total_revenue |
| ------ | ------------- | ------------ | ------------- |
| 40     | 1             | 1            | 75            |
| 37     | 2             | 0            | 0             |
| 33     | 1             | 0            | 0             |
| 24     | 2             | 3            | 175           |

View on DB Fiddle

一些注意事项:

  • 确保 useridusers table 中是 UNIQUE 或 PRIMARY KEY。否则,从 MySQL 5.7.
  • 开始可能会出现错误
  • 我删除了 users amusers bdr 的 JOIN。您没有在查询中使用它们。如果你想 select 从他们那里得到任何东西,你可以把它们放回去。
  • 我还删除了与 networks table 的 JOIN。您可以使用 networkid 列将 users table 与其自身连接起来。
  • COALESCE() 用于 SUM()NULL 转换为 0

为什么您的查询未能return 预期结果?因为您正在加入来自同一网络的所有其他用户的用户订单。因此订单数和总金额乘以同一网络中的用户数。

这样看。在执行 JOIN 时,首先将来自 table 的行的所有组合放入一个大临时文件 table(在过滤掉任何不适用的内容之后)。

然后 聚合如 COUNT()SUM() 是针对这个大 table.

计算的

通常这是不正确的。通常解决方法是 首先 设计一个查询,该查询使用从中间 table 获得正确总和所需的最少 table 数量。 然后再做JOINs你可能需要的。

另一种方法(有时)是使用子查询来进行聚合或提供 JOINs.

的等效项

有时查询会像这样:

SELECT ...
    FROM ( SELECT key, COUNT(*), SUM(..) FROM .. GROUP BY .. ) AS a
    JOIN b  ON ...
    JOIN c  ON ...

为@Paul 的出色回答添加第三个选项

您可以分别进行计数,然后使用 UNION ALL 将它们放在一起,然后 SUM 这些行

  SELECT agg.userid,
         SUM(agg.order_count) AS total_orders,
         SUM(agg.revenue_sum) AS total_revenue,
         SUM(agg.network_user_count) AS network_users

    FROM (

       /** Orders and Revenue */
    SELECT u.userid,
           COUNT(o.id) AS order_count,
           SUM(o.total) AS revenue_sum,
           0 AS network_user_count
      FROM users u
 LEFT JOIN orders o ON o.userid=u.userid
  GROUP BY u.userid 

     UNION ALL

       /** Network Users */
    SELECT u.userid, 
           0 AS order_count,
           0 AS revenue_sum,
           COUNT(un.userid) AS network_user_count
      FROM users u
 LEFT JOIN users un 
        ON un.networkid = u.networkid
  GROUP BY u.userid

         ) agg 

GROUP BY agg.userid
ORDER BY agg.userid DESC;

内部查询 agg 将给出类似

的结果
| userid | order_count | revenue_sum | network_user_count |
| ------ | ----------- | ----------- | ------------------ |
| …
| 24     | 3           | 175         | 0                  |
| 24     | 0           | 0           | 2                  |
| …

然后外部查询会将这些行与 SUMs

合并

有点啰嗦,不过我已经在我们的一些项目中使用了这个方法