web-dev-qa-db-ja.com

外部キーとしてのENUMおよびID

PostgreSQL 11。

私はスタックオーバーフローとここの両方を調べてみましたが、ベストプラクティスに関する答えを見つけることができませんでした。

私はデータベース設計に取り組んでおり、一般的な「結合テーブル」を使用するスキーマにたどり着きました。この結合テーブルには5つの列があります。

CREATE TABLE many_joins_table (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  object_id int NOT NULL,
  object_table joins_object_t NOT NULL,
  parent_id int NOT NULL,
  parent_table joins_parent_t NOT NULL);

このテーブルを使用して、データベース内のオブジェクト間の隣接する多対多の関係を表しています。そのような例の1つはタグです。

CREATE TABLE tag (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  name text NOT NULL UNIQUE);

CREATE TABLE comment (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY);


CREATE TYPE joins_object_t AS ENUM ('tag');
CREATE TYPE joins_parent_t AS ENUM ('comment');

タグがコメントテーブルに追加されると、次のフィールドを持つ新しい行をこの結合テーブルに挿入します。

INSERT INTO many_joins_table
VALUES(1, 'tag'::joins_object_t, 1, 'comment'::joins_parent_t);

列挙型の柔軟性に加えて、PostgreSQL 9.1で対処 https://stackoverflow.com/questions/1771543/adding-a-new-value-to-an-existing-enum-type/7834949#7834949

そのようなアプローチの重大な欠点または利点はありますか?アンチパターンを誤って実装したのではないかと心配しています。この実装(インデックス作成または制約)を改善するために適用できるベストプラクティスはありますか?

ありがとう!

注:タグを実装するより良い方法、つまりintarrayを使用する方法があることを知っています。わかりやすいのでタグを例にしています。 https://stackoverflow.com/questions/23508551/integer-array-lookup-using-postgres

編集:質問の邪魔になる可能性があるため、UUIDを削除しました。

3
Jason

最初に、このようなことのために列挙型から離れてください。列挙型の値は決して削除することができないので、必要ないことが確実である場合にのみ使用してください。ここではそうではありません。

とにかく、私はあなたのデザインが複雑すぎて、まだ参照整合性の重要な機能を欠いていると思います。

関連付けることができるオブジェクトのペアごとにジャンクションテーブルを使用します。このように、あなた

  • 関連できるオブジェクトを明確にする

  • 参照整合性を持つことができます

多くのテーブルを持つことは、データベースが得意なことです。 1000個のテーブルがあり、各オブジェクトを相互に関連付けることができると主張すると、テーブルが多すぎます。しかしその場合は、とにかくオブジェクトタイプごとのテーブルがないモデルを選択する必要があります。

4
Laurenz Albe

あなたはこれを複雑にしていると思います。まず、そのブログは悪いアドバイスです。インターネットへようこそ。無視してください。

int PRIMARY KEY GENERATED BY IDENTITY AS DEFAULTを使用します(つまり、理由があまりないまでIDENTITY COLUMN

これで、2つのことがわかります。

  • コメント
  • タグ

これらの両方は階層化できます。それは彼らが共通して持っているonlyものです。 テーブルはデータの異なる構造をモデル化しません。テーブルはデータを保持します。リレーショナルデータベースの目的は、データのrelationsをモデル化することです。これは、すべてのデータに適合する抽象スキーマをモデル化するときに完全に変態です。

あなたへの質問、

  • 階層が必要ですか?これはより複雑で低速です。それだけではありません。再帰クエリを学習すれば、ほとんどのワークロードに十分対応できます。そうでない場合は、楽しみのために階層的にモデル化しないでください。 StackOverflowには階層タグはありません-それらはかなり成功しています。
  • 階層が必要ですか?多重継承が必要ですか?このネットワークでは、1つの回答または1つの質問に「コメント」で返信できます。 sameコメントで複数の回答に返信できるとしたら、どれほど複雑になるかを考えてください。ユーザー・インターフェースについて考えてください。あなたはそこに行くことができます、あなたはする必要がありますか?

単一継承が必要だとすると、次のことができます

CREATE TABLE tag (
  tag_id     int   PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  tag        name  text,
  parent_tag int   REFERENCES tag
);

この詳細については、 hierarchy を参照してください。具体的には、スレッド化されたコメントに対応する my answer here を参照してください。これで問題はなくなりましたが、このテーブルにクエリを実行するには再帰クエリが必要です。また、質問にピン留めする必要があります。質問にはすべてのサブタグが付けられますか?すべての親タグでタグ付けされますか?質問は同じ階層の2つのタグでタグ付けできますか?

多くの場合、タグを使用すると、操作が簡単になります。

-- case insensitive
CREATE EXTENSION citext;

CREATE OR REPLACE FUNCTION array_lacks_dupes(anyarray)
RETURNS bool
AS $$
        SELECT coalesce(hasdupe,nodupe) AS hasdupe
        FROM (VALUES (true)) AS t(nodupe)
        LEFT OUTER JOIN (
                SELECT false
                FROM unnest($1) AS e
                GROUP BY e
                HAVING count(*) > 1
                LIMIT 1
        ) AS g(hasdupe)
        ON true
$$ LANGUAGE sql
STRICT IMMUTABLE;


CREATE TABLE question (
  question_id int      PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  tags        citext[] CHECK (array_lacks_dupes(tags))
);

CREATE INDEX ON question USING gin (tags);

上記のスキーマを使用すると、奇妙な方法でクエリを実行する必要がなく、インデックスでクエリを解決できます( そのインデックスに他の任意のものを追加して、すべてを1つに実行することもできますルックアップ

SELECT * FORM question
WHERE tags @> ARRAY['foo']::citext;

これにより、'foo'のタグが付いた質問がインデックスに見つかります。複数のタグに一致するかどうかを確認したいですか?

WHERE tags @> ARRAY['foo', 'bar']::citext;
3
Evan Carroll