Можно ли запросить столбцы, разделенные запятыми, для определенного значения?

У меня (и не владею, поэтому я не могу изменить) таблицу с макетом, подобным этому.

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, тем лучше.