关于ORACLE列转行的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于ORACLE列转行的问题相关的知识,希望对你有一定的参考价值。

我有如图1所示的一张表,

图1
想得到如图2所示的结果,

图2
也就是说我想把图1中同一个编号的数据进行合并,并且把合并后的结果存入一个BLOB类型的字段中,想过用to_blob(rawtohex(listagg(……)))来实现,但这样的话listagg()最大只能4K,而我要存入的数据中一个编号下的值就远远大于4K,所以这种方法基本没戏了,有没有哪位大神可以帮帮忙呢?跪谢!
备注:“合并”字段是BLOB类型,为方面说明问题,图中直接列出了详细信息而不是BLOB,但在数据库中要以BLBO存储。

先将varchar2转换为clob再转换为blob。

http://blog.csdn.net/wbo112/article/details/9041575。

希望能解决你的问题。

--准备数据
create table a(bh number, sj date, sl number);
create table b(bh number, zj blob);

insert into a values(1001, sysdate, 30);
insert into a values(1002, sysdate, 31);
insert into a values(1001, sysdate, 32);
insert into a values(1003, sysdate, 34);
insert into a values(1003, sysdate, 35);
insert into a values(1004, sysdate, 36);
insert into a values(1004, sysdate, 38);
insert into a values(1005, sysdate, 23);
insert into a values(1005, sysdate, 30);
insert into a values(1006, sysdate, 35);
insert into a values(1006, sysdate, 30);
insert into a values(1001, sysdate, 30);

--这个方法不知道能否解决你这个4k限制的问题
create or replace procedure insert_b is
  cursor a_cursor is select * from a order by bh;
  a_record a%rowtype;
  temp_bh a.bh%type := 0;
  temp_zj varchar2(32767);
begin
  open a_cursor;
  loop
    fetch a_cursor into a_record;
    --插入最后的一条记录
    if a_cursor%notfound then
      insert into b values(temp_bh, c2b(to_clob(temp_zj)));
      exit;
    end if;
    if temp_bh != a_record.bh then
      --插入上一条的记录值
      if temp_bh != 0 then
        insert into b values(temp_bh, c2b(to_clob(temp_zj)));
      end if;
      temp_bh := a_record.bh;
      --temp_zj := concat(concat(concat(concat(concat(concat('编号:', a_record.bh), ',时间:'), to_char(a_record.sj, 'yyyy-mm-dd')), ',数量:'), a_record.sl), ';');
      temp_zj := '编号:' || a_record.bh || ',时间:' || to_char(a_record.sj, 'yyyy-mm-dd') || ',数量:' || a_record.sl || ';';
    elsif temp_bh = a_record.bh then
      --temp_zj := concat(concat(concat(concat(concat(temp_zj, '时间:'), to_char(a_record.sj, 'yyyy-mm-dd')), ',数量:'), a_record.sl), ';');
      temp_zj := temp_zj || '时间:' || to_char(a_record.sj, 'yyyy-mm-dd') || ',数量:' || a_record.sl || ';';
    end if;
  end loop;
  close a_cursor;
end;
/

--了解了下listagg函数,这个比较简洁,不知道会不会出现你所说的4k限制问题。由你这个问题也让我学到了11g的新函数
create or replace procedure insert_b is
  cursor a_cursor is
    select bh,
           '编号:' || bh || ',' ||
           listagg('时间:' || to_char(sj, 'yyyy-mm-dd') || ',数量:' || sl,
                   ';') within group(order by bh) || '。' as res
      from a
     group by bh;
  v_bh  number;
  v_res varchar2(32767);
begin
  open a_cursor;
  loop
    fetch a_cursor
      into v_bh, v_res;
    exit when a_cursor%notfound;
    --insert into b values (v_bh, c2b(to_clob(v_res)));
    insert into b values(v_bh, to_blob(rawtohex(v_res)));
  end loop;
  close a_cursor;
end;
/

--更新数据
begin
  insert_b;
end;
/

--查询结果
select bh, to_char(b2c(zj)) from b;

--将clob类型转换为blob类型(二进制转换)
create or replace function c2b(src clob default empty_clob()) return blob is
  dest          blob;
  src_len       number := dbms_lob.getlength(src);
  dest_offset   number := 1;
  src_offset    number := 1;
  amount_c      integer := dbms_lob.lobmaxsize;
  blob_csid     number := dbms_lob.default_csid;
  lang_ctx      integer := dbms_lob.default_lang_ctx;
  warning       integer;
