db2 將逗號分隔數據轉換為多值IN列表
將逗號分隔數據轉換為多值IN列表
2010-03-15 11:16:59| 分類: 數據庫技術|舉報|字號 訂閱
下載LOFTER我的照片書 |原文:http://book.csdn.net/bookfiles/530/10053017906.shtml
6.11 將分隔數據轉換為多值IN列表
問題
已經有了分隔數據,想要將其轉換為WHERE子句IN列表中的項目。考慮下面的字符串:
7654,7698,7782,7788
要將該字符串用在WHERE子句中,但是下面的SQL語句是錯誤的,因為EMPNO是一個數值列:
select ename,sal,deptno
from emp
where empno in ( ‘7654,7698,7782,7788‘ )
因為EMPNO是一個數值列,而此IN列表是一個字符串值,所以此SQL語句會失敗。現要將此字符串轉換為用逗號分解的數值列表。
解決方案
表面上看SQL應該將分隔字符串作為一個分隔值列表對待,但是實際情況不是這樣。當SQL遇到括在引號中的逗號時,並不知道此符號表示多值列表,SQL必須將括在引號中的內容作為一個整體對待,也就是一個字符串值。因此必須將字符串分解為各個單獨的EMPNO。這種解決方案的關鍵就是需要遍歷字符串,但並不是一個字符一個字符地遍歷,而是要將這個字符串轉換為有效的EMPNO值。
DB2
通過遍歷傳遞給IN列表的字符串,可以很輕松地將其轉換為若幹行。在這裏函數ROW_NOMBER、LOCATE和SUBSTR尤其有用:
1 select empno,ename,sal,deptno
2 from emp
3 where empno in (
4 select cast(substr(c,2,locate(‘,‘,c,2)-2) as integer) empno
5 from (
6 select substr(csv.emps,cast(iter.pos as integer)) as c
7 from (select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ emps
8 from t1) csv,
9 (select id as pos
10 from t100 ) iter
11 where iter.pos <= length(csv.emps)
12 ) x
13 where length(c) > 1
14 and substr(c,1,1) = ‘,‘
15 ) y
MySQL
通過遍歷傳遞給IN列別的字符串,可以很輕松地將其轉換為若幹行:
1 select empno, ename, sal, deptno
2 from emp
3 where empno in
4 (
5 select substring_index(
6 substring_index(list.vals,‘,‘,iter.pos),‘,‘,-1) empno
6 from (select id pos from t10) as iter,
7 (select ‘7654,7698,7782,7788‘ as vals
8 from t1) list
9 where iter.pos <=
10 (length(list.vals)-length(replace(list.vals,‘,‘,‘‘)))+1
11 ) x
Oracle
通過遍歷傳遞給IN列別的字符串,可以很輕松地將其轉換為若幹行。這裏函數ROWNUM,SUBSTR和INSTR尤其有用:
1 select empno,ename,sal,deptno
2 from emp
3 where empno in (
4 select to_number(
5 rtrim(
6 substr(emps,
7 instr(emps,‘,‘,1,iter.pos)+1,
8 instr(emps,‘,‘,1,iter.pos+1) -
9 instr(emps,‘,‘,1,iter.pos)),‘,‘)) emps
10 from (select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ emps from t1) csv,
11 (select rownum pos from emp) iter
12 where iter.pos <= ((length(csv.emps)-
13 length(replace(csv.emps,‘,‘)))/length(‘,‘))-1
14 )
Postgres
通過遍歷傳遞給IN列別的字符串,可以很輕松地將其轉換為若幹行。使用函數 SPLIT_PART可以簡化將字符串解析為單獨的數值列表的工作:
1 select ename,sal,deptno
2 from emp
3 where empno in (
4 select cast(empno as integer) as empno
5 from (
6 select split_part(list.vals,‘,‘,iter.pos) as empno
7 from (select id as pos from t10) iter,
8 (select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ as vals
9 from t1) list
10 where iter.pos <=
11 length(list.vals)-length(replace(list.vals,‘,‘,‘‘))
12 ) z
13 where length(empno) > 0
14 ) x
SQL Server
通過遍歷傳遞給IN列別的字符串,可以很輕松地將其轉換為若幹行。這裏函數ROW_NUMBER、CHARINDEX和SUBSTRING尤其有用:
1 select empno,ename,sal,deptno
2 from emp
3 where empno in (select substring(c,2,charindex(‘,‘,c,2)-2) as empno
4 from (
5 select substring(csv.emps,iter.pos,len(csv.emps)) as c
6 from (select ‘,‘+‘7654,7698,7782,7788‘+‘,‘ as emps
7 from t1) csv,
8 (select id as pos
9 from t100) iter
10 where iter.pos <= len(csv.emps)
11 ) x
12 where len(c) > 1
13 and substring(c,1,1) = ‘,‘
14 ) y
討論
這種解決方案中第一步,也是最重要的一步就是遍歷字符串。一旦完成了這步操作,剩下的操作就是使用DBMS提供的函數來將字符串解析為單獨的數值。
DB2和SQL Server
內聯視圖X(第6~11行)遍歷字符串,這裏用的是“穿越”字符串的思想,所以其每一行都比其上一行少一個字符:
,7654,7698,7782,7788,
7654,7698,7782,7788,
654,7698,7782,7788,
54,7698,7782,7788,
4,7698,7782,7788,
,7698,7782,7788,
7698,7782,7788,
698,7782,7788,
98,7782,7788,
8,7782,7788,
,7782,7788,
7782,7788,
782,7788,
82,7788,
2,7788,
,7788,
7788,
788,
88,
8,
,
註意,因為整個字符串是由逗號(分界符)括起來的,所以不需要特殊的檢查來確定字符串從哪裏開始及到哪裏結束:
下一步是只保留要用於內部列表中的值。除了最後只有單獨逗號的一行之外,保留以逗號開頭的行。使用SUBSTR或SUBSTRING函數來識別哪些行以逗號開頭,並保留在此行中到下一個逗號之間的所有字符。此操作完成後,將字符串轉換為數值,這樣就可以用來正確地跟數值列EMPNO運算了(行4~14):
EMPNO
------
7654
7698
7782
7788
最後,使用在子查詢中的結果來返回想要得到的行。
MySQL
內聯視圖(行5~9)遍歷字符串。第10行的表達式通過查找逗號(分隔符)的數量來確定在字符串中有多少個值並加1。函數SUBSTRING_INDEX(第6行)返回在字符串中第n個逗號(分隔符)之前(到它左邊)的所有字符:
+---------------------+
| empno |
+---------------------+
| 7654 |
| 7654,7698 |
| 7654,7698,7782 |
| 7654,7698,7782,7788 |
+---------------------+
這些行隨後傳遞給SUBSTRING_INDEX(第5行)函數的另一次調用,此時第n個分隔符參數為-1,這表示在第n個分隔符右邊所有的值需要保留。
+-------+
| empno |
+-------+
| 7654 |
| 7698 |
| 7782 |
| 7788 |
+-------+
最後一步是將結果加入到一個子查詢中。
Oracle
第一步是遍歷字符串:
select emps,pos
from (select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ emps
from t1) csv,
(select rownum pos from emp) iter
where iter.pos <=
((length(csv.emps)-length(replace(csv.emps,‘,‘)))/length(‘,‘))-1
EMPS POS
--------------------- ----------
,7654,7698,7782,7788, 1
,7654,7698,7782,7788, 2
,7654,7698,7782,7788, 3
,7654,7698,7782,7788, 4
有多少行返回,就表示在列表中有多少個值。POS值對於查詢是至關重要的,因為要用它來將字符串分解為單獨的值。使用SUBSTR和INSTR函數來分解字符串,POS用來確定在每個字符串中第n個分隔符的位置。由於字符串是用逗號括起來的,所以不需要特殊的檢測手段來確定字符串的開始位置與結束位置。傳遞給SUBSTR,INSTR(7~9行)確定第n個和第n+1個分隔符的位置。用下一個逗號的位置值(字符串中下一個逗號的位置)減去當前逗號的位置值(字符串中當前逗號的位置),就可以從字符串中提取出每個值。
select substr(emps,
instr(emps,‘,‘,1,iter.pos)+1,
instr(emps,‘,‘,1,iter.pos+1) -
instr(emps,‘,‘,1,iter.pos)) emps
from (select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ emps
from t1) csv,
(select rownum pos from emp) iter
where iter.pos <=
((length(csv.emps)-length(replace(csv.emps,‘,‘)))/length(‘,‘))-1
EMPS
-----------
7654,
7698,
7782,
7788,
最後一步就是刪除每個值後面的逗號,將其轉換為數值並且將其插入到子查詢中。
PostgreSQL
內聯視圖Z(6~9行)遍歷字符串,返回行的數量也就是在字符串中有多少值。要找出在字符串中有多少值,用帶有分隔符的字符串長度減去不帶分隔符的字符串長度即可(第9行),函數SPLIT_PART用來分解字符串,該函數查找第n個分隔符之前的值。
SELECT列表.vals,
split_part(list.vals,‘,‘,iter.pos) as empno,
iter.pos
from (select id as pos from t10) iter,
(select ‘,‘||‘7654,7698,7782,7788‘||‘,‘ as vals
from t1) list
where iter.pos <=
length(list.vals)-length(replace(list.vals,‘,‘,‘‘))
vals | empno | pos
-----------------------+-------+-----
,7654,7698,7782,7788, | | 1
,7654,7698,7782,7788, | 7654 | 2
,7654,7698,7782,7788, | 7698 | 3
,7654,7698,7782,7788, | 7782 | 4
,7654,7698,7782,7788, | 7788 | 5
最後一步就是將返回值(EMPNO)轉換為數值,並且插入到子查詢中。
db2 將逗號分隔數據轉換為多值IN列表