2016-08-17 2 views
1

3 таблицы.Mysql левое соединение не работает должным образом

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 

Необходимо получить всех клиентов, их общий баланс и дату последнего заказа и идентификатор заказа. Если заказчик не указал дату возврата заказа как 0000-00-00 и идентификатор заказа как 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

Что не так в запросе? Может ли кто-нибудь помочь мне в этом?

Edit: Ожидаемый результат

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

кажется правильным для меня. Хотите добавить ожидаемый результат для ясности? – fancyPants

+0

@fancyPants, добавил ожидаемый результат в вопросе. –

ответ

2

Вопрос заключается в том, что соединение между заказами и кошельку будет производить столько строк, сколько там заказов для каждого кошелька, когда вы на самом деле просто хотят одну строку для каждого кошелька из заказа table (поскольку вы используете только максимальные значения). В тестовом примере вы получаете 3 строки для клиента 1, который составляет сумму 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 | 
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). Опять же, для каждого клиента_ид требуется либо одна строка, либо нулевые строки.

 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 

Ваша ошибка заключалась в следующем: вы присоединились две таблицы, каждый с несколькими строками для каждого идентификатора клиента. Например, у конкретного клиента могли быть два порядка и три ряда кошельков. Затем объединение приводит к шести строкам, представляющим все возможные комбинации строк кошелька и заказа. Это называется комбинаторным взрывом.

Решение, которое я изложил, гарантирует, что для каждого customer_id будет только одна строка (или, может быть, нет строк), и поэтому исключает комбинаторный взрыв.

Pro tip: Использование таких подзапросов позволяет легко протестировать ваш запрос: вы можете протестировать каждый подзапрос отдельно.

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) 

Примечание дублирования идентификатора клиента 1 с количеством 20.

Смежные вопросы