リレーショナルデータベースでリストを使用しても大丈夫ですか?


94

私はプロジェクトのコンセプトに合わせてデータベースを設計しようとしており、熱く議論されている問題のように思われました。私はいくつかの記事を読んで、フィールドにIDなどのリストを保存することは決して(またはほとんど決して)大丈夫ではないことを示すいくつかのStack Overflowの回答を読んでいます-すべてのデータはリレーショナルでなければなりません

しかし、私が直面している問題は、タスクアサイナーを作成しようとしていることです。ユーザーはタスクを作成し、複数のユーザーに割り当てて、データベースに保存します。

もちろん、これらのタスクを「Person」に個別に保存する場合、1人に0〜100個のタスクを割り当てることができるため、ダミーの「TaskID」列を数十個用意し、それらをマイクロ管理する必要があります。

繰り返しますが、タスクを「タスク」テーブルに保存する場合、ダミーの「PersonID」列を数十個用意し、それらをマイクロ管理する必要があります。これは以前と同じ問題です。

このような問題の場合、何らかの形でIDのリストを保存しても大丈夫ですか、それとも原則を破らずに達成できる別の方法を考えていないだけですか?


22
これは「リレーショナルデータベース」とタグ付けされているので、答えとしてではなくコメントとして残しておきますが、他の種類のデータベースでリストを保存するのが理にかなっています。Cassandraには結合がないため、思い浮かびます。
キャプテンマン

12
ここで調査してから質問するのは良い仕事です!実際、第1正規形に決して違反しないという「推奨」は、あなたにとって本当にうまくいきました。なぜなら、別のリレーショナルアプローチ、つまり「多対多」関係を考え出す必要があるからです。使用すべきリレーショナルデータベース。
ジミーB

6
「今まで大丈夫ですか」はい....続くものは何でも、答えはイエスです。正当な理由がある限り。ベストプラクティスに違反することを強いるユースケースは常に存在します。(しかし、あなたの場合、あなたは絶対にすべきではありません)
-xyious

3
現在、タグのリストを格納するために配列(区切り文字列ではなく a VARCHAR ARRAY)を使用しています。それはおそらくそれらが最終的に後で保存される方法ではありませんが、リストは、あなたが指す前にデータベーススキーマ全体を構築することを望まない他の何も持っていないプロトタイピング段階で非常に便利です他のことをします。
ニックハートリー

3
@Ben " (インデックスは作成できませんが) "-Postgresでは、JSON列(およびおそらくチェックしていませんが、おそらくXML)に対するいくつかのクエリインデックス作成可能です。
ニックハートリー

回答:


249

調査する必要があるキーワードとキー概念は、データベースの正規化です。

行うことは、割り当てに関する情報を個人またはタスクテーブルに追加するのではなく、その割り当て情報と関連する関係を持つ新しいテーブルを追加することです。

たとえば、次の表があります。

人:

+ ---- + ------------ +
| ID | 名前|
+ ==== + =========== +
| 1 | アルフレッド|
| 2 | ジェダイディア|
| 3 | ジェイコブ|
| 4 | エゼキエル|
+ ---- + ------------ +

タスク:

+ ---- + --------------------- +
| ID | 名前|
+ ==== + ==================== +
| 1 | 鶏に餌をやる|
| 2 | プラウ|
| 3 | 搾乳牛|
| 4 | 納屋を上げる|
+ ---- + --------------------- +

次に、割り当てを持つ3番目のテーブルを作成します。この表は、人とタスクの関係をモデル化したものです。

+ ---- + ------------ + --------- +
| ID | PersonId | TaskId |
+ ==== + =========== + ========= +
| 1 | 1 | 3 |
| 2 | 3 | 2 |
| 3 | 2 | 1 |
| 4 | 1 | 4 |
+ ---- + ------------ + --------- +

