Як зберігати масиви в MySQL?


118

У мене дві таблиці в MySQL. Особа таблиці має такі стовпці:

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

Відповіді:


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Таблиця містить один рядок для кожного плоду людини , пов'язаного з і ефективно пов'язує 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
Третя таблиця - таблиця зв’язків між Особою та Фруктом. Тож якщо людина має 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

58

Причиною відсутності масивів у SQL є те, що більшість людей насправді не потребує цього. Реляційні бази даних (саме SQL) працюють з використанням відносин, і більшість часу найкраще, якщо ви присвоюєте один рядок таблиці кожному "шматочку інформації". Наприклад, там, де ви можете подумати, "я хотів би тут список речей", замість цього складіть нову таблицю, пов'язуючи рядок в одній таблиці з рядком в іншій таблиці. [1] Таким чином, ви можете представляти відносини M: N. Ще одна перевага полягає в тому, що ці посилання не захаращують рядок, що містить зв'язаний елемент. І база даних може індексувати ці рядки. Масиви зазвичай не індексуються.

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

Прочитайте , будь ласка, про нормалізацію бази даних . Золоте правило: "[Кожен] не-ключ [атрибут] повинен містити факт про ключ, весь ключ і нічого, крім ключа." Масив робить занадто багато. У ньому є кілька фактів, і він зберігає порядок (який не пов'язаний із самим відношенням). А продуктивність погана (див. Вище).

Уявіть, що у вас є стіл для людей і у вас стіл з телефонними дзвінками людей. Тепер ви можете зробити так, щоб кожен рядок мав список його телефонних дзвінків. Але кожна людина має багато інших стосунків до багатьох інших речей. Чи означає це, що таблиця моєї особи повинна містити масив для кожної речі, до якої він підключений? Ні, це не атрибут самої людини.

[1]: Добре, якщо у таблиці зв'язання є лише два стовпці (первинні ключі від кожної таблиці)! Якщо самі відносини мають додаткові атрибути, вони повинні бути представлені у цій таблиці як стовпці.


2
Дякую Янусю. Що має сенс. Тепер я розумію, чому MySQL не підтримує тип масиву в стовпці.
тонга

2
@Sai - чи мені справді потрібне рішення NoSQL?
тонга

1
Гаразд, тому якщо у мене є таблиця, у якій поле містить числовий масив з тисяч елементів, наприклад, деякі двовимірні дані, зібрані з датчика, чи набагато краще використовувати БД NoSQL?
тонга

5
@tonga: Об'єм даних не визначає тип db для використання, характер даних. Якщо немає відносин, вам не потрібно в реляційній базі даних. Але оскільки це галузевий стандарт, ви можете зберігати його та просто не використовувати реляційні функції. Більшість даних певним чином є реляційними! Поширена причина денормалізації реляційних баз даних або використання сховищ ключових значень - це з причин продуктивності. Але ці проблеми виникають лише після того, як у вас є МІЛЬЙОНИ рядів! Не оптимізуйте передчасно! Я рекомендую просто перейти з db SQL (я рекомендую PostgreSQL). Якщо у вас є проблеми, запитайте.
Янус Троельсен

2
У PostgreSQL також є вбудовані сховища ключових значень, а це означає, що відійти від реляційної моделі буде навіть простіше, якщо вона вам не підходить.
Янус Троельсен

50

MySQL 5.7 тепер забезпечує тип даних JSON . Цей новий тип даних забезпечує зручний новий спосіб зберігання складних даних: списків, словників тощо.

Однак, рядки не відображають добре бази даних, тому об'єктно-реляційні карти можуть бути досить складними. Історично люди зберігають списки / масиви в MySQL, створюючи таблицю, яка описує їх, і додаючи кожне значення як власний запис. Таблиця може мати лише 2 або 3 стовпчики, або вона може містити ще багато інших. Спосіб зберігання цього типу даних дійсно залежить від характеристик даних.

Наприклад, чи містить список статичну чи динамічну кількість записів? Чи залишиться список невеликим, чи очікується, що він зросте до мільйонів записів? Чи буде багато читань на цій таблиці? Багато пише? Багато оновлень? Це все фактори, які необхідно враховувати, вирішуючи, як зберігати колекції даних.

Також ключове: сховища даних про цінності / магазини документів, такі як Cassandra, MongoDB, Redis тощо, також є хорошим рішенням. Тільки знайте, де насправді зберігаються дані (якщо вони зберігаються на диску чи в пам'яті). Не всі ваші дані повинні бути в одній базі даних. Деякі дані не відображаються добре в реляційній базі даних, і у вас можуть бути причини для їх зберігання в іншому місці, або ви можете використовувати ключ пам'яті: база даних значень як гарячий кеш для даних, збережених на диску десь або як ефемерне сховище для таких речей, як сеанси.


43

Сторона, яку слід врахувати, ви можете зберігати масиви в Postgres.


6
Додаткова примітка: їх можна проіндексувати, тому перевірка запитів на наявність конкретних значень у масиві може бути дуже швидкою. Те саме стосується складних типів JSON.
тимчасово

5
Це жодним чином не відповідає на питання. ОП запитав про MySQL.
jhpratt

1
Якщо ви використовуєте ArrayField у Postgres і маєте вичерпний список значень у цьому стовпчику (наприклад, фіксований список тегів), ви можете створити індекс GIN - це значно пришвидшить запити до цього стовпця.
lumos42

25

У MySQL використовуйте тип JSON.

Якщо відповідати вищезазначеним відповідям, стандарт SQL включає типи масивів протягом майже двадцяти років; вони корисні, навіть якщо MySQL їх не реалізував.

У вашому прикладі, однак, ви, ймовірно, захочете створити три таблиці: людина та фрукти, а потім 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

Повернення цінностей

Повертає рядок, що містить подання байтового потоку значення, яке може зберігатися в будь-якому місці.

Зауважте, що це двійковий рядок, який може містити нульові байти, і його потрібно зберігати та обробляти як такий. Наприклад, вихід серіалізації () зазвичай повинен зберігатися в полі BLOB в базі даних, а не в полі CHAR або TEXT.


-4

ви можете зберігати масив, використовуючи подібний group_Concat

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

ТУТ приклад у скрипці


4
Не добре пояснено. Погані назви таблиці.
Мартін Ф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.