2011-08-27 59 views
2

我有(并没有拥有,所以我不能改变)与此类似的布局表。是否可以查询逗号分隔的列以获取特定值?

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;(例如,从谷歌)

我希望它返回行2和3。

是否有更好的方式来编写这些查询,而不是使用大量的LIKE语句?

+6

同样,这就是为什么您不在关系数据库中放置CSV或其他序列化值的原因之一。 – NullUserException

+5

另外'FIND_IN_SET'是一个MySQL函数,不会与Oracle – NullUserException

+4

一起工作......并且与您看起来一样糟糕,实际上更糟糕。除非您稍后过滤结果;你不能只搜索LIKE'%c2%' - 你实际上应该以这种方式指定每一个:“... where(categories ='c2'或类别'%,c2'或类别'%c2 ,'%'或类别'c2,%')。除非你这样做,否则你会匹配'c20','c201'等。 – Gerrat

回答

9

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

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

但此查询将是缓慢的,如将所有查询使用LIKE,尤其是与领先的通配符。

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

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

+0

+1不错的建议。 ...使坏的情况最好 – Gerrat

1

Yes和No ...

“是”:

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

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'。无论如何,请阅读正则表达式: 匹配行的开头或词边界,然后是目标搜索值,然后是一组字边界或行的结尾。

1

只要逗号分隔的列表是512个字符或更少,也可以使用在本实例中为正则表达式(Oracle的正则表达式的功能,例如,REGEXP_LIKE(),被限制为512个字符):

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

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