次に、データベースがPersonIdおよびTaskIdsがそれらの外部アイテムの有効なIDでなければならないことを強制するように、外部キー制約を設定します。最初の行のために、私たちは見ることができるPersonId is 1ので、アルフレッドに割り当てられTaskId 3搾乳牛

ここで確認できるのは、タスクごと、またはユーザーごとに、必要な数の割り当てをできることです。この例では、エゼキエルにはタスクが割り当てられておらず、アルフレッドには2が割り当てSELECT PersonId from Assignments WHERE TaskId=<whatever>;られています。WHEREPersonIdで、その人に割り当てられたすべてのタスクを見つけることができます。

IDを名前とタスクに置き換えてクエリを返したい場合は、テーブルを結合する方法を学習します。


86
あなたがより多くを学ぶために検索したいキーワードは「多対多の関係
ダニーPflughoeft - BlueRaja

34
Thierrysのコメントを少し詳しく説明すると:Xのみが必要で、IDリストを保存するのは非常に簡単なので、正規化する必要はないと思うかもしれませんが、後で拡張される可能性があるシステムでは、正規化していないことを後悔します以前。常に正規化します。唯一の問題はどのようにされ、通常のフォーム
ヤンDoggen

8
@Janに同意しました-私のより良い判断に対して、私は私のチームがしばらく前に設計のショートカットを取ることを許可し、「拡張する必要がない」何かの代わりにJSONを保存しました。それは6ヶ月のFMLのように続きました。その後、アップグレード担当者は、JSONを当初のスキームに移行するために厄介な戦いをしました。本当によく知っているべきだった。
軌道上の明るさのレース

13
@Deduplicator:これは単なる庭の種類の自動インクリメント整数の主キー列の表現です。かなり典型的なもの。
-whatsisname

8
@whatsisname個人またはタスクの表で、私はあなたに同意します。唯一の目的が、既に代理キーを持つ他の2つのテーブル間の多対多の関係を表すことであるブリッジテーブル上ですか?正当な理由がない限り、追加しません。クエリやリレーションシップで使用されることはないため、オーバーヘッドにすぎません。
jpmc26

35

ここで2つの質問をしています。

最初に、列にシリアル化されたリストを保存してもよいかどうかを尋ねます。はい、その罰金。 プロジェクトで必要な場合。 例としては、カタログページの製品成分があり、各成分を個別に追跡しようとする必要はありません。

残念ながら、2番目の質問では、よりリレーショナルなアプローチを選択する必要があるシナリオについて説明しています。3つのテーブルが必要です。1つは人用、もう1つはタスク用、もう1つはどのタスクがどの人に割り当てられているかのリストを保持します。最後の行は、主キー、タスクID、および個人IDの列を持つ、人/タスクの組み合わせごとに1行の垂直です。


9
参照する成分の例は、表面上で正しいものです。ただし、その場合はプレーンテキストになります。これはプログラミングの意味でのリストではありません(文字列が明らかにそうではない文字のリストであることを意味しない限り)。データを「IDのリスト」(または単に「[..]のリスト」)として説明するOPは、それらが何らかの時点でこのデータを個々のオブジェクトとして処理していることを意味します。
18年

10
@Flater:しかし、それはリストです。アイテムが(さまざまに)Webページ、プレーンテキストドキュメント、モバイルで適切に表示されるように、(さまざまに)HTMLリスト、マークダウンリスト、JSONリストなどとして再フォーマットできる必要があります。アプリ...そして、あなたは本当にプレーンテキストでそれを行うことはできません。
ケビン

12
@Kevinそれがあなたの目標であれば、食材をテーブルに保存することで、はるかに簡単かつ簡単に達成できます!ああ、私が望む、たとえば、知らない、後に、人々は...希望の場合は言うまでもありません推奨代替、またはのための外観のような愚かな何かせずにすべてのレシピ任意のピーナッツ、またはグルテン、または動物性タンパク質...
ダンブロン

