ПрограммированиеFullstack разработчик

Как работать с массивами и аналогами массивов в SQL для хранения и анализа множественных значений в одной ячейке, и когда оправдан такой подход?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса

Классический SQL не предусматривает хранение нескольких значений в одной ячейке — реляционная модель требует нормализации. Однако, в современных задачах часто встречаются поля типа "список тегов", "шкала оценок", где удобно оперировать именно множеством значений на уровне отдельной строки. Некоторые СУБД (PostgreSQL, Oracle) предоставляют типы данных ARRAY или аналогичные механизмы.

Проблема

Использование массивов нарушает принцип нормализации, затрудняет многие операции (фильтрация, обновление, индексация), а также делает код менее переносимым между СУБД. Но бывает удобно или неизбежно — например, для кэширования или быстрого поиска по небольшим спискам значений.

Решение

  • В PostgreSQL поддержка массива нативная. Пример:
CREATE TABLE products ( id SERIAL PRIMARY KEY, tags TEXT[] ); -- Вставка: INSERT INTO products(tags) VALUES (ARRAY['eco','sale','hot']); -- Поиск по массиву: SELECT * FROM products WHERE 'eco' = ANY (tags);
  • В MySQL 5.x массивов нет, часто используются JSON или разделённые строки и функции для разбора.
  • В Oracle — коллекции, nested table/varray.
  • Для оптимальных аналитических задач лучше нормализовать (создать связную вторичную таблицу product_tags) и использовать JOIN, а массив хранить лишь в особых случаях (performance или специфические требования).

Ключевые особенности:

  • Удобно, когда массив реально нужен и СУБД это поддерживает.
  • Проблемы с индексами и фильтрацией при больших массивах.
  • Не переносимо между СУБД, затрудняет поддержку.

Вопросы с подвохом.

Можно ли индексировать отдельные элементы массива?

В PostgreSQL — да, через GIN/GIST-индексы:

CREATE INDEX idx_tags ON products USING GIN (tags);

Как быстрее проверить вхождение значения в массив в строковой колонке через разделитель?

SQL стандартно не умеет, используют поиск по шаблону:

SELECT * FROM users WHERE ',admin,' like concat('%,',role,',%');

Но этот подход ненадёжен и медленный.

Сколько значений можно хранить в массиве, и что ограничивает?

Ограничение зависит от СУБД — например, в PostgreSQL ограничение только на размер строки (1–2 МБ).

Типовые ошибки и анти-паттерны

  • Сохранять массивы в одной ячейке ради "простоты" и усложнять анализ
  • Неверно фильтровать значения через LIKE без учёта разделителей
  • Полагаться на уникальность и индексацию по строкам-массивам

Пример из жизни

Негативный кейс

В ecommerce проекте теги товаров решили хранить как строку через запятую в одном столбце. Очень затруднился быстрый поиск товаров по тегу, ошибки в фильтрации, повторение тегов случалось из-за ошибок парсинга.

Плюсы:

  • "Просто" и быстро реализуемо

Минусы:

  • Очень медленно при масштабах, сложно поддерживать, невозможно гарантировать уникальность значений

Позитивный кейс

В PostgreSQL для маленьких, неизменяемых наборов (user roles) использовали ARRAY и GIN-индекс. Для больших — отдельную таблицу ролей.

Плюсы:

  • Быстрый поиск по ARRAY через индекс
  • Остаётся совместимость с реляционной моделью там, где нужно

Минусы:

  • Не переносимо, требует знания расширенных особенностей СУБД