如何在MySQL中存储数组?

ton*_*nga 95 mysql database-schema

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

id | name | fruits
Run Code Online (Sandbox Code Playgroud)

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

____________________________
fruit_name | color  | price
____________________________
apple      | red    | 2
____________________________
orange     | orange | 3
____________________________
...,...
Run Code Online (Sandbox Code Playgroud)

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

Bad*_*olf 141

正确的方法是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`)
);
Run Code Online (Sandbox Code Playgroud)

person_fruit表包含一个人与每个水果相关联的一行,并有效地将这些personfruits表连接在一起,IE

1 | "banana"
1 | "apple"
1 | "orange"
2 | "straberry"
2 | "banana"
2 | "apple"
Run Code Online (Sandbox Code Playgroud)

当你想要找回一个人和他们所有的水果时,你可以这样做:

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
Run Code Online (Sandbox Code Playgroud)

  • 第三个表是Person和Fruit之间的链接表.所以,如果一个人有100个水果.我需要在第三个表中创建100行,对吗?这有效吗? (4认同)
  • @tonga 确实,100 行中的每一行都具有相同的 `person_id`,但具有不同的 `fruit_name`。这实际上是对 Janus 答案中理论的实现。 (2认同)
  • 两个表之间的任何关系都需要存储在第三个表中吗?我可以只通过存储两个表中的主键来进行查询以查找关系吗? (2认同)
  • 是的,这就是现在设置示例的方式.有关此人的任何信息应该在"人"表中,有关"水果"表中水果的任何信息,以及特定于"person_fruit"表中特定人与特定水果之间关系的任何信息.因为在这个例子中没有任何附加信息,`person_fruit`表只有两列,即`person`和`fruits`表的主键.特定水果的数量是其他可以在"person_fruit"表中出现的例子. (2认同)
  • 使用`INT`作为'fruits`中的键并且在`person_fruit`中只有这个`INT`不是更好吗?因此,如果你在`fruits`中的行数多于`person_fruit`中的行数,那么名称可以在以后更改,也需要更少的空间. (2认同)

Jan*_*sen 50

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

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

请阅读有关数据库规范化的内容.黄金法则是"[每个]非关键[属性]必须提供关于密钥,整个密钥以及密钥的事实." 数组做得太多了.它有多个事实,它存储订单(与关系本身无关).性能很差(见上文).

想象一下,你有一张人桌,你有一张桌子,可以让人打电话.现在你可以让每个人都有他的电话列表.但每个人与许多其他事物有许多其他关系.这是否意味着我的人员表应该包含他连接的每一件事物的数组?不,这不是这个人本身的属性.

[1]:如果链接表只有两列(每个表的主键),这没关系!如果关系本身具有其他属性,则应在此表中将其表示为列.

  • @tonga:数据量不确定要使用的数据库类型,数据的性质如何.如果没有关系,则不需要在关系数据库中.但由于这是行业标准,您可以保留它,而不是使用关系功能.大多数数据在某种程度上是相关的!对关系数据库进行非规范化或使用键值存储的常见原因是出于性能原因.但是这些问题只有在你有数百万行时才会出现!不要过早优化!我建议去一个SQL db(我推荐PostgreSQL).如果你有问题,请问. (5认同)
  • 感谢Janus。那讲得通。现在,我了解了为什么MySQL不支持列中的数组类型。 (2认同)
  • @Sai - 对于我正在做的事情,我真的需要NoSQL解决方案吗? (2认同)
  • PostgreSQL还内置了键值存储,这意味着如果它不适合你,就更容易摆脱关系模型. (2认同)

Cha*_*dis 44

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

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

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

此外,密钥:价值数据存储/文件存储,如Cassandra,MongoDB,Redis等也提供了一个很好的解决方案.请注意数据实际存储的位置(如果存储在磁盘或内存中).并非所有数据都需要位于同一数据库中.某些数据无法很好地映射到关系数据库,您可能有理由将其存储在其他位置,或者您可能希望使用内存中的键:值数据库作为存储在磁盘某处或作为临时存储的数据的热缓存像会话这样的东西.


Eri*_*tke 35

要考虑的旁注,您可以在Postgres中存储数组.

  • 这并不能以任何方式回答这个问题。OP询问了MySQL。 (6认同)
  • 附加说明:可以对它们进行索引,因此检查数组中是否存在特定值的查询会非常快。复杂的JSON类型也是如此。 (4认同)
  • 如果您在 Postgres 中使用 ArrayField 并在该列中有详尽的值列表(如固定的标签列表),您可以创建一个 GIN 索引 - 它将显着加快对该列的查询速度。 (2认同)

dre*_*rew 17

在MySQL中,使用JSON类型.

与上述答案相反,SQL标准已包含近20年的数组类型; 它们很有用,即使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);
Run Code Online (Sandbox Code Playgroud)

如果您希望将此人与一系列水果相关联,您可以通过以下视图进行关联:

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;
Run Code Online (Sandbox Code Playgroud)

该视图显示以下数据:

+---------------+-----------------+----------------------------------+
| pfs_person_id | pfs_person_name | pfs_fruit_name_array             |
+---------------+-----------------+----------------------------------+
|             1 | John            | ["apple", "orange"]              |
|             2 | Mary            | ["orange", "pineapple"]          |
|             3 | John            | ["apple", "orange", "pineapple"] |
+---------------+-----------------+----------------------------------+
Run Code Online (Sandbox Code Playgroud)

在5.7.22中,您将要使用JSON_ARRAYAGG,而不是从字符串中一起破解数组.