10
@ダンブロン:ヤグニ。現時点では、UIロジックが簡単になるため、リストのみを使用しています。私たちが必要とするか、ビジネスロジック層内のリストのような行動を必要とする場合は、その後、それは別のテーブルに正規化されなければなりません。テーブルと結合は必ずしも高価ではありませんが、無料ではありません。また、要素の順序(「材料の順序を気にしますか?」)とさらなる正規化(「3個の卵を入れますか?」 into( 'eggs'、3)? 'salt、to taste'はどうですか( 'salt'、NULL)? ")。
ケビン

7
@ケビン:YAGNIはここではかなり間違っています。あなた自身は、リストをさまざまな方法(HTML、マークダウン、JSON)で変換できる必要があると主張しているため、リストの個々の要素が必要であると主張しています。データストレージアプリケーションと「リスト処理」アプリケーションが独立して開発された2つのアプリケーションでない場合(および別個のアプリケーションレイヤー!=別個のアプリケーション)、データベース構造は常に作成し、すぐに利用できる形式でデータを格納する必要があります-追加の解析/変換ロジックを回避します。
18年

22

あなたが記述しているが間、あなたの場合には、関係「多くの多くの」として知られているPersonTask。通常、「リンク」または「相互参照」テーブルとも呼ばれる3番目のテーブルを使用して実装されます。例えば:

create table person (
    person_id integer primary key,
    ...
);

create table task (
    task_id integer primary key,
    ...
);

create table person_task_xref (
    person_id integer not null,
    task_id integer not null,
    primary key (person_id, task_id),
    foreign key (person_id) references person (person_id),
    foreign key (task_id) references task (task_id)
);

2
task_idタスクでフィルタリングされたクエリを実行する場合は、最初にインデックスを追加することもできます。
jpmc26

1
ブリッジテーブルとも呼ばれます。また、各列にインデックスを付けることをお勧めしますが、ID列を持たないことで余分なプラスを提供できることを願っています。
-jmoreno

13

...フィールドにIDなどのリストを保存することは決して(またはほとんど)できません

あなたが唯一の時間かもしれない単一のフィールドに複数のデータ項目を保存するには、そのフィールドがされている場合でのみ、これまで単一のエンティティとして使用され、決してそれらの小さな要素から作られていると考えられていません。例としては、BLOBフィールドに保存された画像があります。それはたくさんの小さな要素(バイト)で構成されていますが、これらはデータベースにとって何の意味持たず、すべて一緒にしか使用できません(そしてエンドユーザーには見た目もきれいです)。

「リスト」は定義により小さな要素(アイテム)で構成されるため、ここではそうではなく、データを正規化する必要があります。

...これらのタスクを個別に「Person」に保存する場合、ダミーの「TaskID」列を数十個用意する必要があります...

いいえ。PersonとTaskの間の交差テーブル(弱エンティティ)にいくつかの行があります。データベースは、多くの行を扱うのに非常に優れています。実際、多くの[繰り返し]列を扱うのはかなりゴミです。

whatsisnameによって与えられたニース明確な例。


4
現実のシステムを作成するとき、「絶対に言ってはいけない」は、生きるのにとても良いルールです。
l0b0

1
多くの場合、正規化された形式でリストを維持または取得するための要素ごとのコストは、リストの各アイテムがマスターアイテムのIDを保持する必要があるため、アイテムをblobとして保持するコストを大幅に超える可能性がありますが関連付けられており、実際のデータに加えてリスト内でのその場所。リスト全体を更新せずに一部のリスト要素を更新できることでコードが恩恵を受ける場合でも、すべてをblobとして保存し、何かを書き換える必要があるときはいつでもすべてを書き換える方が安上がりです。
supercat

4

特定の事前計算フィールドでは正当な場合があります。

クエリの一部が高価で、データベーストリガーを使用して事前に計算されたフィールドを自動的に更新することにした場合、列内にリストを保持するのが正当な場合があります。

たとえば、UIでは、グリッドビューを使用してこのリストを表示します。ここでは、ダブルクリックすると、各行で完全な詳細(完全なリスト)を開くことができます。

REGISTERED USER LIST
+------------------+----------------------------------------------------+
|Name              |Top 3 most visited tags                             |
+==================+====================================================+
|Peter             |Design, Fitness, Gifts                              |
+------------------+----------------------------------------------------+
|Lucy              |Fashion, Gifts, Lifestyle                           |
+------------------+----------------------------------------------------+

クライアントが新しい記事にアクセスしたときのトリガーまたはスケジュールされたタスクによって、2番目の列を更新し続けます。

このようなフィールドは、検索でも使用できます(通常のテキストとして)。

そのような場合、リストを保持することは正当です。最大フィールド長を超える可能性がある場合を考慮する必要があります。


また、Microsoft Accessを使用している場合、提供される複数値フィールドは別の特別なユースケースです。フィールド内のリストを自動的に処理します。

ただし、他の回答に示されている標準の正規化された形式にいつでもフォールバックできます。


要約:通常の形式のデータベースは、データモデリングの重要な側面を理解するために必要な理論モデルです。ただし、当然のことながら、正規化ではパフォーマンスやその他のデータ取得コストは考慮されません。それはその理論モデルの範囲外です。ただし、実際の実装では、リストまたはその他の事前計算(および制御)された複製を保存することがしばしば必要です。

上記に照らして、実際の実装では、完全な正規形に依存して20秒実行するクエリ、または0.08秒かかる事前計算値に依存する同等のクエリを優先しますか?ソフトウェア製品の遅さを嫌う人はいません。


1
事前に計算されたものがなくても合法です。データを適切に保存するために何度か実行しましたが、パフォーマンス上の理由から、いくつかのキャッシュされた結果をメインレコードに入れると便利です。
ローレンペクテル

@LorenPechtel –はい、ありがとう。事前に計算された用語を使用する際に、必要に応じてキャッシュされた値が保存されている場合も含めます。複雑な依存関係を持つシステムでは、パフォーマンスを正常に保つ方法です。そして、適切なノウハウでプログラムされていれば、これらの値は信頼でき、常に同期しています。答えをシンプルで安全な状態に保つために、答えにキャッシュのケースを追加したくありませんでした。とにかくダウン投票されました。:)
ミロクスラフ

