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

时间:2011-08-27 02:58:48

标签: sql oracle csv ora-00904 denormalized

我有(并且不拥有,所以我无法更改)具有类似于此的布局的表。

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语句。我在Oracle文档中找到了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语句?

5 个答案:

答案 0 :(得分:10)

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

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

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

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

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

答案 1 :(得分: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 docs

答案 2 :(得分: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;

答案 3 :(得分: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

这与单词边界匹配,因此即使逗号后跟一个空格,它仍然可以工作。如果您想要更严格并且仅匹配逗号分隔值的位置,请替换&#39; \ W&#39;用逗号。无论如何,请将正则表达式读为: 匹配行的开头或单词边界的一组,后跟目标搜索值,后跟一个单词边界或行尾的组。

答案 4 :(得分:1)

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

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

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