如何在MySQL中存储数组?


118

我在MySQL中有两个表。表人具有以下列:

id | name | fruits

fruits列可以包含null或字符串数​​组,例如('apple','orange','banana')或('strawberry')等。第二个表是Table Fruit,具有以下三列:

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

那么,我应该如何设计fruits第一个表中的列,以便它可以容纳从fruit_name第二个表中的列获取值的字符串数组?由于MySQL中没有数组数据类型,该怎么办?



1
如何将其添加为单独的条目(橙色,2、1,玫瑰,2、1等),然后可以使用查询将它们当作数组对待。
2013年

@JanusTroelsen:我没有使用PHP来读写数据库。那么有没有通用的方法呢?
汤加2013年

1
@tonga检查我的小提琴是您想要的吗?
echo_Me 2013年

Answers:


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表为一个人关联的每种水果包含一行,并有效地将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
第三个表是“人”和“水果”之间的链接表。因此,如果一个人有100个水果。我需要在第三张表中创建100行,对吗?这样有效吗?
汤加

1
@tonga确实,100行中的每行都具有相同person_id但不同的fruit_name。这实际上是Janus回答中的理论的一种实现。
Bad Wolf 2013年

1
两个表之间的任何关系都需要存储在第三个表中是否总是正确的?我可以仅通过存储两个表中的主键来执行查询以找到关系吗?
汤加

2
是的,这就是现在设置示例的方式。有关该人的任何信息都应该在person表中,有关该fruits表中的水果的任何信息,以及有关该表中特定人和特定水果之间的关系的任何信息person_fruit。因为在此示例中没有任何其他信息,所以该person_fruit表只有两列,即personfruits表的主键。但是,特定水果的数量是person_fruit表格中其他内容的示例。
Bad Wolf

2
这岂不是更好地使用INT在一个关键fruits和唯一有此INTperson_fruit?因此,如果您输入的行数不fruits超过in,则可以稍后更改名称,并且还需要更少的空间person_fruit
2412341234123412341234

58

SQL中没有数组的原因是,因为大多数人实际上并不需要它。关系数据库(SQL正是这样)使用关系来工作,并且在大多数情况下,最好将表的一行分配给每个“信息位”。例如,您可能会认为“我想要这里的东西列表”,而是创建一个新表,将一个表中的行与另一个表中的行链接起来。[1] 这样,您可以表示M:N关系。另一个优点是这些链接不会使包含链接项的行混乱。数据库可以索引这些行。数组通常不被索引。

如果不需要关系数据库,则可以使用例如键值存储。

请阅读有关数据库规范化的信息。黄金法则是“ [每个]非密钥[属性]必须提供有关密钥,整个密钥的事实,而除了密钥之外,什么也不能提供”。数组做的太多了。它具有多个事实,并且存储顺序(与关系本身无关)。而且性能很差(请参见上文)。

想象一下,您有一个人员表,并且您有一个表,其中包含有人打来的电话。现在,您可以使每个人行都有他的电话列表。但是每个人与许多其他事物都有许多其他关系。这是否意味着我的人员表应该为他所连接的每个事物都包含一个数组?不,那不是人本身的属性。

[1]:链接表只有两列(每个表的主键)就可以了!但是,如果关系本身具有其他属性,则应在此表中将它们表示为列。


2
感谢Janus。这就说得通了。现在我了解了为什么MySQL不支持列中的数组类型。
tonga 2013年

2
@Sai-对于我正在做的事情,我真的需要NoSQL解决方案吗?
汤加

1
好的,因此,如果我有一个表,其中一个字段包含数千个元素的数字数组,例如,从传感器收集的一些2D数据,那么使用NoSQL DB会更好吗?
汤加2013年

5
@tonga:数据量并不决定要使用的数据库类型,数据的性质决定了该类型。如果没有关系,则不需要关系数据库。但是,由于这是行业标准,因此您可以保留它,而不必使用相关功能。大多数数据在某种程度上都是相关的!使关系数据库非规范化或使用键值存储的常见原因是由于性能原因。但是,只有当您有数百万行时,这些问题才会出现!不要过早优化!我建议只使用一个SQL数据库(我建议使用PostgreSQL)。如果有问题,请询问。
Janus Troelsen

2
PostgreSQL还具有内置的键值存储,这意味着如果不适合您,则从关系模型中移走甚至更加容易。
Janus Troelsen

50

MySQL 5.7现在提供了JSON数据类型。这种新的数据类型提供了一种方便的新方式来存储复杂数据:列表,字典等。

也就是说,Rray不能很好地映射数据库,这就是为什么对象关系映射可能非常复杂的原因。历史上,人们通过创建描述表/数组并将每个值添加为自己的记录的方式在MySQL中存储列表/数组。该表可能只有2或3列,或者可能包含更多列。您存储此类数据的方式实际上取决于数据的特征。

例如,列表是否包含静态或动态条目数?列表会很小,还是会增长到数百万条记录?这张桌子上会有很多读书吗?很多写?很多更新?这些都是决定如何存储数据集合时需要考虑的因素。

同样,Key:Value数据存储/文档存储(例如Cassandra,MongoDB,Redis等)也提供了很好的解决方案。只需知道数据的实际存储位置(如果将其存储在磁盘或内存中)即可。并非所有数据都必须位于同一数据库中。某些数据无法很好地映射到关系数据库,您可能有理由将其存储在其他位置,或者您可能希望使用内存中的键:值数据库作为存储在磁盘上的数据的临时高速缓存或临时存储用于会议等。


42

注意,可以在Postgres中存储数组。


6
附加说明:可以对它们进行索引,因此检查数组中是否存在特定值的查询会非常快。复杂的JSON类型也是如此。
timetofly

5
这不会以任何方式回答问题。OP问有关MySQL的问题。
jhpratt

1
如果您在Postgres中使用ArrayField并在该列中包含一个详尽的值列表(例如固定的标签列表),则可以创建一个GIN索引-它将大大加快该列的查询速度。
lumos42

25

在MySQL中,使用JSON类型。

与上面的答案相反,SQL标准已经包含了将近二十年的数组类型。即使MySQL尚未实现它们,它们也很有用。

但是,在您的示例中,您可能想要创建三个表:person和fruit,然后创建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

返回值

返回一个字符串,其中包含可以存储在任何地方的值的字节流表示形式。

请注意,这是一个二进制字符串,其中可能包含空字节,因此需要这样存储和处理。例如,serialize()输出通常应存储在数据库的BLOB字段中,而不是CHAR或TEXT字段中。


-4

你可以像这样使用group_Concat存储数组

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

这里有个例子


4
没有很好的解释。错误的表名。
马丁F
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.