5

次の 4 つのテーブルがあるとします。

CREATE TABLE events ( id, name )
CREATE TABLE profiles ( id, event_id )
CREATE TABLE donations ( amount, profile_id )
CREATE TABLE event_members( id, event_id, user_id )

すべてのイベントのリスト、メンバーの数、および寄付の合計を取得しようとしています。問題は、寄付の合計が間違って戻ってくることです (寄付のデカルト結果 * event_members の数のように見えます)。

これがSQLクエリです(Postgres)

SELECT events.name, COUNT(DISTINCT event_members.id), SUM(donations.amount)
FROM            events
LEFT OUTER JOIN profiles      ON events.id = profiles.event_id
LEFT OUTER JOIN donations     ON donations.profile_id = profiles.id
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name

sum(donations.amount) が返ってきます = 実際の寄付の合計 * event_members の行数。count(distinct event_members.id) をコメントアウトし、event_members が外部結合を残した場合、合計は正しいです。

編集: アーウィンは私を正しい方向に向けました。クエリは次のように書き直されました。

SELECT events.name, COUNT(DISTINCT event_members.id),
  select(SUM(donations.amount) from donations,profiles where donons.profile_id = profiles.id and profiles.event_id = events.id) as total_donations
    FROM イベント
    LEFT OUTER JOIN event_members ON event_members.event_id = events.id
    GROUP BY events.name
4

4 に答える 4

5

参照されている質問で詳しく説明したように、最初に集計してからテーブルを結合して、プロキシを回避する必要がありますCROSS JOIN。そのようです:

SELECT e.name, e.sum_donations, m.ct_members
FROM (
    SELECT e.id, e.name, SUM(d.amount) AS sum_donations
    FROM   events             e
    LEFT   JOIN profiles      p ON p.event_id = e.id
    LEFT   JOIN donations     d ON d.profile_id = p.id
    GROUP  BY 1, 2
    ) e
LEFT   JOIN (
    SELECT event_id, COUNT(DISTINCT id) AS ct_members
    FROM   event_members
    GROUP  BY 1
    ) m ON m.event_id = e.id

IFevent_members.idは主キーです (想定されるように)。次のように簡略化できます。

COUNT(*) AS ct_members

ので、idあることが保証されますUNIQUE NOT NULL。それは少し速いです。

于 2013-02-05T07:48:17.943 に答える
2

あなたはこの2つの独立した構造を持っているようです(関連付け-[を意味1-Nします):

events -[ profiles -[ donations
events -[ event members

2番目のものをサブクエリにラップしました:

SELECT events.name,
  member_count.the_member_count
  COUNT(DISTINCT event_members.id),
  SUM(donations.amount)

FROM            events
LEFT OUTER JOIN profiles      ON events.id = profiles.event_id
LEFT OUTER JOIN donations     ON donations.profile_id = profiles.id

LEFT OUTER JOIN (
  SELECT
    event_id,
    COUNT(*) AS the_member_count
  FROM event_members
  GROUP BY event_id
) AS member_count
  ON member_count.event_id = events.id

GROUP BY events.name
于 2013-02-05T07:44:38.230 に答える
1

もちろん、イベントごとに寄付とイベントの間にデカルト積が得られます。どちらもイベントにのみバインドされているためです。寄付と event_members の間にイベント ID 以外の結合関係はありません。これはもちろん、すべてのメンバーがすべての寄付に一致することを意味します。

于 2013-02-05T07:42:23.287 に答える
0

クエリを実行するときは、すべてのイベント(イベント アルファとイベント ベータの 2 つがあるとします) を要求し、メンバーと JOIN します。両方のイベントに参加するメンバー Alice がいるとします。

SELECT events.name, COUNT(DISTINCT event_members.id), SUM(donations.amount)
FROM            events
LEFT OUTER JOIN profiles      ON events.id = profiles.event_id
LEFT OUTER JOIN donations     ON donations.profile_id = profiles.id
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name

各行で、アリスの寄付の合計を尋ねました。アリスが 100 米ドルを寄付した場合、あなたは次のことを求めました。

Alpha  Alice  100USD
Beta   Alice  100USD

したがって、総額を尋ねると、アリスが 200 米ドルを寄付したと答えたとしても驚くことではありません。

すべての寄付の合計が必要な場合は、2 つの異なるクエリを使用することをお勧めします。単一のクエリですべてを実行しようとすることは、可能ではありますが、古典的なSQL アンチパターンになります(実際には、第 18 章「スパゲッティ クエリ」の 1 つです)。

意図しない製品

1 つのクエリですべての結果を生成することの一般的な結果の 1 つは、デカルト積です。これは、クエリ内の 2 つのテーブルに、それらの関係を制限する条件がない場合に発生します。このような制限がない場合、2 つのテーブルを結合すると、最初のテーブルの各行が他のテーブルのすべての行とペアになります。このような各ペアリングが結果セットの行になり、予想よりも多くの行が得られます。

于 2013-02-05T07:42:59.233 に答える