【问题标题】:How to store arrays in MySQL?如何在 MySQL 中存储数组?
【发布时间】:2020-11-18 12:54:21
【问题描述】:

我在 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中没有数组数据类型,那该怎么办呢?

【问题讨论】:

标签: mysql database-schema


【解决方案1】:

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

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

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

此外,键/值数据存储、Cassandra、MongoDB、Redis 等文档存储也提供了很好的解决方案。只需注意数据的实际存储位置(如果它存储在磁盘或内存中)。并非所有数据都需要位于同一个数据库中。有些数据不能很好地映射到关系数据库,您可能有理由将其存储在其他地方,或者您可能希望使用内存中的键:值数据库作为存储在某处磁盘上的数据的热缓存或作为临时存储诸如会话之类的事情。

【讨论】:

    【解决方案2】:

    在 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);
    

    如果您希望将人与一系列水果相关联,您可以通过视图来实现:

    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,而不是从一个字符串中破解数组。

    【讨论】:

      【解决方案3】:

      使用数据库字段类型 BLOB 来存储数组。

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

      返回值

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

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

      【讨论】:

        【解决方案4】:

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

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

        请阅读database normalization。黄金法则是“[每个] 非键 [属性] 必须提供关于键、整个键的事实,并且只提供键。”。数组做的太多了。它有多个事实并存储顺序(与关系本身无关)。而且性能很差(见上文)。

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

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

        【讨论】:

        • 谢谢 Janus。这就说得通了。现在我明白为什么 MySQL 不支持列中的数组类型了。
        • @Sai - 对于我正在做的事情,我真的需要 NoSQL 解决方案吗?
        • 好的,所以如果我有一个表,其中一个字段包含数千个元素的数字数组,例如,从传感器收集的一些 2D 数据,使用 NoSQL DB 会更好吗?跨度>
        • @tonga:数据量并不能决定要使用的数据库类型,而是数据的性质。如果没有关系,则不需要关系数据库。但由于这是行业标准,您可以保留它而不使用关系功能。大多数数据在某种程度上是相关的!非规范化关系数据库或使用键值存储的一个常见原因是出于性能原因。但是这些问题只有在您拥有数百万行时才会出现!不要过早优化!我建议只使用 SQL 数据库(我推荐 PostgreSQL)。如果您有问题,请询问。
        • PostgreSQL 还内置了键值存储,这意味着如果关系模型不适合您,则更容易摆脱它。
        【解决方案5】:

        执行此操作的正确方法是在查询中使用多个表并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 表有效地链接在一起,即,

        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
        

        【讨论】:

        • 第三张表是Person和Fruit之间的链接表。所以如果一个人有100个水果。我需要在第三个表中创建 100 行,对吗?这有效率吗?
        • @tonga 没错,100 行中的每一行都有相同的person_id,但有不同的fruit_name。这实际上是 Janus 回答中理论的一种实现。
        • 两个表之间的任何关系都需要存储在第三个表中是否总是正确的?我可以通过存储两个表中的主键来查询关系吗?
        • 是的,这就是现在设置示例的方式。有关此人的任何信息都应在person 表中,有关水果的任何信息应在fruits 表中,以及任何具体有关特定人与特定水果之间关系的信息应在person_fruit 表中。因为在此示例中没有任何附加信息,person_fruit 表只有两列,即personfruits 表的主键。但是,特定水果的数量是可以在 person_fruit 表中显示的其他示例。
        • INT 用作fruits 中的一个键并且在person_fruit 中只使用这个INT 不是更好吗?因此,可以稍后更改名称,并且如果 fruits 中的行数少于 person_fruit 中的行数,则所需空间也更少。
        【解决方案6】:

        需要考虑的一个旁注,您可以在 Postgres 中存储数组。

        【讨论】:

        • 附加说明:它们可以被索引,因此检查数组中是否存在特定值的查询可以非常快。复杂的 JSON 类型也是如此。
        • 这并不能以任何方式回答问题。 OP 询问了 MySQL。
        • 如果您在 Postgres 中使用 ArrayField 并且在该列中有一个详尽的值列表(如固定的标签列表),您可以创建一个 GIN 索引 - 它会显着加快该列上的查询.
        【解决方案7】:

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

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

        这里是example in fiddle

        【讨论】:

        • 没有很好的解释。错误的表名。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-25
        • 2017-03-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多