Как отфильтровать строки по значениям вложенного JSON массива

    При работе с базами данных часто возникает задача: в колонке хранится JSON-массив объектов, и нужно выбрать только те строки, где хотя бы один элемент массива содержит определённую подстроку в поле name. Стандартный оператор -> не подходит, так как он обращается к верхнему уровню (индексу массива). Разберём эффективные решения для разных СУБД.

    Особенности JSON структуры

    Предположим, колонка data содержит массив вида:

    [  {    "name": "test",    "email": "test@test.com"  },  {    "name": "host",    "email": "host@host.com"  }]

    Нам нужно найти строки, где хотя бы один объект имеет name, содержащий подстроку "st". Прямой доступ через data->0->>'name' проверяет только первый элемент, что не подходит при произвольной длине массива.

    Решение для MySQL и MariaDB

    В MySQL (8.0+) и MariaDB (10.2+) используйте функцию JSON_SEARCH:

    SELECT * FROM table_nameWHERE JSON_SEARCH(data, 'one', '%st%', NULL, '$[*].name') IS NOT NULL;

    Параметр 'one' означает, что достаточно найти первое совпадение. Шаблон '%st%' работает как стандартный LIKE. Путь '$[*].name' перебирает все элементы массива. Альтернатива - JSON_CONTAINS с LIKE через JSON_TABLE.

    Решение для PostgreSQL

    В PostgreSQL используйте оператор @> (содержит) с приведением типов или функцию jsonb_path_exists:

    SELECT * FROM table_nameWHERE data @> '[{"name": "test"}]'::jsonb;

    Но этот способ ищет точное совпадение. Для поиска по подстроке применяйте jsonb_path_exists с выражением like_regex:

    SELECT * FROM table_nameWHERE jsonb_path_exists(data, '$[*].name ? (@ like_regex ".*st.*")');

    Если колонка типа json (не jsonb), сначала приведите её: data::jsonb.

    Решение для ClickHouse

    В ClickHouse для фильтрации по элементам массива используйте функцию arrayExists в паре с JSONExtractString:

    SELECT * FROM table_nameWHERE arrayExists(x -> JSONExtractString(x, 'name') LIKE '%st%', JSONExtractArrayRaw(data));

    Функция JSONExtractArrayRaw превращает массив в массив строк, затем arrayExists проверяет условие для каждого элемента.

    Общий подход через JSON_TABLE (SQL стандарт)

    Если ваша СУБД поддерживает JSON_TABLE (MySQL 8+, Oracle, SQL Server 2016+), можно развернуть массив в строки и затем отфильтровать:

    SELECT DISTINCT t.idFROM table_name t, JSON_TABLE(t.data, '$[*]' COLUMNS (name VARCHAR(100) PATH '$.name')) AS jtWHERE jt.name LIKE '%st%';

    Этот метод универсален и позволяет выполнять любые дополнительные проверки.

    Советы по производительности

    • Для частых запросов создайте генерализованный индекс (MySQL: INDEX idx_name ((CAST(data->'$[*].name' AS CHAR(100) ARRAY)))).
    • В PostgreSQL используйте GIN индекс на колонке jsonb: CREATE INDEX idx_gin ON table_name USING GIN (data jsonb_path_ops);.
    • Избегайте полного сканирования таблицы - тестируйте запросы на EXPLAIN.

    Заключение

    Выбор метода зависит от СУБД и версии. Для MySQL подойдёт JSON_SEARCH, для PostgreSQL - jsonb_path_exists, для ClickHouse - arrayExists. Если нужна максимальная гибкость, используйте JSON_TABLE. Всегда проверяйте планы выполнения запросов.

    Часто задаваемые вопросы