begin
  if src_len > 0 then
    --将dest建立在用户的临时表空间中,true表示将dest读到缓冲区。此处相当于初始化dest
    dbms_lob.createtemporary(dest, true);
    --以readwrite模式打开dest
    dbms_lob.open(dest, dbms_lob.lob_readwrite);
    --读取src,转换字符数据为特定字符集格式,并将转换后的数据写入dest中
    dbms_lob.converttoblob(dest,         --目标blob
                           src,          --源clob
                           amount_c,     --指定要转换的字节数
                           dest_offset,  --指定目标lob的偏移位置(字节或字符)
                           src_offset,   --指定源lob的偏移位置(字节或字符)
                           blob_csid,    --指定字符集标识号
                           lang_ctx,     --指定语言上下文
                           warning);     --存放警告信息
  else
    select empty_blob() into dest from dual;
  end if;
  return dest;
end c2b;
/
                           
--将blob类型转换为clob类型
--varchar2类型可直接转换为clob类型
create or replace function b2c(src blob) return clob is
  dest varchar2(32767);
  temp varchar2(32767);
  v_start pls_integer := 1;
  v_buffer pls_integer := 4000;
begin
  if dbms_lob.getlength(src) is null then
    return '';
  end if;
  dest := '';
  for i in 1..ceil(dbms_lob.getlength(src) / v_buffer) loop
    --当转换出来的字符串乱码时,可尝试使用注释掉的函数
    --temp := utl_raw.cast_to_varchar2(utl_raw.convert(dbms_lob.substr(src, v_buffer, v_start), 'SIMPLIFIED CHINESE_CHINA.ZHS16GBK', 'AMERICAN_THE NETHERLANDS.UTF8'));
    temp := utl_raw.cast_to_varchar2(dbms_lob.substr(src, v_buffer, v_start));
    dest := dest || temp;
    v_start := v_start + v_buffer;
  end loop;
  return dest;
end b2c;
/


参考技术A 在某些数据库中有交叉表,但在mysql中却没有这个功能,但网上看到有不少朋友想找出一个解决方法,特发贴集思广义。
http://topic.csdn.net/u/20090530/23/0b782674-4b0b-4cf5-bc1a-e8914aaee5ab.html?96198
现整理解法如下:
数据样本:
create table tx(
id int primary key,
c1 char(2),
c2 char(2),
c3 int
);
insert into tx values
(1 ,'A1','B1',9),
(2 ,'A2','B1',7),
(3 ,'A3','B1',4),
(4 ,'A4','B1',2),
(5 ,'A1','B2',2),
(6 ,'A2','B2',9),
(7 ,'A3','B2',8),
(8 ,'A4','B2',5),
(9 ,'A1','B3',1),
(10 ,'A2','B3',8),
(11 ,'A3','B3',8),
(12 ,'A4','B3',6),
(13 ,'A1','B4',8),
(14 ,'A2','B4',2),
(15 ,'A3','B4',6),
(16 ,'A4','B4',9),
(17 ,'A1','B4',3),
(18 ,'A2','B4',5),
(19 ,'A3','B4',2),
(20 ,'A4','B4',5);

