MySQLに配列を格納する方法は?


118

MySQLに2つのテーブルがあります。テーブルPersonには次の列があります。

id | name | fruits

fruits列はヌルまたは(「りんご」、「オレンジ」、「バナナ」)、または(「イチゴ」)、等二番目の表は、表フルーツであり、以下の3つの列を有しているような文字列の配列を保持してもよいです。

____________________________
fruit_name | color  | price
____________________________
apple      | red    | 2
____________________________
orange     | orange | 3
____________________________
...,...

では、2番目のテーブルの列fruitsから値を取得する文字列の配列を保持できるように、最初のテーブルの列をどのように設計すればよいfruit_nameでしょうか。MySQLには配列データ型がないため、どのようにすればよいですか?



1
オレンジ、2、1、バラ、2、1などの個別のエントリとして追加して、クエリを使用して、それらを配列のように扱うことができます。
Sai

@JanusTroelsen:DBの読み書きにPHPを使用していません。それを行うための普遍的な方法はありますか?
tonga 2013年

1
@tonga私のフィドルを確認してくださいあなたが欲しいものですか?
echo_Me 2013年

回答:


163

これを行う適切な方法は、複数のテーブルを使用し、 JOINは、クエリでそれらすることです。

例えば:

CREATE TABLE person (
`id` INT NOT NULL PRIMARY KEY,
`name` VARCHAR(50)
);

CREATE TABLE fruits (
`fruit_name` VARCHAR(20) NOT NULL PRIMARY KEY,
`color` VARCHAR(20),
`price` INT
);

CREATE TABLE person_fruit (
`person_id` INT NOT NULL,
`fruit_name` VARCHAR(20) NOT NULL,
PRIMARY KEY(`person_id`, `fruit_name`)
);

person_fruitテーブルは、人が関連付けられている各果物のために1つの行が含まれ、効果的にリンクpersonfruits、一緒にIEをテーブル

1 | "banana"
1 | "apple"
1 | "orange"
2 | "straberry"
2 | "banana"
2 | "apple"

人とそのすべての果物を取得したい場合は、次のようなことができます。

SELECT p.*, f.*
FROM person p
INNER JOIN person_fruit pf
ON pf.person_id = p.id
INNER JOIN fruits f
ON f.fruit_name = pf.fruit_name

4
3番目のテーブルは、PersonとFruitの間のリンクテーブルです。だから人が100個の果物を持っているなら。3番目のテーブルに100行を作成する必要がありますよね?これは効率的ですか?
tonga 2013年

1
@tonga正確には、100行のそれぞれは同じですperson_idが、異なるものになりfruit_nameます。これは事実上、ヤヌスの答えからの理論の実装です。
バートウルフ

1
2つのテーブル間のリレーションを3番目のテーブルに格納する必要があるのは常に本当ですか?2つのテーブルの主キーを格納するだけで、リレーションを検索するクエリを実行できますか?
tonga 2013年

2
はい、これでサンプルの設定が完了しました。その人に関するすべての情報がperson表に含まれている必要があり、その表に関する果物fruitsに関するすべての情報、および特定の人物とそのperson_fruit表に含まれる特定の果物との関係に関する具体的な情報が含まれている必要があります。この例では追加情報がないため、person_fruitテーブルは2つの列(personおよびfruitsテーブルの主キー)のみです。特定の果物の量は、person_fruitしかし、表に入る可能性のある何かの他の例です。
バートウルフ

2
使用する方ではないでしょうINTで、キーのためfruitsだけにこれを持っているINTではperson_fruit?名前は後から変更することができ、あなたがしていない多くのより多くの行を持っている場合も、少ないスペースが必要になりますので、fruitsよりもperson_fruit
12431234123412341234123 2017

58

SQLに配列がない理由は、ほとんどの人は本当にそれを必要としないからです。リレーショナルデータベース(SQLはそのとおりです)はリレーションを使用して機能します。ほとんどの場合、テーブルの1行を各「情報のビット」に割り当てるのが最適です。たとえば、「ここにあるもののリストが欲しい」と思うかもしれませんが、代わりに新しいテーブルを作成して、あるテーブルの行を別のテーブルの行にリンクします。[1] これにより、M:Nの関係を表すことができます。別の利点は、これらのリンクが、リンクされたアイテムを含む行を混乱させないことです。そして、データベースはそれらの行にインデックスを付けることができます。通常、配列にはインデックスが付けられていません。

