web-dev-qa-db-ja.com

2つのSQL LEFT JOINSが誤った結果を生成する

私は3つのテーブルを持っています:

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

fishmarketgroceryの両方のテーブルで、同じ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"
しかし、1つのテーブルだけをLEFT JOINにしようとすると、groceryまたはfishmarketのどちらの訪問("1", "3", "4")でも正しい結果が得られます。

ここで何が悪いのですか?
私はPostgreSQL 9.1を使用しています。

29
Ryan Bostwick

結合は左から右に処理されます(括弧がそれ以外の場合を除きます)。 LEFT JOIN(または単にJOIN、同様の効果)の場合、1人のユーザーに3つの食料品を購入すると、3行(1 x 3)。その後、同じユーザーの4つの魚市場に参加すると、12(3 x 4)行、multiplying結果の前のカウントで、期待どおりのaddingではありません。
それにより、食料品や魚市場への訪問数が増加します。

次のように機能するはずです。

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;

1人または少数のユーザーの集計値を検索するには、相関サブクエリ@ Vinceが提供されるように で十分です。テーブル全体またはその主要部分の場合、nテーブルを集計して結果に結合するonceの方が(はるかに)効率的です。この方法では、外部クエリに別のGROUP BYも必要ありません。

47

元のクエリの場合、グループ化して事前にグループ化された結果を確認すると、受け取っていたカウントが作成された理由がわかります。

おそらく、サブクエリを利用する次のクエリは、意図した結果を実現するでしょう。

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
8
Vince Perta

これは、ユーザーテーブルが食料品テーブルに結合すると、3つのレコードが一致するためです。次に、これらの3つのレコードのそれぞれが魚市場の4つのレコードと一致し、12のレコードが生成されます。探しているものを取得するにはサブクエリが必要です。

2
Tobsey