@LorenPechtel実際には、それはまだ悪い理由です...キャッシュデータは中間キャッシュストアに保持する必要があり、キャッシュがまだ有効である間、そのクエリはメインDBにヒットしないはずです。
テズラ

1
@Tezraいいえ、メインレコードにコピーを置くことが理にかなっているために、セカンダリテーブルのデータが必要になることがよくあると言っています。(私が行っている例は、 -従業員のテーブルには、内の最後の時間、最後のタイムアウトを含み、これらは表示のみを目的として使用され、任意の実際の計算は、クロックイン/クロックアウトレコードを持つテーブルから来ています。。)
ローレンペクテル

0

与えられた2つのテーブル。それぞれを独自のID(PersonID、TaskID)を持つPersonおよびTaskと呼びます。基本的な考え方は、3つ目のテーブルを作成してそれらをバインドすることです。このテーブルをPersonToTaskと呼びます。少なくとも、自身のIDと他の2つのIDが必要です。だから、誰かをタスクに割り当てることになると、Personテーブルを更新する必要はなくなり、PersonToTaskTableに新しい行を挿入するだけで済みます。また、メンテナンスがより簡単になります。タスクを削除する必要があるのは、TaskIDに基づいたDELETEになり、Personテーブルと関連する解析を更新する必要がなくなります。

CREATE TABLE dbo.PersonToTask (
    pttID INT IDENTITY(1,1) NOT NULL,
    PersonID INT NULL,
    TaskID   INT NULL
)

