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