階層構造を扱うことは、リレーショナルデータベースの古典です。例:ディレクトリ、ツリー状メニュー、組織構造。
問題の歴史: データベースはテーブルモデルです。ツリー状データには、各自の長所と短所を持ついくつかのアプローチがあります。
問題: 標準的なparent_idモデルは、遅い再帰的SELECTにつながります。実際のタスク(すべての子孫の検索、パス、サブツリーの数え上げ)は最適化を必要とします。
解決策:
マテリアライズドパスのPostgreSQL用のコード例:
CREATE TABLE categories ( id SERIAL PRIMARY KEY, name TEXT, path TEXT ); -- パスの保存例: '1/2/5/'(ルート/サブカテゴリ/現在) SELECT * FROM categories WHERE path LIKE '1/2/%'; -- 2のすべての子孫
ネストされたセットのコード例:
CREATE TABLE nested_categories ( id SERIAL PRIMARY KEY, name TEXT, lft INT NOT NULL, rgt INT NOT NULL ); -- 子ノード SELECT * FROM nested_categories WHERE lft > 2 AND rgt < 15;
主な特徴:
すべての子孫を任意の深さで検索するために、単にparent_idを使用したネストされたSELECTを使うことはできますか?
これは浅い深さにのみ機能します。再帰的検索には、再帰的CTE(WITH RECURSIVE)またはより複雑なスキームが必要で、単純なJOINは膨大な数の呼び出しと悪いパフォーマンスにつながります。
例:
WITH RECURSIVE cte AS ( SELECT id, parent_id, name FROM categories WHERE id = 1 UNION ALL SELECT c.id, c.parent_id, c.name FROM categories c INNER JOIN cte ON c.parent_id = cte.id ) SELECT * FROM cte;
なぜ分子ツリー(ネストされたセット)はサブツリーを迅速に抽出しますが、ノードの追加/削除は遅いのですか?
ツリーを変更する際、lft/rgtを多くの行で同時に変更する必要があります(変更のステップは、挿入/削除よりも大きいすべての値です)。読み取りには理想的ですが、頻繁な変更には他の方法を使用してください。
parent_idやマテリアライズドパスではなく、クローズテーブルを使用するのはいつですか?
クローズテーブルは、任意のレベルの子孫への複数回のクエリと関係の定期的な再計算に完璧に機能します。欠点は、より多くのスペースを必要とすることです。
例:
CREATE TABLE closure ( ancestor INT, descendant INT, depth INT );
オンラインストアで、カテゴリはparent_idを介して実装されています。すべてのネストは手動で設定され、サブカテゴリの検索はネストされたSELECTを使用しています。
メリット:
デメリット:
マテリアライズドパスまたはクローズテーブルを使用しています。すべてのサブカテゴリへのクエリは瞬時で、再計算はグループスクリプトで行います。
メリット:
デメリット: