web-dev-qa-db-ja.com

親をモデル化する方法->子->子を削除できる場合の孫

私は、子を削除できる一連の関係を扱っていますが、孫と親の間の接続を失いたくないのは明らかです。私は子供を「故人」としてマークすることを考えました(この投稿に関連する用語を使用するため)が、結局私は私のDBに死んだ子供たちの束で立ち往生し、それを望んでいます(関係を維持する目的でのみ) )?

ERD of the relationships

親が削除されると、その子孫もすべて削除されます。さらに、それは「通常の」関係のように機能し、孫と子は常に同じトップレベルの親を持ちます。階層は3つのレベルに固定されています(上記を参照)。最後に、Parent、Child、Grandchildはすべて異なるタイプです(たとえば、3人の「人間」について話しているのではなく、同じベースを持っていません)。

ただし、その関係は通常、親子関係から派生できるため、孫に親を追跡させるのは少し奇妙に感じられます。しかし、私はそれを行う別の方法を考えることはできません。

このモデルは有効ですか?それとも別の方法がありますか?

6
fgblomqvist

実際に選択した構成とその理由を共有したかっただけです(現在のソリューションとは異なるため)。

Grandchildを追跡するParentに外部キーを追加するだけです。

PostgreSQLコードの例:

CREATE TABLE Parent (
  id INT PRIMARY KEY
);

CREATE TABLE Child (
  id INT PRIMARY KEY,
  parent_id INT REFERENCES Parent(id) NOT NULL ON DELETE CASCADE
);

CREATE TABLE Grandchild (
  id INT PRIMARY KEY,
  child_id INT REFERENCES Child(id),
  parent_id INT REFERENCES Parent(id) NOT NULL ON DELETE CASCADE
);

最後に、トリガーを使用して孫がParentと同じChildを持っていることを確認できます。

これはおそらくDB設計を非正規化していることを知っていますが、ここでの他の回答よりも実装がはるかに簡単で、ORMをそのまま使用しても問題なく機能するようです。親の祖父母が決して変わらない場合、これは(非正規化に関して)完全に細かい妥協案です。

私はこの解決策についての提案や意見を喜んで聞きます。

3
fgblomqvist

この回答は、各レベルが異なるタイプであるという説明の前に立っていたあなたの質問に基づいています。あなたはさまざまなタイプの必要性を識別したので、私はそれが 最初に現れた として私の答えに同意し、あなたの 自己回答があなたがどのようにアプローチしたかを文書化しますこの問題。 最上位のテーブルを参照する孫テーブルに単一の列を追加することが、最も簡単なアプローチのようです。

将来の訪問者に役立つように、以下の詳細はそのままにしておきます。


これを相互参照テーブルで実装します。

以下は、SQL Server固有の例です。このテーブルには、名前など、エンティティに関する列が含まれています。

CREATE TABLE dbo.Entities
(
    EntityID int NOT NULL
        CONSTRAINT PK_Entities
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , EntityName varchar(30) NOT NULL
);

次の表は、それらの関係を示しています。

CREATE TABLE dbo.EntityRelationships
(
      EntityIDParent int NOT NULL
        CONSTRAINT FK_EntityRelationships_Parent
        FOREIGN KEY
        REFERENCES dbo.Entities (EntityID)
    , EntityIDChild int NOT NULL
        CONSTRAINT FK_EntityRelationships_Child
        FOREIGN KEY
        REFERENCES dbo.Entities (EntityID)
    , CONSTRAINT PK_EntityRelationships
        PRIMARY KEY CLUSTERED (EntityIDParent, EntityIDChild)
    , CONSTRAINT CK_EntitytRelationships
        CHECK ((EntityIDParent <> EntityIDChild))
);

各関係は一意である必要があります。つまり、指定された親は、指定された子に一度だけ関連付けることができます。

次に、EntitiesテーブルにINSTEAD OF DELETEトリガーを作成します。これにより、削除されたエンティティを削除する前に、必要な関係を親に変更して、削除を適切に処理します。

