暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SQL优化-不一样的行转列

原创 不想用随机名字 2024-10-16
133

  最近在客户现场,遇到开发人员说有一个场景在进行数据迁移的时候sql执行不动,大致有点列转行,然后再去关联其他的表,再进行转列的处理,OB版本是3.2.3,租户是oracle模式。我们先看一下表的数据形式,假设表名字CREDIT_INFO;

CREDIT_NO CUSTOMER_ID
202410101010 AAAAA,CCCC,EEEE
202411101010 BBBBB,DDDD

  表中的CUSTOMER_ID是用逗号分开的多个,我们需要CREDIT_INFO中的多个CUSTOMER_ID转换成CUSTOMER_NAME,如下表展示。

CREDIT_NO CUSTOMER_NAME
202410101010 武松、林冲、李逵
202411101010 宋江、卢俊义

  开发人员的实现方式是创建了一个函数,在函数中对字段按照逗号进行拆分,然后关联客户表(CUST_INFO)获取客户姓名,函数的大致实现方式如下:

create or replace function getsplitname(customer_id_set varchar2,split_str varchar2) return varchar2 is source_str varchar2(32767); return_str varchar2(32767); return_temp varchar2(32767); temp_str varchar2(32767); point_pos number; begin source_str:=customer_id_set; point_pos:=instr(source_str,split_str ); while source_str>=1 loop temp_str := substr(source_str,1,point_pos-1); source_str:=substr(source_str,point_pos+1); point_pos:=instr(source_str,split_str); select customer_name into return_temp from cust_info where customer_id=temp_str ; return_str:=return_str||','return_temp; end loop; select customer_name into return_temp from cust_info where customer_id=source_str; return_str:=return_str||','return_temp; return return_str; end;
复制

sql代码实现方式如下:

insert /*+ENABLE_PARALLEL_DML PARALLEL(8)*/ into table_target select getsplitname(CUSTOMER_ID) from CREDIT_INFO;
复制

  我这里对sql语句做了简化,原sql还关联了很多其他的表,CREDIT_INFO表中够用60万条记录,总之这个sql非常慢,因为调用了函数,每一行数据都要通过这个函数进行子查询,于是我尝试对这个进行了改写,不使用函数,还是使用子查询的方式。改写的sql如下:

select select to_char(wm_concat(customer_name)) from cust_info where cucstomer_id in( select regexp_substr(ci.customer_id,'[^,]+',1,ROWNUM) id from dual connect by rownum<=length(regexp_replace(ci.customer_id,'[^,]+'))+1) as customer_name from credit_info ci;
复制

  尝试后效果仍然没有改善,而且这个子查询的查询计划比较奇怪,虽然customer_id是cust_info表的主键,但是查询计划还是全表扫描,只能怀疑ob对这个写法不是特别支持。于是我打算使用最简单的方法:
  创建一个存储过程,在存储过程中使用游标,对每一行的客户号进行处理。每一个客户号插入一条记录到临时表,以下是我的临时表。

CREATE TABLE CREDIT_MID_USER_TAB ( creditno VARCHAR2(40) , --源表的管理字段 USER_ID_SET VARCHAR2(60), --客户编号 order_no number --排序字段,因为转换完以后需要与客户号的展示顺序一致 );
复制

存储过程脚本如下:

create or replace PROCEDURE CREDIT_USER_MID_FUN is iPointNow NUMBER; PCREDITNO varchar2(32); pInputString VARCHAR2(32767); sSource VARCHAR2(32767); sTemp VARCHAR2(32767); sTemp_1 VARCHAR2(32767); iCount NUMBER; --定义游标,只处理存在多个客户号的情况,区分开是为了提升速度,单个客户的不需要再 循环中处理 CURSOR cus_credit_cor is select credit_no,CUSTOMER_ID from CREDIT_INFO CI where instr(CI.CUSTOMER_ID,',')>0 ; Begin delete from CREDIT_MID_USER_TAB; commit; iCount:=0; open cus_credit_cor; LOOP fetch cus_credit_cor into pCreditNo,pInputString; exit when cus_credit_cor%NOTFOUND; iCount:=0; sSource := pInputString; iPointNow :=instr(sSource,','); while iPointNow>=1 loop sTemp:=substr(sSource,1,iPointNow-1); sSource:=substr(sSource,iPointNow+1); iPointNow :=instr(sSource,','); insert into CREDIT_MID_USER_TAB(creditno,USER_ID_SET,order_no) values(pCreditNo,sTemp,iCount); iCount:=iCount+1; END LOOP; sTemp:=sSource; insert into CREDIT_MID_USER_TAB(creditno,USER_ID_SET,order_no) values(pCreditNo,sTemp,iCount); END LOOP; close cus_credit_cor; commit; --处理只有单个客户号的情况, iCount:=0; insert into CREDIT_MID_USER_TAB(creditno,USER_ID_SET) select credit_no, CUSTOMER_ID from CREDIT_INFO CI where instr(CI.CUSTOMER_ID,',')<1 ; COMMIT; END;
复制

  最后的sql实现需要关联临时表,如下

select LISTAGG(CIT.MFCUSTOMERID,',') WITHIN GROUP(ORDER by CIT.ORDER_NO) NEW_USER_SET from CREDIT_INFO Ci left join CREDIT_MID_USER_TAB CIT group by ci.creditno;
复制

  通过临时表的方式实现行转列,然后用通过LISTAGG函数实现了列转行,速度由原来的执行不动,到加上处理临时表的时间总共2分钟就可以执行完成。
  之前也发布了几篇关于OB的使用笔记,越复杂高大上的sql写法,可能对优化器本身都是一个考验,尽量简化sql语言,让优化器少一些负担,在编写sql脚本时就减少一点访问了路径,可能会事半功倍。避免一些因执行计划总是来回变动带来的效率不一致问题。

最后修改时间:2024-10-16 15:19:02
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论