Два SQL LEFT JOINS дают неверный результат

У меня есть 3 таблицы:

users(id, account_balance)
grocery(user_id, date, amount_paid)
fishmarket(user_id, date, amount_paid)

И то и другое fishmarket а также grocery Таблицы могут иметь несколько вхождений для одного и того же user_id с разными датами и выплаченными суммами или вообще не иметь ничего для любого данного пользователя. Когда я пытаюсь следующий запрос:

SELECT
     t1."id" AS "User ID",
     t1.account_balance AS "Account Balance",
     count(t2.user_id) AS "# of grocery visits",
     count(t3.user_id) AS "# of fishmarket visits"
FROM users t1
LEFT OUTER JOIN grocery t2 ON (t2.user_id=t1."id") 
LEFT OUTER JOIN fishmarket t3 ON (t3.user_id=t1."id") 
GROUP BY t1.account_balance,t1.id
ORDER BY t1.id

Это дает неверные результаты: "1", "12", "12",
Но когда я пытаюсь LEFT JOIN только к одной таблице это дает правильные результаты для любого grocery или же fishmarket посещения, которые "1", "3", "4",

Что я здесь не так делаю?
Я использую PostgreSQL 9.1.

4 ответа

Объединения обрабатываются слева направо (если в скобках не указано иное). если ты LEFT JOIN (или просто JOIN, аналогичный эффект) три бакалеи для одного пользователя вы получаете 3 ряда (1 х 3). Если вы затем присоединитесь к 4 рыбным рынкам для одного и того же пользователя, вы получите 12 (3 x 4) строк, умножив предыдущий счет в результате, не добавляя к нему, как вы, возможно, надеялись.
Тем самым увеличивается количество посещений продуктового и рыбного рынков.

Это должно работать так:

SELECT u.id
     , u.account_balance
     , g.grocery_visits
     , f.fishmarket_visits
FROM   users u
LEFT   JOIN (
   SELECT user_id, count(*) AS grocery_visits
   FROM   grocery
   GROUP  BY user_id
   ) g ON g.user_id = u.id
LEFT   JOIN (
   SELECT user_id, count(*) AS fishmarket_visits
   FROM   fishmarket
   GROUP  BY user_id
   ) f ON f.user_id = u.id
ORDER  BY u.id;

Чтобы найти агрегированные значения для одного или нескольких пользователей, коррелированные подзапросы, например, предоставленные @Vince, вполне подходят. Для всей таблицы или ее основных частей (гораздо) эффективнее агрегировать n-таблицы и объединиться с результатом один раз. Таким образом, нам также не нужен другой GROUP BY во внешнем запросе.

Для исходного запроса, если вы уберете группу, чтобы посмотреть на предварительно сгруппированный результат, вы поймете, почему были получены подсчеты, которые вы получали.

Возможно, следующий запрос с использованием подзапросов достигнет желаемого результата:

SELECT
 t1."id" AS "User ID",
 t1.account_balance AS "Account Balance",
 (SELECT count(*) FROM grocery     t2 ON (t2.user_id=t1."id")) AS "# of grocery visits",
 (SELECT count(*) FROM fishmarket  t3 ON (t3.user_id=t1."id")) AS "# of fishmarket visits"
FROM users t1
ORDER BY t1.id

Это связано с тем, что когда пользовательская таблица присоединяется к продуктовой таблице, сопоставляются 3 записи. Затем каждая из этих трех записей совпадает с 4 записями на рыбном рынке, что дает 12 записей. Вам нужны подзапросы, чтобы получить то, что вы ищете.

      SELECT
     t1."id" AS "User ID",
     t1.account_balance AS "Account Balance",
     Sum(Case When t2.user_id is null then 0 else 1 end) AS "# of grocery visits",
     Sum(Case When t3.user_id is null then 0 else 1 end) AS "# of fishmarket visits"
FROM users t1
LEFT OUTER JOIN grocery t2 ON (t2.user_id=t1."id") 
LEFT OUTER JOIN fishmarket t3 ON (t3.user_id=t1."id") 
GROUP BY t1.account_balance,t1.id
ORDER BY t1.id

Вышеупомянутое также позволит вам добавить дополнительные критерии по мере необходимости.

Другие вопросы по тегам