web-dev-qa-db-ja.com

MySQLに配列を保存する方法

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

id | name | fruits

fruits列はnullまたは( 'Apple'、 'orange'、 'banana')、( 'strawberry')などのような文字列の配列を保持できます。2番目のテーブルはTable Fruitで、次の3つの列があります。

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

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

81
tonga

これを行う適切な方法は、クエリで複数のテーブルを使用してそれらを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行が含まれ、personテーブルとfruitsテーブルを効果的にリンクします。

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
128
Bad Wolf

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

あなたがリレーショナルデータベースを必要としないなら、あなたは使用することができます。キーバリューストア.

データベースの正規化 について読んでください。黄金律は、「[すべて]非キー[属性]は、キー、キー全体、そしてキー以外のものについての事実を提供しなければならない」です。配列はやり過ぎです。それは複数の事実を持ち、それは順序を格納します(これは関係自体とは関係ありません)。そして性能は悪いです(上記参照)。

あなたが人のテーブルを持っていて、人の電話があるテーブルがあると想像してみてください。今、あなたはそれぞれの人の列に彼の電話のリストを持たせることができます。しかし、すべての人が他の多くのものと他の多くの関係を持っています。それは私の人のテーブルは彼が接続されているすべてのもののための配列を含むべきであることを意味しますか?いいえ、それはその人自身の属性ではありません。

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

51
Janus Troelsen

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

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

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

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

38
Charles Addis

注意すべき補足として、配列をPostgresに保存することができます。

26
Eric Grotke

MySQLでは、JSON型を使用してください。

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

しかし、あなたの例では、personとfruit、そしてperson_fruitを結合するために3つのテーブルを作成したいと思うでしょう。

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 を使用します。

10
drew

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

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

戻り値

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

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

2
webdevfreak