Oracle 多行記錄合并/連接/聚合字符串的幾種方法
更新時(shí)間:2009年11月15日 23:01:47 作者:
怎么合并多行記錄的字符串,一直是oracle新手喜歡問的SQL問題之一,關(guān)于這個(gè)問題的帖子我看過不下30個(gè)了,現(xiàn)在就對(duì)這個(gè)問題,進(jìn)行一個(gè)總結(jié)。
什么是合并多行字符串(連接字符串)呢,例如:
SQL> desc test;
Name Type Nullable Default Comments
------- ------------ -------- ------- --------
COUNTRY VARCHAR2(20) Y
CITY VARCHAR2(20) Y
SQL> select * from test;
COUNTRY CITY
-------------------- --------------------
中國(guó) 臺(tái)北
中國(guó) 香港
中國(guó) 上海
日本 東京
日本 大阪
要求得到如下結(jié)果集:
------- --------------------
中國(guó) 臺(tái)北,香港,上海
日本 東京,大阪
實(shí)際就是對(duì)字符實(shí)現(xiàn)一個(gè)聚合功能,我很奇怪為什么Oracle沒有提供官方的聚合函數(shù)來實(shí)現(xiàn)它呢:)
下面就對(duì)幾種經(jīng)常提及的解決方案進(jìn)行分析(有一個(gè)評(píng)測(cè)標(biāo)準(zhǔn)最高★★★★★):
1.被集合字段范圍小且固定型 靈活性★ 性能★★★★ 難度 ★
這種方法的原理在于你已經(jīng)知道CITY字段的值有幾種,且還不算太多,如果太多這個(gè)SQL就會(huì)相當(dāng)?shù)拈L(zhǎng)。。看例子:
SQL> select t.country,
2 MAX(decode(t.city,'臺(tái)北',t.city||',',NULL)) ||
3 MAX(decode(t.city,'香港',t.city||',',NULL))||
4 MAX(decode(t.city,'上海',t.city||',',NULL))||
5 MAX(decode(t.city,'東京',t.city||',',NULL))||
6 MAX(decode(t.city,'大阪',t.city||',',NULL))
7 from test t GROUP BY t.country
8 /
COUNTRY MAX(DECODE(T.CITY,'臺(tái)北',T.CIT
-------------------- ------------------------------
中國(guó) 臺(tái)北,香港,上海,
日本 東京,大阪,
大家一看,估計(jì)就明白了(如果不明白,好好補(bǔ)習(xí)MAX DECODE和分組)。這種方法無愧為最笨的方法,但是對(duì)某些應(yīng)用來說,最有效的方法也許就是它。
2.固定表固定字段函數(shù)法 靈活性★★ 性能★★★★ 難度 ★★
此法必須預(yù)先知道是哪個(gè)表,也就是說一個(gè)表就得寫一個(gè)函數(shù),不過方法1的一個(gè)取值就要便捷多了。在大多數(shù)應(yīng)用中,也不會(huì)存在大量這種合并字符串的需求。廢話完畢,看下面:
定義一個(gè)函數(shù)
create or replace function str_list( str_in in varchar2 )--分類字段
return varchar2
is
str_list varchar2(4000) default null;--連接后字符串
str varchar2(20) default null;--連接符號(hào)
begin
for x in ( select TEST.CITY from TEST where TEST.COUNTRY = str_in ) loop
str_list := str_list || str || to_char(x.city);
str := ', ';
end loop;
return str_list;
end;
使用:
SQL> select DISTINCT(T.country),list_func1(t.country) from test t;
COUNTRY LIST_FUNC1(T.COUNTRY)
-------------------- ----------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
SQL> select t.country,str_list(t.country) from test t GROUP BY t.country;
COUNTRY STR_LIST(T.COUNTRY)
-------------------- -----------------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
這個(gè)時(shí)候,使用分組和求唯一都可以滿足要求。它的原理就是,根據(jù)唯一的分組字段country,在函數(shù)里面再次查詢?cè)撟侄螌?duì)應(yīng)的所有被合并列,使用PL/SQL將其合并輸出。
3.靈活表函數(shù)法 靈活性★★★ 性能★★★ 難度 ★★★
該方法是在方法2的基礎(chǔ)上,使用動(dòng)態(tài)SQL,將表名和字段名稱傳入,從而達(dá)到靈活的目的。
create or replace function str_list2( key_name in varchar2,
key in varchar2,
coname in varchar2,
tname in varchar2 )
return varchar2
as
type rc is ref cursor;
str varchar2(4000);
sep varchar2(2);
val varchar2(4000);
cur rc;
begin
open cur for 'select '||coname||'
from '|| tname || '
where ' || key_name || ' = :x '
using key;
loop
fetch cur into val;
exit when cur%notfound;
str := str || sep || val;
sep := ', ';
end loop;
close cur;
return str;
end;
SQL> select test.country,
2 str_list2('COUNTRY', test.country, 'CITY', 'TEST') emplist
3 from test
4 group by test.country
5 /
COUNTRY EMPLIST
-------------------- -----------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
4.一條SQL法 靈活性★★★★ 性能★★ 難度 ★★★★
一條SQL的法則是某位大師提出的,大家曾經(jīng)在某個(gè)時(shí)期都樂此不彼的尋求各種的問題一條SQL法,但是大師的意思似乎被曲解,很多性能差,可讀性差,靈活差的SQL都是這個(gè)原則產(chǎn)物,所謂畫虎不成反成犬類。不過,解決問題始終是第一原則,這里還是給出一個(gè)比較有代表性的一條SQL方法。
SELECT country,max(substr(city,2)) city
FROM
(SELECT country,sys_connect_by_path(city,',') city
FROM
(SELECT country,city,country||rn rchild,country||(rn-1) rfather
FROM
(SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn FROM test))
CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0')
GROUP BY country;
下面分步解析,有4個(gè)FROM,就有4次結(jié)果集的操作。
step 1 給記錄加上序號(hào)rn
SQL> SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
2 FROM test
3 /
COUNTRY CITY RN
-------------------- -------------------- ----------
日本 大阪 1
日本 東京 2
中國(guó) 上海 1
中國(guó) 臺(tái)北 2
中國(guó) 香港 3
step 2 創(chuàng)造子節(jié)點(diǎn)父節(jié)點(diǎn)
SQL> SELECT country,city,country||rn rchild,country||(rn-1) rfather
2 FROM
3 (SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
4 FROM test)
5 /
日本 大阪 日本1 日本0
日本 東京 日本2 日本1
中國(guó) 上海 中國(guó)1 中國(guó)0
中國(guó) 臺(tái)北 中國(guó)2 中國(guó)1
中國(guó) 香港 中國(guó)3 中國(guó)2
step 3 利用sys_connect_by_path生成結(jié)果集
SELECT country,sys_connect_by_path(city,',') city
FROM
(SELECT country,city,country||rn rchild,country||(rn-1) rfather
FROM
(SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn FROM test)) CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0'
日本 ,大阪
日本 ,大阪,東京
中國(guó) ,上海
中國(guó) ,上海,臺(tái)北
中國(guó) ,上海,臺(tái)北,香港
step 4 最終步驟,篩選結(jié)果集合
SQL> SELECT country,max(substr(city,2)) city
2 FROM
3 (SELECT country,sys_connect_by_path(city,',') city
4 FROM
5 (SELECT country,city,country||rn rchild,country||(rn-1) rfather
6 FROM
7 (SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
8 FROM test))
9 CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0')
10 GROUP BY country;
COUNTRY CITY
-------------------- -------
中國(guó) 上海,臺(tái)北,香港
日本 大阪,東京
可謂是,7歪8搞,最后還是弄出來了,呵呵。 PS:(邏輯上是對(duì)的..但是寫的比較繁瑣,可以簡(jiǎn)化!)
5.自定義聚合函數(shù) 靈活性★★★★★ 性能★★★★★ 難度 ★★★★★
最后一個(gè)方法是我認(rèn)為“王道”的方法,自定義聚合函數(shù)。
就如何我在本開始說的,為啥oracle沒有這種聚合函數(shù)呢?我也不知道,但oracle提供了聚合函數(shù)的API可以讓我方便的自己定義聚合函數(shù)。
詳細(xì)可以看Oracle Data Catridge guide這個(gè)文檔。連接如下:
http://www.oracle.com.cn/other/9ionlinedoc/appdev.920/a96595/toc.htm
下面給出一個(gè)簡(jiǎn)單的例子:
SQL> SELECT t.country,strcat(t.city) FROM test t GROUP BY t.country;
COUNTRY STRCAT(T.CITY)
-------------------- ------------------
日本 東京,大阪
中國(guó) 臺(tái)北,香港,上海
簡(jiǎn)單吧,和官方的函數(shù)一樣的便捷高效。
函數(shù):
CREATE OR REPLACE FUNCTION strcat(input varchar2 )
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING strcat_type;
TYPE:
create or replace type strcat_type as object (
cat_string varchar2(4000),
static function ODCIAggregateInitialize(cs_ctx In Out strcat_type) return number,
member function ODCIAggregateIterate(self In Out strcat_type,value in varchar2) return number,
member function ODCIAggregateMerge(self In Out strcat_type,ctx2 In Out strcat_type) return number,member function ODCIAggregateTerminate(self In Out strcat_type,returnValue Out
varchar2,flags in number) return number)
6.待發(fā)掘...
PS: 在 oracle 10g下,可以使用以下系統(tǒng)函數(shù):
select id,WMSYS.WM_CONCAT(oid) oid
from table1
group by id
總結(jié),合并字符串還有更多的方法希望大家能發(fā)掘,本文的目的主要是拋磚引玉,如果有新的發(fā)現(xiàn)我會(huì)繼續(xù)更新方法。需要注意的問題是,本文采用varchar2為例子,所以長(zhǎng)度有限制,oracle的版本對(duì)方法的實(shí)現(xiàn)也影響。
SQL> desc test;
Name Type Nullable Default Comments
------- ------------ -------- ------- --------
COUNTRY VARCHAR2(20) Y
CITY VARCHAR2(20) Y
SQL> select * from test;
COUNTRY CITY
-------------------- --------------------
中國(guó) 臺(tái)北
中國(guó) 香港
中國(guó) 上海
日本 東京
日本 大阪
要求得到如下結(jié)果集:
------- --------------------
中國(guó) 臺(tái)北,香港,上海
日本 東京,大阪
實(shí)際就是對(duì)字符實(shí)現(xiàn)一個(gè)聚合功能,我很奇怪為什么Oracle沒有提供官方的聚合函數(shù)來實(shí)現(xiàn)它呢:)
下面就對(duì)幾種經(jīng)常提及的解決方案進(jìn)行分析(有一個(gè)評(píng)測(cè)標(biāo)準(zhǔn)最高★★★★★):
1.被集合字段范圍小且固定型 靈活性★ 性能★★★★ 難度 ★
這種方法的原理在于你已經(jīng)知道CITY字段的值有幾種,且還不算太多,如果太多這個(gè)SQL就會(huì)相當(dāng)?shù)拈L(zhǎng)。。看例子:
SQL> select t.country,
2 MAX(decode(t.city,'臺(tái)北',t.city||',',NULL)) ||
3 MAX(decode(t.city,'香港',t.city||',',NULL))||
4 MAX(decode(t.city,'上海',t.city||',',NULL))||
5 MAX(decode(t.city,'東京',t.city||',',NULL))||
6 MAX(decode(t.city,'大阪',t.city||',',NULL))
7 from test t GROUP BY t.country
8 /
COUNTRY MAX(DECODE(T.CITY,'臺(tái)北',T.CIT
-------------------- ------------------------------
中國(guó) 臺(tái)北,香港,上海,
日本 東京,大阪,
大家一看,估計(jì)就明白了(如果不明白,好好補(bǔ)習(xí)MAX DECODE和分組)。這種方法無愧為最笨的方法,但是對(duì)某些應(yīng)用來說,最有效的方法也許就是它。
2.固定表固定字段函數(shù)法 靈活性★★ 性能★★★★ 難度 ★★
此法必須預(yù)先知道是哪個(gè)表,也就是說一個(gè)表就得寫一個(gè)函數(shù),不過方法1的一個(gè)取值就要便捷多了。在大多數(shù)應(yīng)用中,也不會(huì)存在大量這種合并字符串的需求。廢話完畢,看下面:
定義一個(gè)函數(shù)
create or replace function str_list( str_in in varchar2 )--分類字段
return varchar2
is
str_list varchar2(4000) default null;--連接后字符串
str varchar2(20) default null;--連接符號(hào)
begin
for x in ( select TEST.CITY from TEST where TEST.COUNTRY = str_in ) loop
str_list := str_list || str || to_char(x.city);
str := ', ';
end loop;
return str_list;
end;
使用:
SQL> select DISTINCT(T.country),list_func1(t.country) from test t;
COUNTRY LIST_FUNC1(T.COUNTRY)
-------------------- ----------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
SQL> select t.country,str_list(t.country) from test t GROUP BY t.country;
COUNTRY STR_LIST(T.COUNTRY)
-------------------- -----------------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
這個(gè)時(shí)候,使用分組和求唯一都可以滿足要求。它的原理就是,根據(jù)唯一的分組字段country,在函數(shù)里面再次查詢?cè)撟侄螌?duì)應(yīng)的所有被合并列,使用PL/SQL將其合并輸出。
3.靈活表函數(shù)法 靈活性★★★ 性能★★★ 難度 ★★★
該方法是在方法2的基礎(chǔ)上,使用動(dòng)態(tài)SQL,將表名和字段名稱傳入,從而達(dá)到靈活的目的。
create or replace function str_list2( key_name in varchar2,
key in varchar2,
coname in varchar2,
tname in varchar2 )
return varchar2
as
type rc is ref cursor;
str varchar2(4000);
sep varchar2(2);
val varchar2(4000);
cur rc;
begin
open cur for 'select '||coname||'
from '|| tname || '
where ' || key_name || ' = :x '
using key;
loop
fetch cur into val;
exit when cur%notfound;
str := str || sep || val;
sep := ', ';
end loop;
close cur;
return str;
end;
SQL> select test.country,
2 str_list2('COUNTRY', test.country, 'CITY', 'TEST') emplist
3 from test
4 group by test.country
5 /
COUNTRY EMPLIST
-------------------- -----------------
中國(guó) 臺(tái)北, 香港, 上海
日本 東京, 大阪
4.一條SQL法 靈活性★★★★ 性能★★ 難度 ★★★★
一條SQL的法則是某位大師提出的,大家曾經(jīng)在某個(gè)時(shí)期都樂此不彼的尋求各種的問題一條SQL法,但是大師的意思似乎被曲解,很多性能差,可讀性差,靈活差的SQL都是這個(gè)原則產(chǎn)物,所謂畫虎不成反成犬類。不過,解決問題始終是第一原則,這里還是給出一個(gè)比較有代表性的一條SQL方法。
SELECT country,max(substr(city,2)) city
FROM
(SELECT country,sys_connect_by_path(city,',') city
FROM
(SELECT country,city,country||rn rchild,country||(rn-1) rfather
FROM
(SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn FROM test))
CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0')
GROUP BY country;
下面分步解析,有4個(gè)FROM,就有4次結(jié)果集的操作。
step 1 給記錄加上序號(hào)rn
SQL> SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
2 FROM test
3 /
COUNTRY CITY RN
-------------------- -------------------- ----------
日本 大阪 1
日本 東京 2
中國(guó) 上海 1
中國(guó) 臺(tái)北 2
中國(guó) 香港 3
step 2 創(chuàng)造子節(jié)點(diǎn)父節(jié)點(diǎn)
SQL> SELECT country,city,country||rn rchild,country||(rn-1) rfather
2 FROM
3 (SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
4 FROM test)
5 /
日本 大阪 日本1 日本0
日本 東京 日本2 日本1
中國(guó) 上海 中國(guó)1 中國(guó)0
中國(guó) 臺(tái)北 中國(guó)2 中國(guó)1
中國(guó) 香港 中國(guó)3 中國(guó)2
step 3 利用sys_connect_by_path生成結(jié)果集
SELECT country,sys_connect_by_path(city,',') city
FROM
(SELECT country,city,country||rn rchild,country||(rn-1) rfather
FROM
(SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn FROM test)) CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0'
日本 ,大阪
日本 ,大阪,東京
中國(guó) ,上海
中國(guó) ,上海,臺(tái)北
中國(guó) ,上海,臺(tái)北,香港
step 4 最終步驟,篩選結(jié)果集合
SQL> SELECT country,max(substr(city,2)) city
2 FROM
3 (SELECT country,sys_connect_by_path(city,',') city
4 FROM
5 (SELECT country,city,country||rn rchild,country||(rn-1) rfather
6 FROM
7 (SELECT test.country ,test.city,row_number() over (PARTITION BY test.country ORDER BY test.city) rn
8 FROM test))
9 CONNECT BY PRIOR rchild=rfather START WITH rfather LIKE '%0')
10 GROUP BY country;
COUNTRY CITY
-------------------- -------
中國(guó) 上海,臺(tái)北,香港
日本 大阪,東京
可謂是,7歪8搞,最后還是弄出來了,呵呵。 PS:(邏輯上是對(duì)的..但是寫的比較繁瑣,可以簡(jiǎn)化!)
5.自定義聚合函數(shù) 靈活性★★★★★ 性能★★★★★ 難度 ★★★★★
最后一個(gè)方法是我認(rèn)為“王道”的方法,自定義聚合函數(shù)。
就如何我在本開始說的,為啥oracle沒有這種聚合函數(shù)呢?我也不知道,但oracle提供了聚合函數(shù)的API可以讓我方便的自己定義聚合函數(shù)。
詳細(xì)可以看Oracle Data Catridge guide這個(gè)文檔。連接如下:
http://www.oracle.com.cn/other/9ionlinedoc/appdev.920/a96595/toc.htm
下面給出一個(gè)簡(jiǎn)單的例子:
SQL> SELECT t.country,strcat(t.city) FROM test t GROUP BY t.country;
COUNTRY STRCAT(T.CITY)
-------------------- ------------------
日本 東京,大阪
中國(guó) 臺(tái)北,香港,上海
簡(jiǎn)單吧,和官方的函數(shù)一樣的便捷高效。
函數(shù):
CREATE OR REPLACE FUNCTION strcat(input varchar2 )
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING strcat_type;
TYPE:
create or replace type strcat_type as object (
cat_string varchar2(4000),
static function ODCIAggregateInitialize(cs_ctx In Out strcat_type) return number,
member function ODCIAggregateIterate(self In Out strcat_type,value in varchar2) return number,
member function ODCIAggregateMerge(self In Out strcat_type,ctx2 In Out strcat_type) return number,member function ODCIAggregateTerminate(self In Out strcat_type,returnValue Out
varchar2,flags in number) return number)
6.待發(fā)掘...
PS: 在 oracle 10g下,可以使用以下系統(tǒng)函數(shù):
select id,WMSYS.WM_CONCAT(oid) oid
from table1
group by id
總結(jié),合并字符串還有更多的方法希望大家能發(fā)掘,本文的目的主要是拋磚引玉,如果有新的發(fā)現(xiàn)我會(huì)繼續(xù)更新方法。需要注意的問題是,本文采用varchar2為例子,所以長(zhǎng)度有限制,oracle的版本對(duì)方法的實(shí)現(xiàn)也影響。
相關(guān)文章
Oracle鎖表的解決方法及避免鎖表問題的最佳實(shí)踐
在 Oracle 數(shù)據(jù)庫(kù)中,鎖表或鎖超時(shí)相信大家都不陌生,是一個(gè)常見的問題,尤其是在執(zhí)行 DML(數(shù)據(jù)操作語言)語句時(shí),本文將詳細(xì)介紹如何解決鎖表問題以及如何查找引起鎖表的 SQL 語句,并提供避免鎖表問題的最佳實(shí)踐,需要的朋友可以參考下2024-11-11
Informatica bulk與normal模式的深入詳解
本篇文章是對(duì)Informatica bulk與normal模式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
ORA-02298: 無法驗(yàn)證 (約束)提示未找到父項(xiàng)關(guān)鍵字的解決辦法
這篇文章主要介紹了ORA-02298: 無法驗(yàn)證 (約束)提示未找到父項(xiàng)關(guān)鍵字的解決辦法,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Oracle中ROW_NUMBER()OVER()函數(shù)用法實(shí)例講解
Oracle中的row_number()函數(shù)用于在查詢結(jié)果中為每一行生成一個(gè)唯一的行號(hào),下面這篇文章主要給大家介紹了關(guān)于Oracle中ROW_NUMBER()OVER()函數(shù)用法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
oracle12c安裝報(bào)錯(cuò):PRVF-0002的解決方法
本文為大家介紹下如何解決oracle12c安裝報(bào)錯(cuò):PRVF-0002,具體的排查思路如下,有類似情況的朋友可以參考下2013-09-09

