本文連接:http://www.javashuo.com/article/p-yssjykdt-no.htmlsql
10月的次日,前天寫了個Oracle中行轉列的pivot的基本使用方法,而後,由於pivot的用法中,正常狀況下,咱們須要轉出多少個列,都得在咱們的sql中完完整整地寫出,而不能直接在裏面寫個查詢來動態轉換。而後,趁着祖國母親的生日,這幾天放假,整理一下處理方法。dom
1、運行環境
Win10,Oracle Database 11g r2,plsql 12。函數
2、效果預覽
一、固定轉換列的方法oop
二、存儲過程處理
1)調用存儲過程測試
2)查指定的視圖便可ui
三、兩種方法的關係
其實原理很簡單,就是經過動態sql,去把你不肯意寫,或者說是不肯定的轉換列數,經過查詢查出來,拼接進去,而後執行拼接後的sql,建立視圖。.net
3、存儲過程
create or replace procedure p_RowsToCols(as_sql in varchar2 --源數據的查詢sql
,as_sql_cols in varchar2 --動態轉換列的查詢sql,要求轉爲列的那列,字段名必須爲cols,支持排序
,as_aggCol in varchar2 --對應pivot函數的 聚合函數
,as_changeCol in varchar2 --源數據中,要轉爲列的字段名
,as_viewName in varchar2 --結果輸出的視圖名,執行完後查此視圖便可
) is
ls_sql varchar2(4000);
ls_in varchar2(4000);
begin
--拼接in的內容
ls_sql := 'select listagg(''''''''||cols||'''''' "''||cols||''"'', '','')within group(order by rn) ' ||
'from (select rownum rn, cols from (' || as_sql_cols || '))';
execute immediate ls_sql
into ls_in;
--建立視圖
ls_sql := 'create or replace view ' || as_viewName ||' as ' ||
'select * from (' || as_sql || ') ' ||
'pivot (' || as_aggCol || ' for ' || as_changeCol || ' in (' || ls_in || '))';
execute immediate ls_sql;
end p_RowsToCols;
4、測試數據及SQL
貼一下我測試的數據和代碼。regexp
--建表
--drop table SalesList;
create table SalesList(
keHu varchar2(20), --客戶
shangPinId number(8), --商品Id
shangPin varchar2(20), --商品名稱
salesNum number(8) --銷售數量
);
--插入數據
declare
--談幾個客戶
cursor lr_kh is
select regexp_substr('張3、李4、王5、趙六','[^、]+',1, level) keHu from dual
connect by level <= 4;
--進點貨
cursor lr_sp is
select level shangPinId, regexp_substr('上衣、褲子、襪子、帽子','[^、]+',1, level) shangPin from dual
connect by level <= 4;
begin
--循環插入
for v_kh in lr_kh loop
for v_sp in lr_sp loop
insert into SalesList
select v_kh.keHu, v_sp.shangPinId, v_sp.shangPin, floor(dbms_random.value(10,50)) from dual;
end loop;
end loop;
commit;
end;
/
--看下源數據
select * from salesList a;
--固定行轉列
select *
from (select kehu, shangPin, salesNum from salesList) pivot(
max(salesNum) for shangPin in (
'上衣' as 上衣,
'褲子' as 褲子,
'襪子' as 襪子,
'帽子' as 帽子
)
);
--動態行轉列
call p_RowsToCols('select keHu, shangPin, salesNum from salesList',
'select distinct shangPinId, shangPin cols from salesList order by shangPinId',
'max(salesNum)',
'shangPin',
'sales_RowsToCols');
select * from sales_RowsToCols;
完結!!!!!!!!!!!!!!!!!!!!!!
結尾來個悲傷的彩蛋,聞者傷心,聽者落淚!blog
上面介紹的方法是一個很簡單的思路,然鵝!!!在找到這個思路以前,我還傻傻的作了另一個版本,一個比較複雜的版本。。。花了有差很少半天時間。我按照這個思路,剛作完的時候有多開心,如今的我,就有多傷心!!不過不忍心直接del掉,仍是在這邊記錄一下吧,思路大概是這個樣幾:排序
一樣是存儲過程,傳入數據源的查詢sql,經過存儲過程處理,拼接成完整的pivot函數須要的sql,而後直接執行,查出結果,insert到一張通用的記錄表中,而後建一個視圖,指向這部分數據。
完結篇.1 效果預覽
測試數據參考上面的插入sql,一樣的數據。
一、第一個參數:必填,數據源的查詢,列數不限,但查詢的倒數第二列爲轉換列,最後一列數據列;
二、第二個參數:
1)可爲空,內容爲要轉換出的列的查詢sql,可指定表頭順序及顯示的表頭名稱。爲空時即從數據源查詢的倒數第二列中取distinct值,並不保證排序。
2)查詢結果必須爲三列,第一列爲數字排序列(不須要的話這列就隨便指定一個數字就行),第二列對應數據源中轉換列的值,第三列即爲對應的表頭轉換後的名稱(若是名稱不須要改變,這列跟第二列保持同樣就行)。
三、第三個參數:可爲空,內容就是處理完後要查的視圖名。爲空的話,即默認爲tmp_rowToCol。
完結篇.2 跟pivot的對應關係
完結篇.3 其餘好玩的東西
一、由於咱們這個方法的話是吧查出的內容存到表裏,而後經過視圖直接指向數據的,剛纔指定的視圖Sale_RowToCol,具體建立語法,就是下面這個,tmp_RowToCol_XiaoXianNv爲數據存儲的表,fbs = '1' 表示是轉換的實際數據,fguid是本次轉換的一個key。
咱們查下這個表中實際的數據,這是一個在首次調用存儲過程的時候會建立的一個表,203個字段,不超過這麼多列的均可以經過咱們的存儲過程去轉並存儲數據。
而後,這個表的數據是會一直保存着的,emm...好像沒啥卵用,不過說不定哪天腦子抽了嚮往前查查這個仍是挺有意思的。。。(說得我本身都不信)
完結篇.4 貼代碼
csdn的高亮。。好像不太友好
/**
* 動態進行行列轉換,結果集可在一個可指定的視圖中查詢(默認爲tmp_rowToCol)
* 適用於把一列的值轉成多列,轉換效果與Oracle的pivot相同,但不須要寫死轉換出來的每個列
* 轉換後的數據所存的實體表爲tmp_RowToCol_XiaoXianNv,經過一個guid關聯到指定的視圖
* tmp_RowToCol_XiaoXianNv表在此過程當中不作刪除操做。因此若是永久了怕是數據也會挺多。
* 若是不須要保留數據的話,能夠考慮把這個表建爲一個會話級臨時表,而後轉換結果插入後不提交。
* 這樣在同一會話下可查詢,提交或者回滾後數據就不復存在。
*
* 轉換思路:
* 一、經過動態sql,拼接出 for XXX in () 裏面那部份內容,而後經過動態sql執行並把結果插入一個表中
* 二、獲取固定列、轉出列的列名,進行拼接,而後建立視圖指向上一步插入的數據
*
* author: lhy
* date: 2018-10-01 祖國萬歲
*
* as_sql 要轉換的數據源查詢
* 對查詢結果集的要求:至少3列,
* 最後一列爲數據值
* 倒數第二列爲要轉成列的內容
* 前面的就是不需轉換的列
* as_sql_col 查詢要轉的列名,若是不指定,即從as_sql的查詢的倒數第二列中獲取distinct值
* 對查詢的結果集要求:必須爲三列
* 對應pivot函數中的:for xxx in('值1' as colNm1,'值2' as colNm2 ...)
* 第一列:排序列,要求爲數字
* 第二列:值(值1..值2)
* 第三列:字段名(colNm1..colNm2)
* 固然,你不care最後結果的字段的排序和字段名的話,第一列您直接指定一個固定值就行,第三列跟第二列同樣也行
* as_tableName 指定一個視圖名來存放轉換後的數據,調用存儲過程後,經過此視圖查詢結果集
*/
create or replace procedure p_rowToCol(as_sql in varchar2, as_sql_col in varchar2, as_viewName in varchar2) is
lr_curid integer; --遊標id
ls_cnt number(8); --計數用
ls_sql varchar2(4000); --sql語句
ls_sql_col varchar2(4000); --同 as_sql_col
ls_rsltTab dbms_sql.desc_tab; --存放返回的結果集
ls_viewName varchar2(200); --轉換結果存放的表名
ls_guid varchar2(50); --當次轉換的guid
ls_aggColNm varchar2(50); --對應pivot的聚合列的列名
ls_changeColNm varchar2(50); --轉換列的列名
ls_cnt_col number(8); --要轉換出來的列數
ls_in_text varchar2(4000); --對應for()的內容
ls_cnt_end number(8); --最終查詢結果的列數
ls_sql_end varchar2(4000); --最終的插入語句
ls_col_add varchar2(4000); --存放轉出的列名
ls_col_fixed varchar2(4000); --存放不須要轉換的列名
ls_col_insert varchar2(4000); --存放插入的字段
ls_col_view varchar2(4000); --視圖的字段
ls_sql_view varchar2(4000); --存放最後的視圖的sql
ls_thead varchar2(4000); --拼接一個表頭出來,說不定能夠回查
begin
--兩步準備工做,其實若是作過一次,後面的代碼中其實都不須要執行這兩步了
--準備工做一、看下是否存在tmp_RowToCol_XiaoXianNv這個表,首次使用不存在的話建一個(用於存放轉換後的數據)
select count(*) into ls_cnt from all_tables where table_name = upper('tmp_RowToCol_XiaoXianNv');
if ls_cnt = 0 then
ls_sql := 'create table tmp_RowToCol_XiaoXianNv(fguid varchar2(50),fopdt date default sysdate,fbs varchar2(8),';
for i in 1..200 loop
if i = 200 then
ls_sql := ls_sql || 'C' || i || ' varchar2(4000))';
else
ls_sql := ls_sql || 'C' || i || ' varchar2(4000),';
end if;
end loop;
execute immediate ls_sql;
--怕之後數據多查詢慢的話還能夠建個索引給fguid字段
execute immediate 'create index IDX_ROWSTOCOLS_FGUID on tmp_RowToCol_XiaoXianNv (fguid)';
end if;
--準備工做二、看下是否存在一個tmp_XiaoXianNv_t1這個臨時表,首次使用不存在的話建一個(用於處理轉換列排序)
select count(*) into ls_cnt from all_tables where table_name = upper('tmp_XiaoXianNv_t1');
if ls_cnt = 0 then
ls_sql := 'create global temporary table tmp_XiaoXianNv_t1(fseq NUMBER(20),c1 VARCHAR2(4000),c2 VARCHAR2(4000)) on commit delete rows';
execute immediate ls_sql;
end if;
--取個guid,準備開幹
--這個就是這一次轉換的的key,之後要找此次轉換的數據均可以拿着這個key到tmp_RowToCol_XiaoXianNv找
--因此其實也能夠經過傳參來手動指定這個key,而後之後想查回來此次的數據都會比較方便
ls_guid := sys_guid();
--獲取轉換列和聚合列的列名,即as_sql查詢結果的倒數兩列
ls_sql := as_sql;
lr_curid := dbms_sql.open_cursor;
dbms_sql.parse(lr_curid, ls_sql, dbms_sql.native);
dbms_sql.describe_columns(lr_curid, ls_cnt, ls_rsltTab);
ls_changeColNm := ls_rsltTab(ls_cnt - 1).col_name; --倒數第2列,獲取轉換列列名
ls_aggColNm := ls_rsltTab(ls_cnt).col_name; --倒數第1列,獲取聚合列列名
ls_cnt := ls_cnt - 2; --不須要轉換的列數
--拼接不須要轉換的列名,用於後面建視圖(part 1)
for i in 1..ls_cnt loop
ls_col_fixed := ls_col_fixed || ls_rsltTab(i).col_name || ', ';
end loop;
dbms_sql.close_cursor(lr_curid);
--拼接 for xxx in ('值1' as colNm1,'值2' as colNm2 ...) 部分
--獲取全部列名並拼接
--一、先把全部列名的查詢sql搞定
if as_sql_col is null then
ls_sql_col := 'select rownum rn, c1, c1 c2 from (select distinct '|| ls_changeColNm || ' c1 from (' || as_sql || ') order by ' || ls_changeColNm || ')';
else
ls_sql_col := as_sql_col;
end if;
--二、把轉換列的數據插入到臨時表
execute immediate 'delete from tmp_XiaoXianNv_t1';
ls_sql := 'insert into tmp_XiaoXianNv_t1 (fseq, c1, c2) '|| ls_sql_col;
execute immediate ls_sql;
--三、ls_cnt_col count出要轉換出的列數
execute immediate 'select count(*) from tmp_XiaoXianNv_t1' into ls_cnt_col;
--順便算一下最終查詢結果的列數
ls_cnt_end := ls_cnt + ls_cnt_col;
--四、拼接for xx in () 裏面的內容
ls_sql := 'select listagg(''''''''||c1||'''''' ''||c2 , '', '') within group(order by fseq ) from tmp_XiaoXianNv_t1 a';
execute immediate ls_sql into ls_in_text;
--五、順便拼接出行轉列轉換出來的字段名,用於後面建視圖(part 2)
ls_sql := 'select listagg(c2,'','')within group(order by fseq) from tmp_XiaoXianNv_t1';
execute immediate ls_sql into ls_col_add;
--拼接插入的表的字段 tmp_RowToCol_XiaoXianNv(c1,c2,c3...)
select listagg(col, ', ') within group(order by rn)
into ls_col_insert
from (select rownum rn, 'c' || rownum col
from dual
connect by rownum <= ls_cnt_end);
--拼接pivot的insert sql,插入內容,fbs爲標識字段,標記爲1,即爲正式數據
ls_sql_end := 'insert into tmp_RowToCol_XiaoXianNv (fguid,fbs,' || ls_col_insert || ') '
||'select '''|| ls_guid ||''' fguid,''1'',t.* from ('
|| as_sql || ') PIVOT(max(' || ls_aggColNm || ') for ' || ls_changeColNm || ' in ('
|| ls_in_text || ')) t' ;
execute immediate ls_sql_end;
commit;
--拼接表頭的字段
ls_thead := ls_col_fixed || ls_col_add;
ls_thead := replace(ls_thead,' ');
ls_col_view := ls_thead; --轉存一下給下面拼接視圖的使用
select listagg(''''||col||'''',',')within group(order by rn)
into ls_thead from (
select level rn, regexp_substr(ls_thead,'[^,]+',1,level) col
from dual connect by level <= ls_cnt_end
);
--拼接pivot的insert sql,插入內容,fbs爲標識字段,標記爲轉換後的字段數,即ls_cnt_end變量,即爲正式數據
ls_sql := 'insert into tmp_RowToCol_XiaoXianNv (fguid,fbs,' || ls_col_insert || ') values('''|| ls_guid ||''','''||ls_cnt_end||''','||ls_thead||')';
execute immediate ls_sql;
commit;
--拿到結果視圖名,默認爲tmp_rowToCol
if as_viewName is null then
ls_viewName := 'tmp_rowToCol';
else
ls_viewName := as_viewName;
end if;
/***************************************這部分的代碼能夠直接刪掉************************************
--上面是根據前面的數據拼接出來的視圖的字段ls_col_view,若是咱們是隻知道一個guid的時候,咱們其實也能夠去從數據表中查出表頭,而後拼接
ls_sql := 'select max(fbs) from tmp_RowToCol_XiaoXianNv where fguid = '''||ls_guid||''' and fbs <> ''1''';
execute immediate ls_sql into ls_cnt_end; --獲取列數
select listagg(col,'||'',''||')within group(order by rn)
into ls_sql from (
select level rn, 'C'||level col from dual connect by level <= ls_cnt_end
);
ls_sql := 'select '|| ls_sql || ' from tmp_RowToCol_XiaoXianNv where fguid = '''||ls_guid||''' and fbs <> ''1''';
execute immediate ls_sql into ls_col_view; --獲取視圖列名ls_col_view,這裏獲得的跟上面獲取到的是同樣的
**********************************************************************************************/
--拼接視圖的字段別名轉換關係 c1 字段1, c2 字段2 ...
select listagg(c1||' '||col,',')within group(order by rn)
into ls_col_view from (
select 'C'||level c1, level rn, regexp_substr(ls_col_view,'[^,]+',1,level) col
from dual connect by level <= ls_cnt_end
);
--視圖呈現
ls_sql_view := 'create or replace view '|| ls_viewName ||' as select '|| ls_col_view || ' from tmp_RowToCol_XiaoXianNv where fbs = ''1'' and fguid = '''|| ls_guid || '''';
execute immediate ls_sql_view;
end p_rowToCol;
完結篇.5 測試SQL
--建表
--drop table SalesList;
create table SalesList(
keHu varchar2(20), --客戶
shangPinId number(8), --商品Id
shangPin varchar2(20), --商品名稱
salesNum number(8) --銷售數量
);
--插入數據
declare
--談幾個客戶
cursor lr_kh is
select regexp_substr('張3、李4、王5、趙六','[^、]+',1, level) keHu from dual
connect by level <= 4;
--進點貨
cursor lr_sp is
select level shangPinId, regexp_substr('上衣、褲子、襪子、帽子','[^、]+',1, level) shangPin from dual
connect by level <= 4;
begin
--循環插入
for v_kh in lr_kh loop
for v_sp in lr_sp loop
insert into SalesList
select v_kh.keHu, v_sp.shangPinId, v_sp.shangPin, floor(dbms_random.value(10,50)) from dual;
end loop;
end loop;
commit;
end;
/
--查看下數據
select * from salesList a;
--固定行轉列
select *
from (select kehu, shangPin, salesNum from salesList) pivot(
max(salesNum) for shangPin in (
'上衣' as 上衣數量,
'褲子' as 褲子數量,
'襪子' as 襪子數量,
'帽子' as 帽子數量
)
);
--動態行轉列
call p_rowtocol('select keHu, shangPin, salesNum from SalesList',
'',
'Sale_RowToCol');
select * from Sale_RowToCol;
--完整版
call p_rowtocol('select keHu 客戶, shangPin, salesNum from SalesList',
'select distinct shangPinId, shangPin, shangPin||''數量'' from salesList order by shangPinId',
'Sale_RowToCol');
select * from Sale_RowToCol;
--數據存儲的表
select * from tmp_RowToCol_XiaoXianNv
結束語?沒有
啊對,這個存儲過程當中有建表和建視圖的語法,若是你的用戶沒有權限的話須要用dba用戶給一下權限:
grant create table to user;
grant create view to user;
================2019年4月25日 更新================
評論區一個小夥伴報的bug。
拼接出來的sql語句,pivot(xxx for xxx in ('0' 0, '1' 1, '2' 2)) 的這部分,當列爲純數字的時候,別名要加個雙引號。
就是說,拼接出來的應該是 pivot(xxx for xxx in ('0' "0", '1' "1", '2' "2")) 纔對。
修改內容:代碼的11行,拼接列別名的時候,添加個雙引號把列名包住
更新後代碼以下:
嗯,再次感謝提bug的小夥伴。 ———————————————— 版權聲明:本文爲CSDN博主「huay_li」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/huay_li/article/details/82924443