データベーステーブルの列にリストを格納する方法


114

だから、あたりに関連する質問への答えMehrdad、私はそれを得る「適切な」データベース表の列がリストを格納していないこと。むしろ、上記のリストの要素を効果的に保持する別のテーブルを作成してから、直接または結合テーブルを介してそれにリンクする必要があります。ただし、作成するリストのタイプは、一意のアイテムで構成されます(リンクされた質問のフルーツとは異なります)例)。さらに、リスト内の項目は明示的に並べ替えられています。つまり、要素を別のテーブルに保存した場合、アクセスするたびに並べ替える必要があります。最後に、リストは基本的にアトミックです。リストにアクセスしたいときはいつでも、リストの一部ではなくリスト全体にアクセスしたいので、データベースクエリを発行して複数のリストを集めるのはばかげているようです。リスト。

AKXのソリューション(上記にリンク)は、リストをシリアル化してバイナリ列に格納することです。しかし、シリアル化と逆シリアル化について心配する必要があることを意味するため、これも不便に思われます。

より良い解決策はありますか?より良い解決策ない場合、なぜですか?この問題は時々発生するようです。

...私がどこから来たのかを知らせるためのもう少しの情報。SQLとデータベースの一般的な理解を始めた直後に、LINQ to SQLに切り替えられました。オブジェクトがどのように動作するかを考えずにプログラミングオブジェクトモデルを扱うことを期待しているので、今は少し甘やかされています。照会またはデータベースに格納されます。

皆さんありがとう!

ジョン

更新:それで、私が得ている最初の一連の答えで、「CSV / XMLルートに行くことはできますが、しないでください!」と表示されます。だから今私はその理由の説明を探しています。いくつかの良い参考文献を教えてください。

また、私が今何をしているのかをよりよく理解するために:私のデータベースには、(x、y)ペアのリストを持つFunctionテーブルがあります。(この表には、説明に影響しない他の情報も含まれます。)(x、y)ペアのリストの一部を見る必要はありません。むしろ、私はそれらすべてを取り、画面上にプロットします。ユーザーがノードをドラッグして値を変更したり、プロットに値を追加したりできるようにします。

回答:


182

いいえ、一連のアイテムを1つの列に格納する「より良い」方法はありません。リレーショナルデータベースは、行/列の組み合わせごとに1つの値を格納するように特別に設計されています。複数の値を保存するには、リストを1つの値にシリアル化して保存し、取得時に逆シリアル化する必要あります。あなたが話していることを実行する他の方法はありません(あなたが話していることは、一般に決して行われるべきではない悪い考えであるためです)。

そのリストを格納する別のテーブルを作成するのはばかげているとおっしゃっていますが、これはまさにリレーショナルデータベースが行うことです。あなたは困難な戦いと戦い、正当な理由もなくリレーショナルデータベース設計の最も基本的な原則の1つに違反しています。あなたの状態ので、あなただけのSQLを学んでいることを、私は考え強く、より味付けSQLの開発者によってあなたにお勧め実務にこのアイデアとスティックを避けるために、あなたをお勧めします。

違反している原則は、第一正規形と呼ばれます。これは、データベース正規化の最初のステップです。

物事を単純化し過ぎるリスクがありますが、データベースの正規化とは、データに基づいてデータベースを定義するプロセスです。これにより、データに対して賢明で一貫性のあるクエリを記述し、簡単に維持できるようになります。正規化は、データの論理的な不整合や破損を制限するように設計されており、それには多くのレベルがあります。データベースの正規化に関するウィキペディアの記事は、実際にはかなり良いものです。

