【问题标题】:Mysql left join is not working as expectedMysql左连接没有按预期工作
【发布时间】:2016-12-24 09:37:42
【问题描述】:

3 张桌子。

table_customers - customer_id, name
table_orders - order_id, customer_id, order_datetime
table_wallet - customer_id, amount, type  // type 1- credit, type 2- debit

需要获取所有客户、他们的总余额、他们的最后订单日期和订单 ID。如果客户没有将任何退货订单日期设置为 0000-00-00,订单 ID 设置为 0。

这是我的查询。

SELECT 
C.customer_id, 
C.name, 
COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
COALESCE( max( O.order_id  ) , '0' ) AS last_order_id, 
COALESCE( max( date( O.order_datetime ) ) , '0000-00-00' ) AS last_order_date
FROM 
table_customers as C 
LEFT JOIN 
table_wallet as W 
ON C.customer_id = W.customer_id 
LEFT JOIN
table_orders AS O
ON W.customer_id = O.customer_id
group by C.customer_id
ORDER BY C.customer_id

一切都是正确的,除了客户的价值。从结果来看,它似乎被多次添加。

我在这里创建了小提琴。 http://sqlfiddle.com/#!9/560f2/1

查询有什么问题?谁能帮我解决这个问题?

编辑:预期结果

customer_id name    value   last_order_id     last_order_date
  1         abc     20        3             2016-06-22
  2         def     112.55    0             0000-00-00
  3         pqrs      0       4             2016-06-15
  4         wxyz      0       0             0000-00-00

【问题讨论】:

  • 对我来说看起来是正确的。想要添加预期结果以清楚说明吗?
  • @fancyPants,添加了有问题的预期结果。

标签: mysql join aggregate-functions


【解决方案1】:

这是当您JOIN 包含不相关数据的表时的经典组合爆炸问题。

您需要在子查询中计算每个客户的余额。该子查询必须为每个 customer_id 产生一行或零行。它可能看起来像这样。 (http://sqlfiddle.com/#!9/560f2/8/0)

      SELECT customer_id, 
             SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
        FROM table_wallet
       GROUP BY customer_id

同样,您需要在子查询 (http://sqlfiddle.com/#!9/560f2/10/0) 中检索每个客户的最新订单。同样,每个 customer_id 需要一行或零行。

      SELECT customer_id,
             MAX(order_id) AS order_id,
             DATE(MAX(order_datetime)) AS order_date
        FROM table_orders
       GROUP BY customer_id

然后,您可以将这两个子查询LEFT JOIN 当作表格发送到您的table_customers。子查询是表;它们是虚拟表。 (http://sqlfiddle.com/#!9/560f2/12/0)

SELECT c.customer_id,
       c.name,
       w.value,
       o.order_id,
       o.order_date
  FROM table_customers c
  LEFT JOIN (
           SELECT customer_id, 
                  SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
             FROM table_wallet
            GROUP BY customer_id
       ) w ON c.customer_id = w.customer_id
  LEFT JOIN (
           SELECT customer_id,
                  MAX(order_id) AS order_id,
                  DATE(MAX(order_datetime)) AS order_date
             FROM table_orders
            GROUP BY customer_id
       ) o ON c.customer_id = o.customer_id 

您的错误是:您连接了两个表,每个表的每个客户 ID 都有多行。例如,特定客户可能有两个订单和三个钱包行。然后,连接产生六行,代表钱包和订单行的所有可能组合。这就是组合爆炸。

我概述的解决方案确保每个 customer_id 只有一行(或者可能没有行)要加入,因此消除了组合爆炸。

专业提示:使用这样的子查询可以轻松测试您的查询:您可以单独测试每个子查询。

【讨论】:

    【解决方案2】:

    从前面的答案进一步说明,如果我们简单地删除您的 group by 语句,您可以很容易地看到您为什么重复计算。以下代码:

    SELECT 
    C.*,
    O.order_id, O.order_datetime,
    W.amount, W.type
    FROM 
    table_customers as C 
    LEFT JOIN 
    table_wallet as W 
    ON C.customer_id = W.customer_id 
    LEFT JOIN
    table_orders AS O
    ON W.customer_id = O.customer_id
    

    将产生结果:

    customer_id   name  order_id    order_datetime           amount   type
    1             abc   1           April, 22 2016 23:53:09  20       1
    1             abc   2           May, 22 2016 23:53:09    20       1
    1             abc   3           June, 22 2016 23:53:09   20       1
    2             def   (null)      (null)                   100      1
    2             def   (null)      (null)                   12.55    1
    3             pqrs  (null)      (null)                   (null)   (null)
    4             wxyz  (null)      (null)                   (null)   (null)
    

    注意客户 ID 1 与金额 20 的重复。

    【讨论】:

      【解决方案3】:

      问题是订单和钱包之间的连接将产生与每个钱包的订单一样多的行,而您实际上只希望订单表中的每个钱包有一行(因为您只使用最大值)。在您的测试用例中,您得到客户 1 的 3 行,总和为 60 (3*20)。

      解决这个问题的一种方法是改为:

      SELECT 
        C.customer_id, 
        C.name, 
        COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
        COALESCE( O.order_id , '0' ) AS last_order_id, 
        COALESCE( DATE( O.order_datetime ) , '0000-00-00' ) AS last_order_date
      FROM table_customers AS C 
      LEFT JOIN table_wallet AS W ON C.customer_id = W.customer_id 
      LEFT JOIN (
        SELECT 
          customer_id, 
          MAX(order_id) AS order_id, 
          MAX(order_datetime) AS order_datetime
        FROM table_orders 
        GROUP BY customer_id
      ) AS O ON c.customer_id = O.customer_id
      GROUP BY C.customer_id
      ORDER BY C.customer_id
      

      如您所见,orders 表已替换为派生表,该表为每个客户提供一行。

      运行query above 会得到以下结果:

      | customer_id | name |  value | last_order_id | last_order_date |
      |-------------|------|--------|---------------|-----------------|
      |           1 |  abc |     20 |             3 |      2016-06-22 |
      |           2 |  def | 112.55 |             0 |      0000-00-00 |
      |           3 | pqrs |      0 |             4 |      2016-06-15 |
      |           4 | wxyz |      0 |             0 |      0000-00-00 |
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多