2011-08-27 58 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,那就更好了。