SQL - Как проверить, есть ли элемент в списке в записи - PullRequest
1 голос
/ 15 июня 2011

У меня есть имя столбца MyRoles, который содержит список элементов (целых чисел), хранящихся в поле с именем UserRoles. Я хочу написать запрос, который проверяет, есть ли определенный элемент в списке. Список будет выглядеть так: «1,2,3»

Я не могу использовать ГДЕ MyRoles

Как должен выглядеть запрос?

Это похоже на то, что я думаю:

SELECT *
FROM MyTable
WHERE MyRoles CONTAINS ('1')

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

Ответы [ 5 ]

2 голосов
/ 15 июня 2011

Вы можете использовать LIKE:

SELECT *
FROM MyTable
WHERE MyRoles LIKE ('%1%')

Вероятно, это очень плохо работает (так как индекс будет довольно бесполезным для такого поиска). И, конечно, также будет соответствовать 10, даже если 1 не существует в запросе. Вы можете расширить подход:

SELECT *
FROM MyTable
WHERE MyRoles = '1'
  OR MyRoles LIKE '1,%'
  OR MyRoles LIKE '%,1,%'

Лучшим решением было бы нормализовать вашу базу данных и не иметь многозначных полей. Используйте таблицу «многие ко многим» с идентификаторами отдельных ролей и идентификаторов элементов на строку. Это гораздо проще для запроса.

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

1 голос
/ 16 июня 2011

Преобразуйте его в массив:

SELECT *
FROM MyTable
WHERE ('{' || MyRoles || '}')::int[] && array[1]

Еще лучше, вы можете использовать индекс для вышеупомянутого беспорядка.Непосредственное приведение текста к типу массива будет отклонено при построении массива, но вы можете обойти его:

create function role_scan(text) returns int[] as $$
  select ('{' || $1 || '}')::int[];
$$ language sql immutable strict;

create index on MyTable using gin (role_scan(MyRoles));

-- actually use the index
SELECT *
FROM MyTable
WHERE role_scan(MyRoles) && array[1]

При добавлении индекса есть предостережение, о котором вы должны знать.Сборщик статистики не просматривает (в любом случае, до 9,1) фактические значения массива.Избирательность оператора перекрытия (1/200, т. Е. Очень избирательная) жестко запрограммирована для всех целей и задач.Поэтому, если вы запрашиваете очень распространенные значения, вы можете получить сканирование индекса там, где это неуместно.Одним из обходных путей является непосредственный вызов базового метода перекрытия (который обеспечивает избирательность 1/3 и отсутствие потенциального сканирования индекса), когда известно, что применяется множество ролей:

SELECT *
FROM MyTable
WHERE arrayoverlap(role_scan(MyRoles), array[1])
1 голос
/ 15 июня 2011

Остерегайтесь, если вы используете LIKE:

Если MyRoles равен 2,11, тогда оно будет соответствовать LIKE('%1%'), хотя вы этого не хотите.

Использовать мучительный обходной путь

SELECT *
FROM MyTable
WHERE MyRoles LIKE ('%,1,%')

но тогда вам нужно ставить начальные и конечные запятые в каждой записи MyRoles.

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

0 голосов
/ 16 июня 2011

Может быть, здесь помогут регулярные выражения:

SELECT *
FROM MyTable
WHERE MyRoles ~ ('^(.*,)*' || 1 || '(,.*)*$')
0 голосов
/ 15 июня 2011
SELECT *
FROM MyTable
WHERE FIND_IN_SET(1, MyRoles)

EDIT: Он работает на сервере MySQL базы данных.

EDIT:

find_in_set function для postgres:

create function find_in_set(n int, s text) returns bigint as
$$
select z.row_number
from
(
    select row_number() over(), y.x
    from (select unnest(('{' || $2 || '}')::int[]) as x) as y
) as z
where z.x = $1
$$ language sql;
...