Можно ли запросить столбцы, разделенные запятыми, для определенного значения?
У меня (и не владею, поэтому я не могу изменить) таблицу с макетом, подобным этому.
ID | CATEGORIES
---------------
1 | c1
2 | c2,c3
3 | c3,c2
4 | c3
5 | c4,c8,c5,c100
Мне нужно вернуть строки, содержащие определенный идентификатор категории. Я начинаю с написания запросов с помощью операторов LIKE, потому что значения могут быть в любом месте строки
SELECT id FROM table WHERE categories LIKE '%c2%';
Вернул бы строки 2 и 3
SELECT id FROM table WHERE categories LIKE '%c3%' and categories LIKE '%c2%';
Я бы снова получил строки 2 и 3, но не row 4
SELECT id FROM table WHERE categories LIKE '%c3%' or categories LIKE '%c2%';
Снова получим строки 2, 3 и 4
Мне не нравятся все операторы LIKE
. Я нашел FIND_IN_SET()
в документации Oracle, но, похоже, не работает в 10g. Я получаю следующую ошибку:
ORA-00904: "FIND_IN_SET": invalid identifier
00904. 00000 - "%s: invalid identifier"
при выполнении этого запроса: SELECT id FROM table WHERE FIND_IN_SET('c2', categories);
(пример из документов) или этот запрос: SELECT id FROM table WHERE FIND_IN_SET('c2', categories) <> 0;
(пример из Google)
Я ожидаю, что он вернет строки 2 и 3.
Есть ли лучший способ записать эти запросы вместо использования т > выражений LIKE
?
Ответы
Ответ 1
Вы можете использовать LIKE. Вы не хотите соответствовать частичным значениям, поэтому вам придется включать запятые в свой поиск. Это также означает, что вам нужно будет предоставить дополнительную запятую для поиска значений в начале или конце вашего текста:
select
*
from
YourTable
where
',' || CommaSeparatedValueColumn || ',' LIKE '%,SearchValue,%'
Но этот запрос будет медленным, как и все запросы, использующие LIKE, особенно с ведущим шаблоном.
И всегда есть риск. Если вокруг значений есть пробелы или значения могут содержать запятые, в этом случае они окружены кавычками (например, в файлах csv), этот запрос не будет работать, и вам придется добавить еще больше логики, замедляя ваш запрос даже больше.
Лучшим решением было бы добавить дочернюю таблицу для этих категорий. Вернее, даже отдельная таблица для каталогов и таблица, которая пересекает их с помощью YourTable.
Ответ 2
Вы можете написать функцию таблицы PIPELINED, которая возвращает 1 столбец столбца. Каждая строка представляет собой значение из строки, разделенной запятыми. Используйте что-то вроде этого для pop
строки из списка и put
как строку в таблице:
PIPE ROW(ltrim(rtrim(substr(l_list, 1, l_idx - 1),' '),' '));
Использование:
SELECT * FROM MyTable
WHERE 'c2' IN TABLE(Util_Pkg.split_string(categories));
Подробнее здесь: Документы Oracle
Ответ 3
Да и Нет...
"Да":
Нормализовать данные (настоятельно рекомендуется) - т.е. разбить столбец категории, чтобы у вас была каждая категория в отдельном... тогда вы можете просто запросить его в обычном faschion...
"Нет":
Пока вы держите эту "псевдоструктуру", будет несколько проблем (производительность и другие), и вам нужно будет сделать что-то похожее на:
SELECT * FROM MyTable WHERE categories LIKE 'c2,%' OR categories = 'c2' OR categories LIKE '%,c2,%' OR categories LIKE '%,c2'
ЕСЛИ вы абсолютно должны определить функцию, которая называется FIND_IN_SET, как показано ниже:
CREATE OR REPLACE Function FIND_IN_SET
( vSET IN varchar2, vToFind IN VARCHAR2 )
RETURN number
IS
rRESULT number;
BEGIN
rRESULT := -1;
SELECT COUNT(*) INTO rRESULT FROM DUAL WHERE vSET LIKE ( vToFine || ',%' ) OR vSET = vToFind OR vSET LIKE ('%,' || vToFind || ',%') OR vSET LIKE ('%,' || vToFind);
RETURN rRESULT;
END;
Затем вы можете использовать эту функцию, например:
SELECT * FROM MyTable WHERE FIND_IN_SET (categories, 'c2' ) > 0;
Ответ 4
Для будущих поисковиков не забывайте о регулярном выражении:
with tbl as (
select 1 ID, 'c1' CATEGORIES from dual
union
select 2 ID, 'c2,c3' CATEGORIES from dual
union
select 3 ID, 'c3,c2' CATEGORIES from dual
union
select 4 ID, 'c3' CATEGORIES from dual
union
select 5 ID, 'c4,c8,c5,c100' CATEGORIES from dual
)
select *
from tbl
where regexp_like(CATEGORIES, '(^|\W)c3(\W|$)');
ID CATEGORIES
---------- -------------
2 c2,c3
3 c3,c2
4 c3
Это соответствует границе слова, поэтому даже если за запятой следует пробел, он все равно будет работать. Если вы хотите быть более строгим и соответствовать только там, где запятая разделяет значения, замените "\ W" запятой. Во всяком случае, прочитайте регулярное выражение как:
сопоставить группу либо начала строки, либо границы слова, за которой следует целевое значение поиска, за которым следует группа либо границы слова, либо конца строки.
Ответ 5
Пока список с разделителями-запятыми составляет 512 символов или меньше, вы также можете использовать регулярное выражение в этом экземпляре (функции регулярного выражения Oracle, например, REGEXP_LIKE()
, ограничены 512 символами):
SELECT id, categories
FROM mytable
WHERE REGEXP_LIKE('c2', '^(' || REPLACE(categories, ',', '|') || ')$', 'i');
В вышеизложенном я заменяю запятые оператором чередования регулярных выражений |
. Если ваш список разделенных значений уже |
-delimited, тем лучше.