1つを除くすべての列を主キーとしてマークすることは妥当ですか?


9

映画を表すテーブルがあります。フィールドは次のとおり
id (PK), title, genre, runtime, released_in, tags, origin, downloadsです。

重複する行によってデータベースを汚染することはできないため、一意性を強制したいと思います。問題は、異なる映画が同じタイトルを持つ可能性があることです、または同じフィールドを除くtagsdownloads。一意性を強制する方法は?

私は2つの方法を考えました:

  • downloads主キーを除くすべてのフィールドを作成します。downloadsそれはJSONであり、おそらくパフォーマンスに影響を与えるため、私は締め出します。
  • id主キーとしてのみ保持しますが、他のすべての列(再度、を除くdownloads)で一意制約を追加します。

よく似たこの質問を読みましたが、どうすればいいのかよくわかりませんでした。現在、このテーブルは他のテーブルとは関係ありませんが、将来的には関係する可能性があります。

現時点では20,000件弱のレコードですが、その数は増えると思います。これが問題にある程度関連しているかどうかはわかりません。

編集:私はスキーマを変更しました、そしてここに私がテーブルを作成する方法があります:

CREATE TABLE movies (
    id          serial PRIMARY KEY,
    title       text NOT NULL,
    runtime     smallint NOT NULL CHECK (runtime >= 0),
    released_in smallint NOT NULL CHECK (released_in > 0),
    genres      text[] NOT NULL default ARRAY[]::text[],
    tags        text[] NOT NULL default ARRAY[]::text[],
    origin      text[] NOT NULL default ARRAY[]::text[],
    downloads   json NOT NULL,
    inserted_at timestamp NOT NULL default current_timestamp,
    CONSTRAINT must_be_unique UNIQUE(title,runtime,released_in,genres,tags,origin)
);

timestamp列も追加しましたが、触れないので問題ありません。そのため、常に自動で一意になります。


SOの(回答付きの)密接に関連する質問:テーブルの主キーが必要ですか?UNIQUE(複合4列)があり、そのうちの1つをNULLにすることができますか?。列のいずれかがNULLになる可能性がある場合は、至急これを検討してください:dba.stackexchange.com/q/9759/3684
Erwin Brandstetter 2015

回答:


4

あなたのテーブル定義は今や至る所で妥当に見えます。すべての列NOT NULLUNIQUE制約は期待どおりに機能します。ただし、タイプミスやスペルのマイナーな違いを除いて、かなり一般的かもしれませんが、恐れています。@a_horseのコメントを検討してください。

機能的な一意のインデックスを持つ代替

もう1つのオプションは、機能的な一意のインデックスです@Daveがコメントしたものと同様)。しかし、uuidデータサイズを使用して、インデックスのサイズとパフォーマンスを最適化します。

配列からテキストへのキャストはそうではありませんIMMUTABLE(その一般的な実装のため):

したがって、不変であると宣言するための小さなヘルパー関数が必要です。

CREATE OR REPLACE FUNCTION f_movie_uuid(_title text
                                      , _runtime int2
                                      , _released_in int2
                                      , _genres text[]
                                      , _tags text[]
                                      , _origin text[])
  RETURNS uuid LANGUAGE sql IMMUTABLE AS  -- faking IMMUTABLE
'SELECT md5(_title || _runtime::text || _released_in::text
         || _genres::text || _tags::text || _origin::text)::uuid';

インデックス定義に使用します。

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));

SQL Fiddle。

詳細:

生成されたUUIDをPKとして使用する可能性がありますが、私はまだserial4バイトの列を使用します。これは、FK参照やその他の目的のためにシンプルで安価です。UUIDは、PK値を個別に生成する必要がある分散システムに最適なオプションです。または、非常に大きなテーブルの場合でも、そのための太陽系には十分な数の映画がありません。

長所と短所

ユニーク制約は、関係の列に一意索引で実装されています。最初に関連する列を制約定義に配置すると、付随的な利益として他の目的に役立つインデックスがあります。

他の特定の利点があります、ここにリストがあります:

機能的な一意のインデックスは、実質的に速くそれを作ることができ、サイズが小さく(潜在的にかなり)です。列が大きすぎない場合、違いはあまりありません。計算にはわずかなオーバーヘッドコストもあります。

