Как хранить массивы в MySQL?

119

У меня есть две таблицы в MySQL. В таблице Person есть следующие столбцы:

id | name | fruits

fruitsСтолбец может содержать нуль или массив строк типа ( «яблоко», «апельсин», «бананового»), или ( «клубника») и т.д. вторая таблица Таблица фруктов и имеет следующие три столбца:

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

Итак, как мне разработать fruits столбец в первой таблице, чтобы он мог содержать массив строк, принимающих значения из fruit_nameстолбца во второй таблице? Поскольку в MySQL нет типа данных массива, как мне это сделать?

Тонга
источник
1
как насчет добавления его как отдельных записей, оранжевого, 2, 1, розового, 2, 1 и т. д., а затем вы можете использовать запросы для обработки их, как если бы они были массивами.
Сай
@JanusTroelsen: Я не использую PHP для чтения / записи БД. Так есть ли универсальный способ сделать это?
Тонга
1
@tonga проверьте мою скрипку, это то, что вы хотите?
echo_Me

Ответы:

164

Правильный способ сделать это - использовать несколько таблиц и 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Таблица содержит одну строку для каждого плода человека , связанного с и эффективно связывает 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
Злой волк
источник
4
Третья таблица - это таблица связей между Person и Fruit. Итак, если у человека 100 плодов. Мне нужно создать 100 строк в третьей таблице, верно? Это эффективно?
Тонга
1
@tonga Точно, каждая из 100 строк будет иметь одинаковые, person_idно разные fruit_name. Это фактически реализация теории из ответа Януса.
Bad Wolf
1
Всегда ли верно, что любое отношение между двумя таблицами должно храниться в третьей таблице? Могу я просто сделать запрос, чтобы найти связь, просто сохранив первичные ключи из двух таблиц?
Тонга
2
Да, именно так сейчас настроен пример. Любая информация о человеке должна быть в personтаблице, любая информация о фрукте в fruitsтаблице и любая информация, конкретно касающаяся отношений между конкретным человеком и конкретным фруктом в person_fruitтаблице. Потому что в этом примере нет никакой дополнительной информации для person_fruitтаблицы только две колонны, первичные ключи personи fruitsтаблиц. Однако количество определенного фрукта является примером чего-то еще, что может быть указано в person_fruitтаблице.
Bad Wolf
2
Не было бы лучше использовать INTдля ключа в fruitsи только это INTв person_fruit? Таким образом, имя можно изменить позже, и ему также потребуется меньше места, если у вас не намного больше строк, fruitsчем в person_fruit.
12431234123412341234123
59

Причина того, что в SQL нет массивов, заключается в том, что большинству людей он действительно не нужен. Реляционные базы данных (как раз и является SQL) работают с использованием отношений, и в большинстве случаев лучше, если вы назначите одну строку таблицы каждому «биту информации». Например, если вы думаете: «Мне нужен здесь список вещей», вместо этого создайте новую таблицу, связав строку в одной таблице со строкой в ​​другой таблице. [1] Таким образом, вы можете представить отношения M: N. Еще одно преимущество состоит в том, что эти ссылки не загромождают строку, содержащую связанный элемент. И база данных может индексировать эти строки. Массивы обычно не индексируются.

Если вам не нужны реляционные базы данных, вы можете использовать, например, хранилище ключей и значений.

Прочтите о нормализации базы данных , пожалуйста, . Золотое правило гласит: «[Каждый] неключевой [атрибут] должен предоставлять факт о ключе, весь ключ и ничего, кроме ключа». Массив делает слишком много. Он содержит несколько фактов и хранит порядок (который не связан с самим отношением). И производительность оставляет желать лучшего (см. Выше).

Представьте, что у вас есть стол с людьми, и у вас есть стол, за которым звонят люди. Теперь вы можете создать для каждой строки человека список его телефонных звонков. Но у каждого человека много других отношений ко многому другому. Означает ли это, что моя личная таблица должна содержать массив для каждой отдельной вещи, к которой он подключен? Нет, это не атрибут самого человека.