CREATE TRIGGER EntityRelationshipDelete
ON dbo.Entities
INSTEAD OF DELETE
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
    SELECT erp.EntityIDParent
        , erc.EntityIDChild
    FROM deleted d
        INNER JOIN dbo.EntityRelationships erp ON d.EntityID = erp.EntityIDChild
        INNER JOIN dbo.EntityRelationships erc ON d.EntityID = erc.EntityIDParent
    EXCEPT --don't create duplicate entries
    SELECT er.EntityIDParent, er.EntityIDChild
    FROM dbo.EntityRelationships er;

    DELETE
    FROM dbo.EntityRelationships 
    FROM dbo.EntityRelationships er
        INNER JOIN deleted d ON er.EntityIDChild = d.EntityID OR er.EntityIDParent = d.EntityID;

    DELETE 
    FROM dbo.Entities
    FROM dbo.Entities e
        INNER JOIN deleted d ON e.EntityID = d.EntityID;
END;
GO

ここでは、そのセットアップをテストします。

INSERT INTO dbo.Entities (EntityName)
VALUES ('Grandparent')
    , ('Parent')
    , ('Child');

INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
VALUES (1, 2)
    , (2, 3);

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;

上記の選択の結果:

╔═════════════╦════════════╗
║EntityName║EntityName║
╠══════ ═══════╬════════════╣
║祖父母║親║
║親║子║
╚═══ ══════════╩════════════╝

ここでは、「親」エンティティを削除し、関係を再クエリします。

DELETE 
FROM dbo.Entities
WHERE dbo.Entities.EntityName = 'Parent';

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;

結果:

╔═════════════╦════════════╗
║EntityName║EntityName║
╠══════ ═══════╬════════════╣
║祖父母║子供║
╚═════════════ ╩════════════╝

DELETE FROM dbo.EntitiesWHERE句なしで)を実行すると、bothテーブルからすべての行が削除されることに注意してください。

もう少し複雑な例を示すには、祖父母が2人、親が2人、子供が1人いるとします。

INSERT INTO dbo.Entities (EntityName)
VALUES ('Grandparent 1')
    , ('Grandparent 2')
    , ('Parent 1')
    , ('Parent 2')
    , ('Child');

INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
VALUES (1, 3)
    , (2, 3)
    , (1, 4)
    , (3, 5)
    , (4, 5);

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;
╔═══════════════╦════════════╗
║EntityName║EntityName║
╠════ ═══════════╬════════════╣
║祖父母1║親1║
║祖父母1║親2║
║祖父母2║親1║
║親1║子║
║親2║子║
╚═══════════ ════╩════════════╝

EntitiesテーブルからParent 1を削除すると、次のようになります。

DELETE 
FROM dbo.Entities
WHERE dbo.Entities.EntityName = 'Parent 1';

私たちはこれを見ます:

╔═══════════════╦════════════╗
║EntityName║EntityName║
╠════ ═══════════╬════════════╣
║祖父母1║親2║
║祖父母1║子║
║祖父母2║子供║
║親2║子供║
╚═══════════════╩════════ ════╝

これにより、テストデータのクリーンアップが実行されます。

IF OBJECT_ID(N'dbo.EntityRelationships', N'U') IS NOT NULL
DROP TABLE dbo.EntityRelationships;

IF OBJECT_ID(N'dbo.Entities', N'U') IS NOT NULL
DROP TABLE dbo.Entities;
GO
8
Max Vernon

PostgreSQLおよび ltree

PostgreSQLを使用している場合は、 ltree をチェックアウトして、これを実行し、正常で索引付け可能な状態に保つことができます。

CREATE EXTENSION ltree; -- required if you don't have it.

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');

これでTop.Science.Astronomyを削除でき、Top.Scienceの子孫であるすべての関係を@>で簡単にクエリできます

DELETE FROM test
WHERE path = 'Top.Science.Astronomy';

SELECT *
FROM test
WHERE 'Top.Science' @> path;
7
Evan Carroll