すべての列を連結すると'foo ' || 'bar' = 'foob ' || 'ar'、誤検知が発生する可能性があります(が、この場合は非常にありそうありません。タイプミスは非常に多く、ここでは無視できます。

一意性と配列

配列は、しなければならないであろう、一貫ソートに依存する任意のユニークな配置に意味をなすために=ので、オペレータ'{1,2}' <> '{2,1}'。私はのためのルックアップテーブルを提案genretagおよびoriginserialPKと配列要素のためのあいまい検索を許可する一意のエントリを、。次に:

どちらの方法でも、配列を直接操作したり、正規化されたスキーマとマテリアライズドビューを操作したりする場合、適切なインデックスと演算子を使用すると、検索が非常に効率的になります。

さておき

Postgres 9.4以降を使用している場合は、のjsonb代わりjsonに検討してください。


6

あなたが友達のグループと一緒に外出していて、会話が映画に変わると想像してください。「三銃士」についてどう思いましたか?「どっち?」と答えます。

どちらも同じ映画について考えていることを確実に確認するために、どのような追加情報が必要ですか?監督の名前は?制作スタジオ?発売年?星の名前の1つですか?2つ以上の組み合わせですか?

私の質問とあなたの答えは同じです。

しかし、私はそのジャンルが良い候補になるとは思いません。1つの理由として、ジャンルは主観的すぎる基準です。「三銃士」の行動ですか?ドラマ?冒険?コメディ?アクションアドベンチャー?ラブコメ?同じ映画をさまざまなジャンルでよく見かけます。複数のジャンルを許可する場合でも、ユーザーは、実際に探している映画にリストされていない、まったく異なるジャンルを選択する場合があります。

特に劇場版とVCR / DVD / b-rayバージョン間では、ランタイムも異なる場合があります。

したがって、あるメディアリリースから別のリリースに変更されない、ハードで客観的な属性が必要です。残念ながら、特に続編のリリース後、映画の名前が変更されたことが知られているため、映画の名前が除外される可能性があります。

リリース日はどうですか?1993年の劇場公開?1999年のVCRリリース?2004年のDVD発売は?あなたはアイデアを得ます。

考えてみてください。アラン・スミシー監督の映画はどれですか?本当の監督が、事後にプロジェクトに彼の名前を付けるためについに前進したことがありますか?知りません。

いくつかの基準が残っている間は停止することをお勧めします。

追加のポイント:

  • はい、代理キーを保持し、自然キーフィールドに一意のインデックスを作成します(最終的にそれらを特定できる場合)。代理キーは、外部キー参照に最適です。映画への参照を含むすべてのテーブルのすべての自然キーフィールドを複製する必要はありません。
  • 配列フィールド(ジャンル、タグ、起点)をドロップします。これらの属性を適切に正規化してください。特に検索可能にしたい場合は( "... where genre = 'horror' ...")、配列フィールドが見た目よりもずっと面倒なことはありません。ルックアップテーブルを適切に維持しない限り、大文字と小文字の違いやスペルに関する問題( "Science Fiction"と "SciFi")が自動的になくなるわけではありません。ただし、大きなテーブルのすべての行のすべての配列セルよりも、小さなテーブルの1つのフィールドでこのような違いを確認する方がはるかに簡単です。

4

ID列は、強制する必要がある/必要とする一意性に関しては、まったく利点がありません。意味のないIDを追加することで、属性の組み合わせの一意性が強制されることはありません。その "利点"は、このテーブルへの外部キーを必要とする新しいテーブルが必要になるところまでたどり着いたときにのみ示されます。その場合、IDを含めていれば、そのIDを新しいテーブルのFKとして使用できます。(しかし、それが無料の昼食になるとは思わないでください。このようなアプローチの欠点は、作成した新しいテーブルの一部である可能性のある情報を取得するだけの目的で、より多くの結合を作成する可能性があることです。 )


1
FOOおよびBAR属性の値の組み合わせは一意である必要があるとビジネスルールで規定されている場合、IDを追加してもそれは実現されません。IDを追加すると、テーブルの参照にFOOとBARをそのまま含める必要がなくなります。FOOおよびBAR属性(BUSINESS識別子を運ぶ)は、それらがあり得た場所ではない(そして、少なくともビジネスの観点からは、それらが期待されている可能性が非常に高い)ため、これはさらに結合を必要とします。
Erwin Smout、2015

1
一意である必要があるのは「行」ではなく、企業が言う必要があるのはその識別子であるということです。それが属性FOOとBARの組み合わせである場合、それは属性FOOとBARの組み合わせです。
Erwin Smout、2015

2
Idがあるかどうかにかかわらず、テーブル内の「ビジネス」列の一意性の強制に関する問題は解決されません。一意性の強制は、適切なキーを宣言することによって行う必要があります(これを行います-「KEY」の代わりに「CONSTRAINT」という構文語を使用したという事実は、それがキーではないことを意味しません)。
Erwin Smout、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.