web-dev-qa-db-ja.com

レコードに子レコードがあるかどうかを最も効率的に確認するにはどうすればよいですか?

parentテーブルから単一のレコードを返すクエリを書いています。子がある場合も、このクエリで返したいと思います。これは1対多の関係です。

parent:
 -parent_id
 -name

child:
-child_id
-name
-parent_id

私の最初の本能は次のクエリを書くことです:

select name, (select count(child_id) from child c  where c.parent_id=p.parent_id) children
     from parent p
     where name like 'some name'

しかし、私は実際にはカウントを気にしないので、これがより効率的な方法があるかどうか疑問に思っていました。子があるかどうかだけです。ポインタはありますか?

7
TheCatWhisperer

Postgresにはブールデータ型があることを忘れないでください。以下は、クエリを表現する最も簡潔な方法です。

select
  parent_id,
  name,
  exists (select from child where parent_id = p.parent_id) as has_children
from parent p;

https://dbfiddle.uk/?rdbms=postgres_10&fiddle=86748ba18ba8c0f31f1b77a74230f67b

8
Colin 't Hart

方法

集計方法

集約メソッドと呼ぶ一般的な方法。 bool_or(child_id IS NOT NULL)も機能しますが、速くはありませんでした。

_SELECT parent_id, count(*)>1 AS has_children
FROM parent
LEFT OUTER JOIN children
  USING (parent_id)
GROUP BY parent_id;
_

_LEFT JOIN LATERAL_制限あり

しかし、あなたはこれをLEFT JOIN LATERAL()のように試してみることもできます。

_SELECT parent_id, has_children
FROM parent AS p
LEFT JOIN LATERAL (
  SELECT true
  FROM children AS c
  WHERE c.parent_id = p.parent_id
  FETCH FIRST ROW ONLY
) AS t(has_children)
  ON (true);
_

EXISTS

参考までに、EXISTSで_CROSS JOIN LATERAL_を使用することもできます(これはどのように計画されていると思います)。 EXISTSメソッドと呼びます。

_SELECT parent_id, has_children
FROM parent AS p
CROSS JOIN LATERAL (
  SELECT EXISTS(
    SELECT 
    FROM children AS c
    WHERE c.parent_id = p.parent_id
  )
) AS t(has_children);
_

これは同じです

_SELECT parent_id, EXISTS(
    SELECT 
    FROM children AS c
    WHERE c.parent_id = p.parent_id
) AS has_children
FROM parent AS p;
_

ベンチマーク

サンプルデータセット

子供1000000人、両親2500人。私たちのシムはそれを成し遂げます。

_CREATE TABLE parent (
  parent_id int PRIMARY KEY
);
INSERT INTO parent
  SELECT x
  FROM generate_series(1,1e4,4) AS gs(x);
CREATE TABLE children (
  child_id int PRIMARY KEY,
  parent_id int REFERENCES parent
);
INSERT INTO children
  SELECT x, 1 + (x::int%1e4)::int/4*4
  FROM generate_series(1,1e6) AS gs(x);

VACUUM FULL ANALYZE children;
VACUUM FULL ANALYZE parent;
_

結果(pt1)

  • 集計方法:450ms、
  • LEFT JOIN LATERAL ( FETCH FIRST ROW ONLY ):850ミリ秒
  • [〜#〜] exists [〜#〜]メソッド:850ms

結果(インデックスの追加と再実行)

さて、インデックスを追加しましょう

_CREATE INDEX ON children (parent_id);
ANALYZE children;
_

これで、タイミングプロファイルはまったく異なります

  • 集計方法:450ms、
  • LEFT JOIN LATERAL ( FETCH FIRST ROW ONLY ):30ms
  • [〜#〜] exists [〜#〜]メソッド:30ms
6
Evan Carroll

これは私がSQLサーバーで行う方法です(私はpostgresqlを持っていません-私はそれが似ていると思います)

SELECT p.parent_id,
CASE WHEN EXISTS (SELECT 1 FROM Child c WHERE c.ParentId=p.ParentId)
                THEN 'Yes'
                ELSE 'No'
                END as has_child,
FROM Parent p
--WHERE EXISTS (SELECT 1 FROM Child c WHERE c.ParentId=p.ParentId)
2
abbhey