Mysql左连接没有按预期工作

时间:2016-08-17 16:10:36

标签: mysql join aggregate-functions

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

3 个答案:

答案 0 :(得分:2)

问题在于订单和钱包之间的连接将产生与每个钱包的订单一样多的行,当您真正想要从订单表中每个钱包一行时(因为您只使用最大值)。在您的测试用例中,客户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

如您所见,订单表由派生表替换,每个客户可获得一行。

运行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 |

答案 1 :(得分:2)

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 :(得分:2)

为了进一步说明之前的答案,如果我们只是删除您的分组语句,您可以轻松地了解为什么要重复计算。以下代码:

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的重复。