web-dev-qa-db-ja.com

LEFT JOIN混乱

この簡単なシナリオで問題が発生しています。私はおそらくいくつかの基本的な概念を見逃しています...

最初の「もの」と2番目の注文の2つのテーブルがあります。私はsqlfiddleを作成しました: http://sqlfiddle.com/#!17/e9d19/6/

クエリ:

select t1.*, t2.*
from things t1
left join things_orders t2
on t1.id = t2.thing_id

結果を各アイテムと個人の行に表示したい。私の例では、アダムはリンゴ、バナナ2個、チェリー3個を注文しました。ベンはリンゴ(データベースに行がない)、バナナ2個、チェリー3個を注文しました。 Iwant結果は6行になります(簡略化された出力):

Apple Adam 1
Banana Adam 2
Cherry Adam 3
Apple Ben null <-- wanted row, but not showing
Banana Ben 2
Cherry Ben 3

2番目のテーブルにnull値を含む行が必要であるとは思いませんでしたが、多分必要です。

これは、sqlfiddleにもあるDDLです。

CREATE TABLE things (
    id smallint NOT NULL,
    name text COLLATE pg_catalog."default",
    CONSTRAINT things_pkey PRIMARY KEY (id)
);

CREATE TABLE things_orders (
    person text COLLATE pg_catalog."default" NOT NULL,
    thing_id smallint NOT NULL,
    qty integer,
    CONSTRAINT things_orders_pk PRIMARY KEY (person, thing_id)
);

INSERT INTO things VALUES 
  (1, 'Apple')
, (2, 'Banana')
, (3, 'Cherry')
;

INSERT INTO things_orders VALUES
  ('Adam', 1, 1)
, ('Adam', 2, 2)
, ('Adam', 3, 3)
, ('Ben', 2, 2)
, ('Ben', 3, 3)
;
3
aakoch

通常、テーブルpeopleには、関連する人物ごとに1つの行だけが保持されます。 (例のような「ベン」はほとんど一意ではありません。)そうでない場合は、作成することを検討してください。標準の多対多の関係に焼き付けます。

次に、_CROSS JOIN_と_LEFT JOIN_から_things_orders_を使用して、人とモノからデカルト積を作成します。

_SELECT p.id AS person_id, p.person, t.id as thing_id, t.name as thing, tp.qty
FROM   people p
CROSS  JOIN things t
LEFT   JOIN things_orders tp ON tp.thing_id  = t.id
                            AND tp.person_id = p.id;
_

関連:

Meanwhile、_things_orders_が大きく、1人あたりの行数が多い場合、一意の人物を取得するプレーンDISTINCTは高価です。そして、_(person)_のインデックスは役立ちますが、Postgresはまだサポートしていませんindex skip scans。 (Postgres 12に入らなかったかもしれませんが、おそらくPostgres 13に入るでしょう...)

それまでは、再帰CTEは高速回避策です

_WITH RECURSIVE persons AS (
   (
   SELECT person
   FROM   things_orders
   ORDER  BY person
   LIMIT  1
   )
   UNION ALL
   SELECT x.person
   FROM   persons p
   CROSS  JOIN LATERAL (
      SELECT person
      FROM   things_orders
      WHERE  person > p.person
      ORDER  BY person
      LIMIT  1
      ) x
   )
SELECT p.person, t.thing_id, t.name, tp.qty
FROM   persons p
CROSS  JOIN (SELECT id AS thing_id, name FROM things) t
LEFT   JOIN things_orders tp USING (thing_id, person);
_

db <> fiddle ここ

NULLではなくCOALESCE(tp.qty, 0)に_0_を表示させたい場合があります...

関連:

余談ですが、「id」と「name」は一般的に役に立たない識別子です。

0

あなたは食べ物のリストを持っています。そのため、注文に存在しない場合でも、それらのすべてを(左結合を使用して)取得します。

しかし、人のリストはありません。そのため、注文に含まれていなくてもそれらすべてを取得したい場合は、リストを作成する必要があります。サブクエリまたはCTE。

2つのリストがあれば、ペアの完全なリスト(食品-人)を作成できます。次に、そのリストにジョイン注文を残します-必要な結果が得られます。

SELECT t0.person, t1.name, t2.qty
FROM ( SELECT DISTINCT person 
       FROM things_orders ) t0
CROSS JOIN things t1
LEFT JOIN things_orders t2 ON t1.id = t2.thing_id 
                          AND t0.person = t2.person
4
Akina