我有(并且不拥有,所以我无法更改)具有类似于此的布局的表。
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
语句?
答案 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');
在上面我用正则表达式替换运算符|
替换逗号。如果您的分隔值列表已经|
- 已分隔,那就更好了。