2013-08-02 38 views
1

我在包中有2個過程。我正在調用一個過程來獲取逗號分隔的用戶標識列表。在NUMBER列的IN子句中使用逗號分隔值

我將結果存儲在VARCHAR變量中。現在,當我使用這個逗號分開,把一個IN子句中它拋出「ORA-01722:INVALID NUMBER"例外。

這是我的變量看起來像

l_userIds VARCHAR2(4000) := null; 

這是我在哪裏分配值

l_userIds := getUserIds(deptId); -- this returns a comma separated list 

我的第二個查詢是像 -

select * from users_Table where user_id in (l_userIds); 

如果我運行這個查詢,我得到INVALID NUMBER錯誤。

有人可以幫助這裏。

+0

查詢是不完整的,選擇本身是不是plsql(您需要將結果集存儲到變量中)請顯示完整查詢 – Sebas

+0

試試這個select * from users_Table其中user_id in(select l_userIds); – maSTAShuFu

回答

1

問題是oracle不會將您傳遞的VARCHAR2字符串解釋爲數字序列,而只是一個字符串。

一種解決方案是使整個查詢字符串(VARCHAR2),然後執行它使發動機知道他的翻譯內容:

DECLARE 
    TYPE T_UT IS TABLE OF users_Table%ROWTYPE; 
    aVar T_UT; 
BEGIN 
    EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar; 
... 

END; 

更復雜而且優雅的解決方案是拆分將該字符串轉換爲表TYPE並將其直接用於查詢。看什麼Tom thinks about it

8

你真的需要返回逗號分隔列表嗎?它通常會好得多聲明集合類型

CREATE TYPE num_table 
    AS TABLE OF NUMBER; 

聲明返回該集合的實例函數

CREATE OR REPLACE FUNCTION get_nums 
    RETURN num_table 
IS 
    l_nums num_table := num_table(); 
BEGIN 
    for i in 1 .. 10 
    loop 
    l_nums.extend; 
    l_nums(i) := i*2; 
    end loop; 
END; 

,然後使用該集合中查詢

SELECT * 
    FROM users_table 
WHERE user_id IN (SELECT * FROM TABLE(l_nums)); 

也可以使用動態SQL(@Sebas演示)。但是,不利的一面是,對該過程的每次調用都會生成一條新的SQL語句,在執行之前需要再次解析它。它還會對庫緩存施加壓力,這會導致Oracle清除大量其他可重用的SQL語句,這些語句可能會產生很多其他性能問題。

+0

以這種方式使用集合/表時,IN子句中1000個值的限制是否適用?謝謝。 –

+1

@BobJarvis - 不,它沒有。 1000值限制僅適用於文字或單個綁定變量,而不適用於查詢(無論是針對表格還是集合)。 –

+0

嗨@JustinCave剛試過這個,相信有兩個更正要求'return l_nums; '裏面的函數,而在查詢中使用集合 - '... select * from TABLE(get_nums)' –

2

可以使用like代替in搜索列表:

select * 
from users_Table 
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%'; 

這有簡單的優點(無附加功能或動態SQL)。但是,它確實排除了使用user_id上的索引。對於一張小巧的桌子來說,這不應該是一個問題。

-1

請勿使用此解決方案!

首先,我想刪除它,但我認爲,這可能是一個讓人看到如此糟糕的解決方案的信息。使用像這樣的動態SQL會導致創建多個執行計劃 - 對於IN子句中的每一組數據,有1個執行計劃,因爲沒有使用綁定,對於數據庫,每個查詢都是不同的(SGA會被大量非常類似的執行計劃,每次使用不同的參數運行查詢時,SGA中會不必要地使用更多的內存)。

想要使用動態SQL更正確地編寫另一個答案(使用綁定變量),但是Justin Cave's答案是最好的,無論如何。

你也可能想嘗試REF CURSOR(沒試過,準確的代碼我自己,可能需要一些小的調整):

DECLARE 
    deptId     NUMBER := 2; 
    l_userIds    VARCHAR2(2000) := getUserIds(deptId); 
    TYPE t_my_ref_cursor IS REF CURSOR; 
    c_cursor    t_my_ref_cursor; 
    l_row     users_Table%ROWTYPE; 
    l_query     VARCHAR2(5000); 
BEGIN 
    l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')'; 
    OPEN c_cursor FOR l_query; 

    FETCH c_cursor INTO l_row; 
    WHILE c_cursor%FOUND 
    LOOP 
     -- do something with your row 
     FETCH c_cursor INTO l_row; 
    END LOOP; 

END; 
/

+1

這樣做的動態SQL的作品(這是幾年前@Sebas建議)。但是,您正在生成獨特的,不可共享的SQL語句,這會給共享池帶來很大的壓力。如果您要頻繁運行此代碼,您將爲系統創建性能問題。 –

+0

非常感謝您的評論。事實上,引入動態SQL在性能方面更好。 – AndrewMcCoist