CREATE PROCEDURE dbo.Task_Assigned (@PersonID INT, @TaskID INT)
AS
BEGIN
    INSERT PersonToTask (PersonID, TaskID)
    VALUES (@PersonID, @TaskID)
END

CREATE PROCEDURE dbo.Task_Deleted (@TaskID INT)
AS
BEGIN
    DELETE PersonToTask  WHERE TaskID = @TaskID
    DELETE Task          WHERE TaskID = @TaskID
END

簡単なレポートや、誰がタスクに割り当てられているのでしょうか?

CREATE PROCEDURE dbo.Task_CurrentAssigned (@TaskID INT)
AS
BEGIN
    SELECT PersonName
    FROM   dbo.Person
    WHERE  PersonID IN (SELECT PersonID FROM dbo.PersonToTask WHERE TaskID = @TaskID)
END

もちろん、もっと多くのことができます。TaskAssignedおよびTaskCompletedにDateTimeフィールドを追加した場合、TimeReportを実行できます。それはすべてあなた次第です


0

人間が読める主キーがあり、テーブル構造の垂直性に対処することなく、タスク#のリストが必要な場合に機能します。つまり、最初の表を読みやすくします。

------------------------  
Employee Name | Task 
Jack          |  1,2,5
Jill          |  4,6,7
------------------------

------------------------  
Employee Name | Task 
Jack          |  1
Jack          |  2
Jack          |  5
Jill          |  4
Jill          |  6
Jill          |  7
------------------------

問題は、タスクリストをオンデマンドで保存または生成するかどうかです。これは、リストが必要な頻度、データ行の数、データの使用方法などの要件に大きく依存します。 ..その後、ユーザーエクスペリエンスと要件を満たすためのトレードオフを分析する必要があります。

たとえば、2行をリコールするのにかかる時間と、2行を生成するクエリを実行する時間を比較します。時間がかかり、ユーザーが最新のリストを必要としない場合(* 1日に1回未満の変更が予想されます)、保存できます。

または、ユーザーに割り当てられたタスクの履歴レコードが必要な場合は、リストが保存されていても意味があります。ですから、それはあなたが何をしているのかに本当に依存します。決して言わないでください。


あなたが言うように、それはすべてデータがどのように取得されるかに依存します。ユーザー名でこのテーブルを/ only /クエリする場合、「リスト」フィールドで十分です。ただし、このようなテーブルにクエリを実行して、タスク#1234567で作業しているユーザーを見つけ、それでもパフォーマンスを維持するにはどうすればよいでしょうか。ほぼすべての種類の「find-X-anywhere-in-the-field」文字列関数により、/ Table Scan /へのそのようなクエリが発生し、クロールの速度が低下します。適切に正規化され、適切にインデックス付けされたデータでは、それは起こりません。
フィルW.

0

別のテーブルにする必要があるものを取り、それを90度回転させて、別のテーブルにシューホーンします。

itemProdcode1、itemQuantity1、itemPrice1 ... itemProdcode37、itemQuantity37、itemPrice37がある注文テーブルがあるようなものです。プログラムで処理するのが面倒であることに加えて、明日誰かが38個のものを注文することを保証できます。

「リスト」が実際にはリストではない場合、つまり、リスト全体が独立していて、個々の行項目が明確で独立したエンティティを参照しない場合にのみ、あなたのやり方でやります。その場合は、十分な大きさのデータ型にすべて詰め込んでください。

したがって、注文はリストであり、部品表はリスト(またはリストのリストであり、「横向き」を実装するのはさらに悪夢です)。しかし、メモ/コメントと詩はそうではありません。


0

「大丈夫」でない場合、すべてのWordpressサイトのwp_usermetaに1行にwp_capabilitiesのリスト、1行にdismissed_wp_pointersのリスト、その他のリストがあることはかなり悪いです...

実際、このような場合、ほとんど常にリストが必要になるため、速度が向上する可能性あります。しかし、Wordpressがベストプラクティスの完璧な例であることは知られていません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.