web-dev-qa-db-ja.com

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

私はプロジェクトのコンセプトに合わせてデータベースを設計しようとしていて、激しく議論されている問題のように思えました。私はいくつかの記事と、フィールドにIDなどのリストを格納することは決して(またはほとんど)許されないというすべてのStack Overflowの回答を読みました。すべてのデータはリレーショナルである必要があります。

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

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

次に、タスクを「タスク」テーブルに保存すると、ダミーの「PersonID」列が数十個必要になり、それらを細かく管理する必要があります。以前と同じ問題が発生します。

このような問題の場合、何らかの形でIDのリストを保存しても大丈夫ですか、それとも原則を破ることなくこれを実現できる別の方法を考えていませんか?

95
linus72982

調査する必要があるキーワードとキーコンセプトは databasenormalization です。

あなたがやろうとしていることは、人や仕事のテーブルへの割り当てに関する情報を追加するのではなく、関連する関係を持つその割り当て情報を持つ新しいテーブルを追加することです。

たとえば、次のテーブルがあるとします。

人:

 + −−−− + −−−−−−−−−−− + 
 | 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を確認できるため、AlfredTaskId 3搾乳牛

ここで確認できるのは、タスクごと、または人ごとに必要な数だけ、または多くの割り当てを持つことができるということです。この例では、Ezekielにはタスクが割り当てられておらず、Alfred 100人のタスクが1つある場合、SELECT PersonId from Assignments WHERE TaskId=<whatever>;を実行すると100行が生成され、さまざまな異なる人が割り当てられます。 PersonIdでWHEREを実行すると、その個人に割り当てられているすべてのタスクを見つけることができます。

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

249
whatsisname

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

まず、列にシリアル化されたリストを保存してもよいかどうかを尋ねます。はい、大丈夫です。 プロジェクトで必要な場合例としては、カタログページの製品の原材料が挙げられ、各原材料を個別に追跡する必要はありません。

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

35
GrandmasterB

あなたが説明しているものは、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)
);
21
Mike Partridge

...フィールドにIDなどのリストを格納することは決して(またはほとんどありません)

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

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

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

いいえ。PersonとTaskの間の交差テーブル(別名、弱いエンティティ)に数行があります。データベースは、たくさんの行を扱うのに本当に優れています。たくさんの[繰り返し]列を操作するのは、実際にはかなりごみです。

Whatsisnameによる明確な例。

13
Phill W.

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

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

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

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

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

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

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


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

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


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

上記に照らして、実際の実装では、完全な正規形に依存し、20秒実行するクエリ、または0.08秒かかる計算済みの値に依存する同等のクエリを好むでしょうか。ソフトウェア製品が遅いと非難されることを好む人はいません。

4
miroxlav

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
Mad Myche

人間が読める主キーがあり、テーブル構造の垂直方向の性質を処理する必要なくタスク番号のリストが必要な場合は、機能する可能性があります。つまり、最初の表を読む方がはるかに簡単です。

------------------------  
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回未満の変更を想定)、それを保存できます。

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

0
Double E CPU

あなたは別のテーブルであるべきものを取って、それを90度回転させて、別のテーブルに靴角を削っています。

これは、itemProdcode1、itemQuantity1、itemPrice1 ... itemProdcode37、itemQuantity37、itemPrice37がある注文テーブルのようなものです。プログラムで処理するのが面倒なだけでなく、明日誰かが38の物を注文したいと思うことを保証できます。

「リスト」が実際にリストではない場合、つまり、全体として表示され、個々のラインアイテムが明確で独立したエンティティを参照していない場合にのみ、あなたの方法で行います。その場合は、十分な大きさのデータ型にすべてを詰め込みます。

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

0

それが「問題がある」場合、すべてのWordpressサイトがwp_usermetaにwp_capabilitiesを1つの行に、dismissed_wp_pointersリストを1つの行に、その他のすべてのサイトにこれまでにあることは...

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

0
NoBugs