mysql> select * from tx;
+----+------+------+------+
| id | c1 | c2 | c3 |
+----+------+------+------+
| 1 | A1 | B1 | 9 |
| 2 | A2 | B1 | 7 |
| 3 | A3 | B1 | 4 |
| 4 | A4 | B1 | 2 |
| 5 | A1 | B2 | 2 |
| 6 | A2 | B2 | 9 |
| 7 | A3 | B2 | 8 |
| 8 | A4 | B2 | 5 |
| 9 | A1 | B3 | 1 |
| 10 | A2 | B3 | 8 |
| 11 | A3 | B3 | 8 |
| 12 | A4 | B3 | 6 |
| 13 | A1 | B4 | 8 |
| 14 | A2 | B4 | 2 |
| 15 | A3 | B4 | 6 |
| 16 | A4 | B4 | 9 |
| 17 | A1 | B4 | 3 |
| 18 | A2 | B4 | 5 |
| 19 | A3 | B4 | 2 |
| 20 | A4 | B4 | 5 |
+----+------+------+------+
20 rows in set (0.00 sec)
mysql>
期望结果
+------+-----+-----+-----+-----+------+
|C1 |B1 |B2 |B3 |B4 |Total |
+------+-----+-----+-----+-----+------+
|A1 |9 |2 |1 |11 |23 |
|A2 |7 |9 |8 |7 |31 |
|A3 |4 |8 |8 |8 |28 |
|A4 |2 |5 |6 |14 |27 |
|Total |22 |24 |23 |40 |109 |
+------+-----+-----+-----+-----+------+
1. 利用SUM(IF()) 生成列 + WITH ROLLUP 生成汇总行,并利用 IFNULL将汇总行标题显示为 Total
mysql> SELECT
-> IFNULL(c1,'total') AS total,
-> SUM(IF(c2='B1',c3,0)) AS B1,
-> SUM(IF(c2='B2',c3,0)) AS B2,
-> SUM(IF(c2='B3',c3,0)) AS B3,
-> SUM(IF(c2='B4',c3,0)) AS B4,
-> SUM(IF(c2='total',c3,0)) AS total
-> FROM (
-> SELECT c1,IFNULL(c2,'total') AS c2,SUM(c3) AS c3
-> FROM tx
-> GROUP BY c1,c2
-> WITH ROLLUP
-> HAVING c1 IS NOT NULL
-> ) AS A
-> GROUP BY c1
-> WITH ROLLUP;
+-------+------+------+------+------+-------+
| total | B1 | B2 | B3 | B4 | total |
+-------+------+------+------+------+-------+
| A1 | 9 | 2 | 1 | 11 | 23 |
| A2 | 7 | 9 | 8 | 7 | 31 |
| A3 | 4 | 8 | 8 | 8 | 28 |
| A4 | 2 | 5 | 6 | 14 | 27 |
| total | 22 | 24 | 23 | 40 | 109 |
+-------+------+------+------+------+-------+
5 rows in set, 1 warning (0.00 sec)
2. 利用SUM(IF()) 生成列 + UNION 生成汇总行,并利用 IFNULL将汇总行标题显示为 Total
mysql> select c1,
-> sum(if(c2='B1',C3,0)) AS B1,
-> sum(if(c2='B2',C3,0)) AS B2,
-> sum(if(c2='B3',C3,0)) AS B3,
-> sum(if(c2='B4',C3,0)) AS B4,SUM(C3) AS TOTAL
-> from tx
-> group by C1
-> UNION
-> SELECT 'TOTAL',sum(if(c2='B1',C3,0)) AS B1,
-> sum(if(c2='B2',C3,0)) AS B2,
-> sum(if(c2='B3',C3,0)) AS B3,
-> sum(if(c2='B4',C3,0)) AS B4,SUM(C3) FROM TX
-> ;
+-------+------+------+------+------+-------+
| c1 | B1 | B2 | B3 | B4 | TOTAL |
+-------+------+------+------+------+-------+
| A1 | 9 | 2 | 1 | 11 | 23 |
| A2 | 7 | 9 | 8 | 7 | 31 |
| A3 | 4 | 8 | 8 | 8 | 28 |
| A4 | 2 | 5 | 6 | 14 | 27 |
| TOTAL | 22 | 24 | 23 | 40 | 109 |
+-------+------+------+------+------+-------+
5 rows in set (0.00 sec)
mysql>

3. 利用SUM(IF()) 生成列,直接生成结果不再利用子查询
mysql> select ifnull(c1,'total'),
-> sum(if(c2='B1',C3,0)) AS B1,
-> sum(if(c2='B2',C3,0)) AS B2,
-> sum(if(c2='B3',C3,0)) AS B3,
-> sum(if(c2='B4',C3,0)) AS B4,SUM(C3) AS TOTAL
-> from tx
-> group by C1 with rollup ;
+--------------------+------+------+------+------+-------+
| ifnull(c1,'total') | B1 | B2 | B3 | B4 | TOTAL |
+--------------------+------+------+------+------+-------+
| A1 | 9 | 2 | 1 | 11 | 23 |
| A2 | 7 | 9 | 8 | 7 | 31 |
| A3 | 4 | 8 | 8 | 8 | 28 |
| A4 | 2 | 5 | 6 | 14 | 27 |
| total | 22 | 24 | 23 | 40 | 109 |
+--------------------+------+------+------+------+-------+
5 rows in set (0.00 sec)
mysql>

4. 动态,适用于列不确定情况,
mysql> SET @EE='';
mysql> SELECT @EE:=CONCAT(@EE,'SUM(IF(C2=\'',C2,'\'',',C3,0)) AS ',C2,',') FROM (SELECT DISTINCT C2 FROM TX) A;

mysql> SET @QQ=CONCAT('SELECT ifnull(c1,\'total\'),',LEFT(@EE,LENGTH(@EE)-1),' ,SUM(C3) AS TOTAL FROM TX GROUP BY C1 WITH ROLLUP');
Query OK, 0 rows affected (0.00 sec)
mysql> PREPARE stmt2 FROM @QQ;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE stmt2;
+--------------------+------+------+------+------+-------+
| ifnull(c1,'total') | B1 | B2 | B3 | B4 | TOTAL |
+--------------------+------+------+------+------+-------+
| A1 | 9 | 2 | 1 | 11 | 23 |
| A2 | 7 | 9 | 8 | 7 | 31 |
| A3 | 4 | 8 | 8 | 8 | 28 |
| A4 | 2 | 5 | 6 | 14 | 27 |
| total | 22 | 24 | 23 | 40 | 109 |
+--------------------+------+------+------+------+-------+
5 rows in set (0.00 sec)
mysql>

