是否有可能查询逗号分隔列的特定值?

我有(并没有拥有,所以我不能改变)一个布局类似于这个表。

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

我需要返回包含特定类别ID的行。 我通过用LIKE语句编写查询开始,因为值可以在string中的任何位置

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语句。 我在Oracle文档中find了FIND_IN_SET() ,但它在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语句?

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

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

但是这个查询将会很慢,所有使用LIKE的查询都会比较慢,特别是使用前导通配符。

总是有风险。 如果值周围有空格,或者值本身可以包含逗号(在这种情况下,它们被引号括起来,就像在csv文件中一样),这个查询将不起作用,您将不得不添加更多的逻辑,减慢查询速度更。

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

你可以写一个PIPELINED表函数,返回一个1列表。 每行都是逗号分隔的string中的一个值。 使用这样的东西从列表中pop一个string, 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文档

是和否…

“是”:

规范化数据(强烈推荐) – 即分类的列,以便你有每个分类在一个单独的…那么你可以只是在一个正常的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; 

然后你可以使用这个function:

 SELECT * FROM MyTable WHERE FIND_IN_SET (categories, 'c2' ) > 0; 

为了将来的search者,不要忘记正则expression式的方式:

 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 

这匹配在一个字的边界上,所以即使逗号后面跟着一个空格,它仍然可以工作。 如果您希望更加严格并且只匹配逗号分隔的值,请用逗号replace“\ W”。 无论如何,阅读正则expression式如下:匹配行的开始或词边界的一组,然后是目标search值,然后是一组字边界或行的结尾。

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

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

在上面我用正则expression式replace运算符|replace逗号 。 如果您的分隔值列表已经| – 有限,好多了。

Interesting Posts