リレーショナルデータベースが必要ない場合は、Key-Valueストアなどを使用できます。

データベースの正規化について読むください。ゴールデンルールは、「[すべて]非キー[属性]は、キー、キー全体、およびキーのみに関する事実を提供する必要があります」です。配列が多すぎます。複数のファクトがあり、順序を格納します(これは関係自体には関係ありません)。また、パフォーマンスが低下します(上記を参照)。

人テーブルがあり、人が電話をかけるテーブルがあるとします。これで、各人の行に彼の電話のリストを持たせることができます。しかし、すべての人は他の多くのものと他の多くの関係を持っています。それは私の人物テーブルに、彼が接続されているすべてのものの配列が含まれている必要があることを意味しますか?いいえ、それは本人自身の属性ではありません。

[1]:リンクテーブルに2つの列(各テーブルの主キー)しかない場合は問題ありません。ただし、リレーションシップ自体に追加の属性がある場合、それらはこの表では列として表されます。


2
Janusに感謝します。それは理にかなっている。これで、MySQLが列の配列型をサポートしない理由がわかりました。
tonga 2013年

2
@Sai-私がやっていることについて、本当にNoSQLソリューションが必要ですか?
tonga 2013年

1
さて、フィールドに数千の要素の数値配列が含まれているテーブルがある場合、たとえば、センサーから収集された2Dデータの場合、NoSQL DBを使用する方がはるかに優れていますか?
tonga 2013年

5
@tonga:データの量によって、使用するデータベースのタイプが決まりません。データの性質によって決まります。関係がない場合は、リレーショナルデータベースは必要ありません。ただし、これは業界標準なので、そのままにして、リレーショナル機能を使用しないでください。ほとんどのデータは何らかの意味でリレーショナルです。リレーショナルデータベースを非正規化したり、キーと値のストアを使用したりする一般的な理由は、パフォーマンス上の理由です。しかし、これらの問題は、何百万もの行がある場合にのみ発生します。時期尚早に最適化しないでください!私はSQL dbだけを使用することをお勧めします(PostgreSQLをお勧めします)。問題がある場合は、尋ねてください。
Janus Troelsen 2013年

2
PostgreSQLには、組み込みのKey-Valueストアもあります。つまり、自分に合わない場合は、リレーショナルモデルから離れる方が簡単です。
Janus Troelsen 2013年

50

MySQL 5.7はJSONデータ型を提供するようになりました。この新しいデータ型は、複雑なデータを格納する便利な新しい方法を提供します:リスト、辞書など。

とはいえ、レイはデータベースをうまくマップしないため、オブジェクトリレーショナルマップが非常に複雑になる可能性があります。歴史的に人々は、リスト/配列を記述したテーブルを作成し、各値を独自のレコードとして追加することにより、リスト/配列をMySQLに保存してきました。テーブルには2つまたは3つの列しか含まれていない場合と、さらに多くの列が含まれている場合があります。このタイプのデータを格納する方法は、実際にはデータの特性によって異なります。

たとえば、リストには静的または動的な数のエントリが含まれていますか?リストは小さいままですか、それとも数百万のレコードに増えると予想されますか?このテーブルにはたくさんの読み取りがありますか?たくさんの書き込み?たくさんのアップデート?これらはすべて、データのコレクションを格納する方法を決定するときに考慮する必要がある要因です。

また、Cassandra、MongoDB、RedisなどのKey:Valueデータストア/ドキュメントストアも優れたソリューションを提供します。データが実際に保存されている場所(ディスクまたはメモリに保存されている場合)に注意してください。すべてのデータが同じデータベースにある必要はありません。一部のデータはリレーショナルデータベースにうまくマッピングできず、他の場所に格納する理由がある場合や、メモリ内のkey:valueデータベースを、ディスクのどこかに格納されているデータのホットキャッシュとして、または一時的なストレージとして使用したい場合があります。セッションのようなもののために。


42

考慮すべき補足事項として、配列をPostgresに保存できます。


6
追加の注記:インデックスを付けることができるため、配列内の特定の値の存在を確認するクエリは非常に高速になります。複雑なJSON型についても同様です。
timetofly