[1]: Ничего страшного, если таблица связывания имеет только два столбца (первичные ключи из каждой таблицы)! Если у самой связи есть дополнительные атрибуты, они должны быть представлены в этой таблице в виде столбцов.

Янус Троельсен
источник
2
Спасибо, Янус. В этом есть смысл. Теперь я понимаю, почему MySQL не поддерживает тип массива в столбце.
Тонга
2
@Sai - Действительно ли мне нужно решение NoSQL для того, что я делаю?
Тонга
1
Хорошо, если у меня есть таблица, в которой поле содержит числовой массив из тысяч элементов, например, некоторые 2D-данные, собранные с датчика, лучше ли использовать базу данных NoSQL?
Тонга
5
@tonga: количество данных не определяет тип используемой базы данных, а характер данных. Если отношений нет, в реляционной базе данных нет необходимости. Но поскольку это отраслевой стандарт, вы можете сохранить его и просто не использовать реляционные функции. Большинство данных в некотором роде реляционные! Распространенная причина денормализации реляционных баз данных или использования хранилищ ключ-значение - это соображения производительности. Но эти проблемы возникают только тогда, когда у вас есть МИЛЛИОНЫ строк! Не оптимизируйте преждевременно! Я бы рекомендовал просто использовать базу данных SQL (я рекомендую PostgreSQL). Если возникнут проблемы, спросите.
Janus Troelsen
2
PostgreSQL также имеет встроенные хранилища ключей и значений, а это означает, что будет еще проще отказаться от реляционной модели, если она вам не подходит.
Янус Троэльсен,
50

MySQL 5.7 теперь предоставляет тип данных JSON . Этот новый тип данных обеспечивает удобный новый способ хранения сложных данных: списков, словарей и т. Д.

Тем не менее, rrays плохо отображают базы данных, поэтому объектно-реляционные карты могут быть довольно сложными. Исторически люди сохраняли списки / массивы в MySQL, создавая таблицу, которая их описывает, и добавляя каждое значение как свою собственную запись. В таблице может быть только 2 или 3 столбца, а может быть и больше. То, как вы храните этот тип данных, действительно зависит от характеристик данных.

Например, содержит ли список статическое или динамическое количество записей? Останется ли список небольшим или ожидается, что он вырастет до миллионов записей? Будет ли много чтений по этой таблице? Много пишет? Много обновлений? Это все факторы, которые необходимо учитывать при принятии решения о том, как хранить коллекции данных.

Кроме того, хранилища данных Key: Value / хранилища документов, такие как Cassandra, MongoDB, Redis и т. Д., Также являются хорошим решением. Просто помните, где на самом деле хранятся данные (если они хранятся на диске или в памяти). Не все ваши данные должны находиться в одной базе данных. Некоторые данные плохо отображаются в реляционной базе данных, и у вас могут быть причины для их хранения в другом месте, или вы можете использовать базу данных ключ: значение в памяти в качестве горячего кеша для данных, хранящихся где-то на диске, или в качестве временного хранилища для таких вещей, как сеансы.

Чарльз Аддис
источник
43

Следует учесть, что вы можете хранить массивы в Postgres.

Эрик Гротке
источник
6
Дополнительное примечание: их можно индексировать, поэтому запросы, проверяющие наличие определенных значений в массиве, могут выполняться очень быстро. То же самое и со сложными типами JSON.
timetofly
5
Это никак не отвечает на вопрос. ОП спросил о MySQL.
jhpratt
1
Если вы используете ArrayField в Postgres и имеете исчерпывающий список значений в этом столбце (например, фиксированный список тегов), вы можете создать индекс 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.

webdevfreak
источник
-4

вы можете сохранить свой массив, используя group_Concat, например

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

ЗДЕСЬ пример на скрипке

echo_Me
источник
4
Не очень хорошо объяснено. Плохие имена таблиц.
Martin F