3

我有(并且不拥有,所以我不能更改)一个布局与此类似的表格。

ID | CATEGORIES
---------------
1  | c1
2  | c2,c3
3  | c3,c2
4  | c3
5  | c4,c8,c5,c100

我需要返回包含特定类别 ID 的行。我首先使用 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 行,但不是第 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语句?

4

5 回答 5

12

您可以使用 LIKE。您不想匹配部分值,因此您必须在搜索中包含逗号。这也意味着您必须提供额外的逗号来搜索文本开头或结尾的值:

select 
  * 
from
  YourTable 
where 
  ',' || CommaSeparatedValueColumn || ',' LIKE '%,SearchValue,%'

但是这个查询会很慢,所有使用 LIKE 的查询也会很慢,尤其是带有前导通配符的查询。

而且总是有风险。如果值周围有空格,或者值本身可以包含逗号,在这种情况下它们被引号包围(如在 csv 文件中),则此查询将不起作用,您必须添加更多逻辑,从而减慢查询速度更。

更好的解决方案是为这些类别添加一个子表。或者更确切地说,甚至是一个单独的类别表,以及一个将它们交叉链接到 YourTable 的表。

于 2011-08-27T06:51:04.560 回答
2

您可以编写一个返回 1 列表的 PIPELINED 表函数。每行都是逗号分隔字符串中的一个值。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 文档

于 2014-06-05T11:26:37.150 回答
1

是与否...

“是的”:

规范化数据(强烈推荐) - 即拆分类别列,以便您将每个类别放在一个单独的...然后您可以以正常方式查询它...

“否”:
只要您保持这种“伪结构”,就会有几个问题(性能和其他),您将不得不做类似的事情:

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;
于 2011-08-27T04:35:00.680 回答
1

为了以后的搜索者,不要忘记正则表达式的方式:

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”替换为逗号。无论如何,将正则表达式读作:匹配一组行首或单词边界,然后是目标搜索值,然后是一组单词边界或行尾。

于 2014-10-24T20:33:29.487 回答
1

只要逗号分隔的列表不超过 512 个字符,您还可以在此实例中使用正则表达式(Oracle 的正则表达式函数,例如,REGEXP_LIKE()限制为 512 个字符):

SELECT id, categories
  FROM mytable
 WHERE REGEXP_LIKE('c2', '^(' || REPLACE(categories, ',', '|') || ')$', 'i');

在上面我用正则表达式替换运算符替换逗号|。如果您的分隔值列表已经是|-delimited,那就更好了。

于 2015-04-03T17:18:17.183 回答