5
これは決して質問に答えるものではありません。OPはMySQLについて尋ねました。
jhpratt

1
PostgresでArrayFieldを使用し、その列に完全な値のリストがある場合(タグの固定リストのように)、GINインデックスを作成できます。これにより、その列でのクエリが劇的にスピードアップします。
lumos42

25

MySQLでは、JSONタイプを使用します。

上記の回答とは対照的に、SQL標準には約20年間配列型が含まれています。MySQLがそれらを実装していない場合でも、これらは有用です。

ただし、この例では、おそらくpersonとfruitの3つのテーブルを作成し、次にperson_fruitでそれらを結合します。

DROP TABLE IF EXISTS person_fruit;
DROP TABLE IF EXISTS person;
DROP TABLE IF EXISTS fruit;

CREATE TABLE person (
  person_id   INT           NOT NULL AUTO_INCREMENT,
  person_name VARCHAR(1000) NOT NULL,
  PRIMARY KEY (person_id)
);

CREATE TABLE fruit (
  fruit_id    INT           NOT NULL AUTO_INCREMENT,
  fruit_name  VARCHAR(1000) NOT NULL,
  fruit_color VARCHAR(1000) NOT NULL,
  fruit_price INT           NOT NULL,
  PRIMARY KEY (fruit_id)
);

CREATE TABLE person_fruit (
  pf_id     INT NOT NULL AUTO_INCREMENT,
  pf_person INT NOT NULL,
  pf_fruit  INT NOT NULL,
  PRIMARY KEY (pf_id),
  FOREIGN KEY (pf_person) REFERENCES person (person_id),
  FOREIGN KEY (pf_fruit) REFERENCES fruit (fruit_id)
);

INSERT INTO person (person_name)
VALUES
  ('John'),
  ('Mary'),
  ('John'); -- again

INSERT INTO fruit (fruit_name, fruit_color, fruit_price)
VALUES
  ('apple', 'red', 1),
  ('orange', 'orange', 2),
  ('pineapple', 'yellow', 3);

INSERT INTO person_fruit (pf_person, pf_fruit)
VALUES
  (1, 1),
  (1, 2),
  (2, 2),
  (2, 3),
  (3, 1),
  (3, 2),
  (3, 3);

人物を果物の配列に関連付ける場合は、ビューを使用して行うことができます。

DROP VIEW IF EXISTS person_fruit_summary;
CREATE VIEW person_fruit_summary AS
  SELECT
    person_id                                                                                              AS pfs_person_id,
    max(person_name)                                                                                       AS pfs_person_name,
    cast(concat('[', group_concat(json_quote(fruit_name) ORDER BY fruit_name SEPARATOR ','), ']') as json) AS pfs_fruit_name_array
  FROM
    person
    INNER JOIN person_fruit
      ON person.person_id = person_fruit.pf_person
    INNER JOIN fruit
      ON person_fruit.pf_fruit = fruit.fruit_id
  GROUP BY
    person_id;

ビューには次のデータが表示されます。

+---------------+-----------------+----------------------------------+
| pfs_person_id | pfs_person_name | pfs_fruit_name_array             |
+---------------+-----------------+----------------------------------+
|             1 | John            | ["apple", "orange"]              |
|             2 | Mary            | ["orange", "pineapple"]          |
|             3 | John            | ["apple", "orange", "pineapple"] |
+---------------+-----------------+----------------------------------+

5.7.22では、文字列から配列をハッキングするのではなく、JSON_ARRAYAGGを使用する必要があります。


2

配列を格納するには、データベースフィールドタイプBLOBを使用します。

参照:http : //us.php.net/manual/en/function.serialize.php

戻り値

どこにでも格納できる値のバイトストリーム表現を含む文字列を返します。

これはnullバイトを含む可能性のあるバイナリ文字列であり、そのように格納および処理する必要があることに注意してください。たとえば、serialize()出力は、通常、CHARまたはTEXTフィールドではなく、データベースのBLOBフィールドに格納する必要があります。


-4

そのようなgroup_Concatを使用して配列を保存できます

 INSERT into Table1 (fruits)  (SELECT GROUP_CONCAT(fruit_name) from table2)
 WHERE ..... //your clause here

ここでフィドルの例


4
よく説明されていません。不正なテーブル名。
マーティンF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.