希望能帮的上你........
参考技术B 这个是构造的表,你看下能能不能帮到你。
--构造表结构
/*create table xx_test(
x_num varchar2(50),
x_date date,
x_qty number,
x_blob blob
) ;*/

/*truncate table xx_test ;*/
--插入数据
insert into xx_test(x_num,x_date,x_qty,x_blob) select '1001',to_date('2014-06-01','yyyy-mm-dd'),30,empty_blob() from dual;
insert into xx_test(x_num,x_date,x_qty,x_blob) select '1001',to_date('2014-06-25','yyyy-mm-dd'),10,empty_blob() from dual;
insert into xx_test(x_num,x_date,x_qty,x_blob) select '1002',to_date('2014-06-15','yyyy-mm-dd'),35,empty_blob() from dual;
insert into xx_test(x_num,x_date,x_qty,x_blob) select '1002',to_date('2014-06-13','yyyy-mm-dd'),17,empty_blob() from dual;
commit;

--将合并后的数据更新到blob列

update xx_test xx
set x_blob =
(select to_blob(UTL_RAW.cast_to_raw(
'编号:' || t1.x_num || ',时间: ' || t1.x_date ||
',数量: ' || t1.x_qty || ' ; ' || '时间: ' ||
t2.x_date || ',数量: ' || t2.x_qty) )
from (select t.rowid,
t.*,
LAG(t.x_num) OVER(PARTITION BY t.x_num ORDER BY t.x_num) RNNEXT
from xx_test t) t1,
xx_test t2
where t1.x_num = t2.x_num
and t1.RNNEXT is null
and t2.rowid <> t1.rowid
and t1.x_num = xx.x_num);
commit;

--结果
select * from xx_test;

oracle 列转行

有两张表
A 产品表
产品名称,产品编码
产品1 001

B销量表
产品编号 月份 销量
001 1 1000
001 2 2000
001 3 3000
。。。。。。
001 12 12000

通过sql语句如何输出

产品名称 产品编号 1月 2月 3月 。。。。。12月
产品1 001 1000 2000 3000 。。。。 12000

最后请教一个设计表的问题。想这种情况是如上表结构比较好呢?还是将B表设计为
产品编号 1月 2月 3月 。。。。。12月

我觉得设计成一张表就够了,比如table:AB吧然后用游标实现 ,
create or replace procedure ptest as
strname varchar2(30);
str varchar2(1000);
strid varchar2(10);
cursor cur is select * from AB;
begin
str:='';
open cur;
loop
fetch cur into strid,strname;
exit when cur%notfound;
str:=str||strname||',';
end loop;
str:=substr(str,1,length(str)-1);
dbms_output.put_line(str);
end;
参考技术A 基本思路是运用case when语句,最好把创建表的语句给出(可用dbms_metadata.get_ddl,运行前把long设置大点)。

自己利用示例表得出需求,可以根据你自己的表来进行修改:

demo@ORCL>select max(case when deptno=10 then ename else null end) as "部门10",
max(case when deptno=20 then ename else null end) as "部门20",
max(case when deptno=30 then ename else null end) as "部门30"
from
(select deptno,ename,row_number() over(partition by deptno order by ename) rn from emp) x
group by rn order by 1,2,3;

部门10 部门20 部门30
---------- ---------- ---------
CLARK ADAMS ALLEN
KING FORD BLAKE
MILLER JONES JAMES
SCOTT MARTIN
SMITH TURNER
WARD
参考技术B oracle下可以用函数decode处理:

select t1.产品名称,t1.产品编号,
sum(decode(t2.月份,1,t2.销量,0)) 1月,
sum(decode(t2.月份,2,t2.销量,0)) 2月,
sum(decode(t2.月份,3,t2.销量,0)) 3月,
sum(decode(t2.月份,4,t2.销量,0)) 4月,
...
sum(decode(t2.月份,11,t2.销量,0)) 11月,
sum(decode(t2.月份,12,t2.销量,0)) 12月
from 产品表 t1, 销量表 t2
where t1.产品编码=t2.产品编码
group by t1.产品名称,t1.产品编号;

一般设计成你现有这样的结构就可以了.因为销量表是个明细记录表,记录数比较大.
参考技术C select a.产品名称,b.产品编码, wmsys.wm_concat(销量) from 产品表 a, 销量表 b where a.产品编号 = b.产品编码 group by a.产品名称 ,b.产品编码

以上是关于关于ORACLE列转行的问题的主要内容,如果未能解决你的问题,请参考以下文章

ORACLE 同表列转行

oracle 列转行

oracle列转行函数

Oracle列转行,行转列

oracle 列转行的问题。图1是数据库查询的结果 图二是我想要的结果的格式,谢谢了。

oracle 多列 列转行