基本的に、正規化の最初のルール(または形式)は、テーブルがリレーションを表す必要があることを示しています。この意味は:

  • 1つの行を他の行と区別できる必要があります(つまり、テーブルには主キーとして機能できるものが必要です。これは、行が重複してはならないことも意味します。
  • データの順序は、行の物理的な順序ではなく、データによって定義する必要があります(SQLは、セットの概念に基づいています。つまり、信頼すべき順序は、クエリで明示的に定義した順序のみです)。
  • すべての行/列の交差には、1 つののみが含まれている必要があります

最後のポイントは明らかにここの顕著なポイントです。SQLは、セットを格納するように設計されており、セットを自分で格納するための「バケット」を提供するためのものではありません。はい、可能です。いいえ、世界は終わりません。ただし、SQLと、それに伴うORMの使用にすぐに飛び込むことによるベストプラクティスについては、すでに理解できていません。LINQ to SQLは、グラフ計算機と同じように素晴らしいです。ただし、同じように、それらが使用するプロセスが実際にどのように機能するかを知る代わりとして使用することはできません

リストは完全に「アトミック」になった可能性があり、このプロジェクトでは変更されない場合があります。ただし、他のプロジェクトで同様のことをする習慣になり、最終的には(おそらくすぐに)簡単な列の簡単なリストを作成するシナリオに遭遇しますそれが完全に不適切なアプローチ。格納しようとしているものに適切なテーブルを作成するための追加の作業はそれほど多くありません。また、データベースの設計を見て他のSQL開発者に惑わされることはありません。さらに、LINQ to SQLは関係を確認し、リストへの適切なオブジェクト指向インターフェイスを自動的に提供します。非標準で不適切なデータベースハッカーを実行できるように、ORMが提供する便利さをあきらめるのはなぜですか。


17
したがって、列にリストを格納することは悪い考えであると強く信じていますが、その理由を説明していません。私はSQLから始めたばかりなので、「なぜ」のほんの少しだけが本当に役立つでしょう。たとえば、私は「困難な戦いに立ち向かい、正当な理由なくリレーショナルデータベース設計の最も基本的な原則の1つに違反している」とおっしゃっていますが、その原則は何ですか。私が「ダメ」と言った理由はなぜですか。(具体的には、私のリストのソートされたアトミックな性質)
JnBrymn 2010年

6
基本的に、それはベストプラクティスに凝縮された長年の経験に帰着します。問題の基本的なプリンシパルは、第1 正規形として知られています
Toby

1
アダムに感謝します。非常に有益です。あなたの最後の質問で良い点。
JnBrymn 2010年

8
「[…]そして、あなたが他のSQL開発者があなたのデータベース設計を見るとき、あなたは馬鹿にされません。」第一正規形を尊重するのには非常に十分な理由があります(そしてあなたの答えはそれらに言及しています)が、仲間のプレッシャー/「それがこの辺りで物事が行われている方法」はそれらの中にありません
リン、

5
私たちはすでにデータベースの列に一連のリストを毎日保存しています。それらは「char」と「varchar」と呼ばれます。もちろん、Postgresでは、テキストとも呼ばれます。1NFが実際に言っているのは、どのフィールドの情報も小さなフィールドに分割したくないということです。そうすると、失敗してしまいます。したがって、名前は保存せず、個人名、ミドルネーム、および姓(ローカライゼーションに応じて)を保存し、それらをつなぎ合わせます。それ以外の場合は、テキストの文字列をまったく保存しません。一方、彼が望んでいるのは文字列の文字列だけです。そして、それを行う方法があります。
HaakonLøtveit17年

15

SQLをすべて忘れて、「NoSQL」アプローチを採用することができます。 RavenDBMongoDB、およびCouchDBは、考えられるソリューションとしてすぐに思い浮かびます。NoSQLのアプローチでは、リレーショナルモデルを使用しません。スキーマに制約されることすらありません。


11

私が多くの人が行っているのを見てきたのはこれです(これが最善のアプローチではない可能性があります。間違っている場合は修正してください)。

例で使用しているテーブルを以下に示します(テーブルには、特定のガールフレンドに付けたニックネームが含まれています。各ガールフレンドには一意のIDがあります):

nicknames(id,seq_no,names)

IDの下に多くのニックネームを格納したいとします。これがseq_noフィールドを含めた理由です。

次に、これらの値をテーブルに入力します。

(1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')

あなたがガールフレンドID 1に与えたすべての名前を見つけたいなら、あなたは使うことができます:

select names from nicknames where id = 1;

5

簡単な答え:リストが常にリストとして使用されることが確実である場合に限り、リストで使用されない文字( '\ 0'など)を使用してリストを結合します今までにテキスト、それを保存します。その後、それを取得するときに、「\ 0」で分割できます。もちろん、これに対処する方法は他にもありますが、それらは特定のデータベースベンダーに依存しています。

例として、PostgresデータベースにJSONを保存できます。リストがテキストで、それ以上手間をかけずにリストが必要な場合、それは妥当な妥協案です。

他の人たちはシリアライズの提案を試みましたが、シリアライズは良い考えだとは思いません。データベースについてのすばらしい点の1つは、異なる言語で書かれたいくつかのプログラムが互いに対話できることです。そして、Javaのフォーマットを使用してシリアル化されたプログラムは、Lispプログラムがそれをロードしようとした場合、それほどうまくいきません。

この種のことを行う良い方法が必要な場合は、通常、配列または類似のタイプを使用できます。たとえばPostgresは、配列を型として提供し、必要に応じてテキストの配列を格納できます。MySqlMS SQLには JSONを使用して同様のトリックがあり、IBMのDB2は配列型も提供しています(独自の役立つドキュメント)。これが必要でなければ、これはそれほど一般的ではありません。

その道を行くことによって失うものは、一連のものの束としてのリストの概念です。少なくとも名目上、データベースはフィールドを単一の値として扱います。しかし、それがあなたが望むすべてであるならば、あなたはそれのために行くべきです。それはあなた自身のためにしなければならない価値判断です。


3

他の皆が言ったことに加えて、私はあなたのアプローチを今よりも長期的に分析することをお勧めします。現在の項目が一意である場合。現在のアイテムを頼ることは、新しいリストを必要とすることをケース。現在、リストが短いことがほとんど必要です。ドメインの詳細はありませんが、これらの要件が変更される可能性があると考えるのはそれほど難しいことではありません。リストをシリアル化すると、より正規化された設計では必要のない柔軟性が失われます。ところで、それは必ずしも完全な多対多の関係を意味するわけではありません。親への外部キーとアイテムの文字列を持つ単一の子テーブルを作成できます。

リストをシリアル化するこの道をさらに進みたい場合は、リストをXMLで保存することを検討してください。SQL Serverなどの一部のデータベースには、XMLデータ型さえあります。私がXMLを提案する唯一の理由は、ほぼ定義上、このリストは短くする必要があるということです。リストが長い場合、一般的にそれをシリアライズすることは恐ろしいアプローチです。CSVルートを使用する場合は、区切り文字を含む値を考慮する必要があります。これは、引用符で囲まれた識別子を使用する必要があることを意味します。リストが短いと仮定すると、CSVとXMLのどちらを使用しても、おそらく大きな違いはありません。


将来の変更を予測するための+1-常にデータモデルを拡張可能に設計します。
coolgeek

2

私はそれをCSVとして保存します。それが単純な値の場合は、それで十分です(XMLは非常に冗長であり、XMLとのシリアル化は多すぎるでしょうが、それもオプションになります)。

ここだ良い答え LINQでCSVを引き出す方法については。


私はそれについてです。それでも私はシリアライズとデシリアライズをしなければならないことを意味します...しかし、それは実行可能だと思います。私はいくつかあったことを望む容認私が欲しいものを行うための方法は、私はそこではないと思います。
JnBrymn 2010年

capnproto.orgは、選択した言語でサポートされていない場合のcapnproto中(CSVまたはXMLと比較して)同様に、迅速、直列化およびデシリアライズする必要はありませんへの道であるmsgpack.org/index.html
VoronoiPotato

2

リストに対してクエリを実行する必要がある場合は、テーブルに保存します。

リストが常に必要な場合は、区切りリストとして列に格納できます。この場合でも、特に理由がない限り、ルックアップテーブルに保存してください。


1

回答で言及されていないオプションは1つだけです。DB設計を非正規化できます。したがって、2つのテーブルが必要です。1つのテーブルには適切なリスト、行ごとに1つのアイテムが含まれ、別のテーブルにはリスト全体が1つの列に含まれます(たとえば、カンマ区切り)。

ここでは、「伝統的な」DB設計です。

List(ListID, ListName) 
Item(ItemID,ItemName) 
List_Item(ListID, ItemID, SortOrder)

ここにそれは非正規化されたテーブルです:

Lists(ListID, ListContent)

ここでのアイデア-トリガーまたはアプリケーションコードを使用してリストテーブルを維持します。List_Itemのコンテンツを変更するたびに、リストの適切な行が自動的に更新されます。リストをほとんど読んだ場合、それはかなりうまくいくかもしれません。長所-リストを1つのステートメントで読むことができます。短所-更新にはより多くの時間と努力が必要です。


0

本当にそれを列に格納してクエリ可能にしたい場合は、多くのデータベースがXMLをサポートしています。クエリを実行しない場合は、カンマ区切りの値として保存し、区切りが必要な場合は関数で解析できます。リレーショナルデータベースの使用を検討している場合、正規化の大部分はそのようなデータの分離です。ただし、すべてのデータがリレーショナルデータベースに適合するとは言っていません。データの多くがモデルに適合しない場合は、常に他のタイプのデータベースを調べることができます。


0

特定のケースでは、データベースにアイテムの偽の「リスト」を作成できると思います。たとえば、商品にはその詳細を示すいくつかの写真があります。カンマで分割された写真のすべてのIDを連結して、文字列をDBの場合、必要なときに文字列を解析するだけです。私は現在ウェブサイトに取り組んでおり、この方法を使用する予定です。


0

たくさんの答えがあったので、最終的に決心した道を選ぶのは非常に気が進まなかった。彼らはSQLとその原理について理解を深める一方で、私は無法者になることを決めました。また、普遍的な真理がほとんどないことを理解するのではなく、ルールを破っている人にフラストレーションを吹き込むことがより重要な場合があるため、私の調査結果を投稿することもためらっていました。

私はそれを広範囲にテストしましたが、私の特定のケースでは、配列タイプ(PostgreSQLで広く提供されています)を使用したり、別のテーブルをクエリしたりするよりもはるかに効率的でした。

これが私の答えです。リストの各項目の固定長を利用することで、PostgreSQLの単一フィールドにリストを正常に実装しました。各アイテムがARGB 16進値としての色であるとしましょう。これは8文字を意味します。したがって、各アイテムの長さを掛けて、最大10アイテムの配列を作成できます。

ALTER product ADD color varchar(80)

リスト項目の長さが異なる場合は、常にパディングを\ 0で埋めることができます

注意:整数のリストはストレージの消費量が少ないため、これは16進数の場合は必ずしも最善のアプローチではありませんが、これは、各項目に割り当てられた固定長を使用して配列のこの概念を説明するためだけのものです。

理由:1 /非常に便利:サブストリングi * n、(i +1)* nでアイテムiを取得します。2 /クロステーブルクエリのオーバーヘッドはありません。3 /サーバー側の効率とコストを削減します。リストは、クライアントが分割する必要があるミニBLOBのようなものです。

私はルールに従って人々を尊重しますが、多くの説明は非常に理論的であり、特定のケースでは、特に低レイテンシソリューションで最適なコストを目指している場合、いくつかのマイナーな調整が歓迎以上であることを認めないことがよくあります。

「神がSQLの神聖な原則に違反していることを禁じています」:ルールを説明する前に、よりオープンで実用的なアプローチを採用することは、常に進むべき道です。そうしないと、スカイネットに消滅する前に、ロボット工学3つの法則を朗読する率直な狂信者のようになるかもしれません。

このソリューションは画期的なものであり、読みやすさとデータベースの柔軟性の点で理想的であるとは思いませんが、レイテンシに関しては確かに有利です。


しかし、これは非常に特殊なケースです:固定数の固定長アイテム。それでも、「少なくともカラーxのすべての製品」のような単純な検索は、標準のSQLよりも難しくなります。
ガートアーノルド

複数回述べたように、私はそれを色に使用していません。それを使用するフィールドには、インデックスを付けたり、条件として使用したりすべきではありませんが、それでも重要です
アントニンガブレ

わかりました。これは非常に具体的であることを示しています。小さな追加要件がそれに潜入すると、標準のソリューションよりもすぐに厄介になります。リストを1つのdbフィールドに格納したくなる人の大多数は、おそらくそれを行わない方がよいでしょう。
ガートアーノルド

0

多くのSQLデータベースでは、テーブルにコンポーネントとしてサブテーブルを含めることができます。通常の方法では、いずれかの列のドメインをテーブルにすることができます。これは、CSVなどの規則を使用して、DBMSに認識されていない方法で部分構造をエンコードすることに加えてです。

Ed Coddが1969年から1970年にリレーショナルモデルを開発していたとき、彼はこの種のテーブルのネストを許可しない正規形を明確に定義しました。正規形は後に第1正規形と呼ばれました。次に、すべてのデータベースについて、同じ情報を表す第1正規形のデータベースがあることを示しました。

なぜこれに悩むのですか?さて、最初の標準形式のデータベースは、すべてのデータへのキー付きアクセスを許可します。テーブル名、そのテーブルへのキー値、および列名を指定すると、データベースには最大1つのセルが含まれ、1つのデータ項目が含まれます。

セルにリスト、テーブル、またはその他のコレクションを含めることを許可する場合、キーのアイデアを完全に作り直すことなく、サブアイテムへのキー付きアクセスを提供することはできません。

すべてのデータへのキー付きアクセスは、リレーショナルモデルの基本です。この概念がなければ、モデルはリレーショナルではありません。リレーショナルモデルが優れたアイデアである理由、およびその優れたアイデアの制限とは何かについて、リレーショナルモデルで蓄積された50年分の経験を検討する必要があります。


-1

リストのように見えるテキストとして保存し、実際のリストとしてデータを返すことができる関数を作成できます。例:

データベース:

 _____________________
|  word  | letters    |
|   me   | '[m, e]'   |
|  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
|  for   |'[f, o, r]' |
|___in___|_'[i, n]'___|

また、リストコンパイラー関数(Pythonで記述されていますが、他のほとんどのプログラミング言語に簡単に変換できます)。TEXTは、sqlテーブルからロードされたテキストを表します。リストを含む文字列から文字列のリストを返します。文字列ではなくintを返す場合は、modeを 'int'に等しくします。同様に、「string」、「bool」、または「float」を使用します。

def string_to_list(string, mode):
    items = []
    item = ""
    itemExpected = True
    for char in string[1:]:
        if itemExpected and char not in [']', ',', '[']:
            item += char
        elif char in [',', '[', ']']:
            itemExpected = True
            items.append(item)
            item = ""
    newItems = []
    if mode == "int":
        for i in items:
            newItems.append(int(i))

    elif mode == "float":
        for i in items:
            newItems.append(float(i))

    elif mode == "boolean":
        for i in items:
            if i in ["true", "True"]:
                newItems.append(True)
            elif i in ["false", "False"]:
                newItems.append(False)
            else:
                newItems.append(None)
    elif mode == "string":
        return items
    else:
        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
    return newItems

また、必要な場合に備えて、list-to-string関数を以下に示します。

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.