Convert WM_CONCAT to Listagg

社会主义新天地 提交于 2019-12-23 14:57:55

问题


My DBA is upgrading my oracle db from v10 to v12. I have some old SP's that uses wm_concat and I need to change it to listagg. The problematic code is this:

Select  registration_id,package_set_id,
        REPLACE(REPLACE(WM_CONCAT(REPLACE( (case when ROW_NUMBER() over (partition by product_id,product_detail_set_id,registration_id,product_family_id,application_id,package_Set_id,
               legal_status order by packset_country)=1 then legal_status else null end), ',' , '#')) OVER (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID,
               REGISTRATION_ID  ,PRODUCT_FAMILY_ID,APPLICATION_ID,PACKAGE_SET_ID   ORDER BY Packset_country  ), ',' , ' | '), '#', ',') as legal_status,

        (REPLACE(REPLACE(WM_CONCAT(REPLACE(ev_code, ',' , '#')) OVER (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID,
               REGISTRATION_ID  ,PRODUCT_FAMILY_ID,APPLICATION_ID,PACKAGE_SET_ID   ORDER BY ev_code  ), ',' , ' | '), '#', ',')) EV_CODES,

         min(marketed_date) over (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID,REGISTRATION_ID  ,PRODUCT_FAMILY_ID,APPLICATION_ID,PACKAGE_SET_ID) as marketed_date,

         (REPLACE(REPLACE(WM_CONCAT(REPLACE(Packset_country, ',' , '#')) OVER (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID, REGISTRATION_ID ,PRODUCT_FAMILY_ID,
                APPLICATION_ID,PACKAGE_SET_ID   ORDER BY Packset_country, reg_packset_country_id ), ',' , ' | '), '#', ',')) REGISTRATION_PACKSET_COUNTRIES,
         ROW_NUMBER() OVER (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID,REGISTRATION_ID  ,PRODUCT_FAMILY_ID,APPLICATION_ID,PACKAGE_SET_ID  
                ORDER BY Packset_country desc ,reg_packset_country_id)  ROW_NUM,     
         REPLACE(REPLACE(WM_CONCAT(REPLACE( (case when currently_marketed_in_country='Y' then packset_country end), ',' , '#')) OVER (PARTITION BY PRODUCT_ID,  PRODUCT_DETAIL_SET_ID,
                REGISTRATION_ID  ,PRODUCT_FAMILY_ID,APPLICATION_ID,PACKAGE_SET_ID  ORDER BY packset_country ,currently_marketed_in_country,reg_packset_country_id ), ',' , ' | '), '#', ',') as CURRENTLY_MARKETED_COUNTRIES
from radw_dwh.dw202_fact_reg_pack_countries

The expected result is:

I tried to change it but there is a problem when I'm trying to use "ROW_NUMBER()" in side "LISTAGG".

How can I fix this?


回答1:


The basic syntax of LISTAGG is:

LISTAGG(col_name_to_be_aggregated, ',') WITHIN GROUP (ORDER BY col)

In your case, since you have a sub-query as result set to WM_CONCAT, you could put the same sub-query in place of col_name_to_be_aggregated in LISTAGG.

I think you can also get rid of all the REPLACE functions, since, LISTAGG can accept the delimiter of your choice.

Try,

LISTAGG
(
  CASE
  WHEN ROW_NUMBER() OVER (PARTITION BY product_id,
                                       product_detail_set_id,
                                       registration_id,
                                       product_family_id,
                                       application_id,
                                       package_Set_id, 
                                       legal_status 
                                       order by packset_country)=1 THEN
    legal_status
  ELSE
    NULL
  END), ',') WITHIN GROUP (ORDER BY required_col)

Also, I would like to explain why you need to move to LISTAGG in 12c. Since t has been removed from the latest 12c version. Therefore, any application which has had been relying on WM_CONCAT function will not work once upgraded to 12c. Read Why not use WM_CONCAT function in Oracle?

For pre-11g Release 2, you can't use LISTAGG. There are many string aggregation techniques, have a look at my answer here.

More details about Oracle String Aggregation Techniques




回答2:


As example in code odciaggregate interface in code:

    create or replace type string_agg_type as object ( total varchar2(4000), 
static function ODCIAggregateInitialize(sctx IN OUT string_agg_type ) return number,
 member function ODCIAggregateIterate(self IN OUT string_agg_type , value IN varchar2 ) return number, 
member function ODCIAggregateTerminate(self IN string_agg_type, returnValue OUT varchar2, flags IN number) return number,
 member function ODCIAggregateMerge(self IN OUT string_agg_type, ctx2 IN string_agg_type) return number ); 
/ 
create or replace type body string_agg_type is static function odciaggregateinitialize(sctx IN OUT string_agg_type) return number is begin sctx := string_agg_type(null); return odciconst.success; end; 
member function odciaggregateiterate(self IN OUT string_agg_type, value IN varchar2) return number is begin self.total := self.total || ',' || value; return odciconst.success; end; 
member function odciaggregateterminate(self IN string_agg_type, returnvalue OUT varchar2, flags IN number) return number is begin returnvalue := ltrim(self.total, ','); return odciconst.success; end; 
member function odciaggregatemerge(self IN OUT string_agg_type, ctx2 IN string_agg_type) return number is begin self.total := self.total || ctx2.total; return odciconst.success; end; 
end;
/ 
CREATE or replace FUNCTION stragg(input varchar2) RETURN varchar2 PARALLEL_ENABLE AGGREGATE USING string_agg_type; 
/


with t as ( select 'a1' val from dual union all select 'b2' val from dual ) select stragg(val) as val from t; val --------------------------- a1,b2



回答3:


Motivation for the following approach

We had several problems with wm_concat (known bugs) or wm_concat-to-list_agg-conversion-for-12c-use.

(Not to mention the nice compact/declarative usage using wm_concat in many scenarios where other solutions may be quite complicated to read.)

(Sometimes having to use xmlagg because of > 4000 chars/clob problems or regexp_replace workarounds for the wm_concat( distinct ... ) emulation), e.g. like here.

Strategy

So finally in my opinion a rather nice strategy (which may differ depending on your environment/needs/strategy) is, to

  1. create two functions
    • create function wm_concat_32767(... (working on varchar(32767) which is possible since Oracle 12c) and
      • depending on MAX_STRING_SIZE of your db you may want to adopt this to "4000" or others
    • create function wm_concat_clob(... in your db in the sys-schema first.
    • They should be based on code like provided by the answer from Dart XKey (maybe copied from asktom/Tom Kyte)
  2. create public synonym wm_concat for sys.wm_concat_32767
    • (I would generally let the wm_concat point to the likely faster wm_concat_32767 rather than wm_concat_clob)
    • this allows the easy reuse/migration of existing wm_concat(varchar(4000)) based < 11.2g code/usage
    • one might already know the name of the function and thus easy to get use to
    • (the problem that online documentation about it may be misleading is what I could live with since the benefits would outweight the disadvantages for me in general)
    • it will even allow the use of wm_concat( distinct ... ) queries with compact declarative syntax
      • as opposed to the listagg/xmlagg/regexp_replace-based workarounds mentioned above
  3. create public synonym wm_concat_clob for sys.wm_concat_clob
  4. give proper public execution rights to the wm_concat_*functions

Code

So finally the adopted code we use (maybe I'll update everything mentioned above over time):

wm_concat_32767(...) creation:

  • depending on MAX_STRING_SIZE of your db you may want to adopt this to "4000" or other values

_

create or replace type  string_agg_type  as object ( 
  total varchar2(32767), 
  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type ) return number,
  member function ODCIAggregateIterate(self IN OUT string_agg_type , value IN varchar2 ) return number, 
  member function ODCIAggregateTerminate(self IN string_agg_type, returnValue OUT varchar2, flags IN number) return number,
  member function ODCIAggregateMerge(self IN OUT string_agg_type, ctx2 IN string_agg_type) return number 
); 
/ 
create or replace type body  string_agg_type  is static function odciaggregateinitialize(sctx IN OUT string_agg_type) return number is begin sctx := string_agg_type(null); return odciconst.success; end; 
  member function odciaggregateiterate(self IN OUT string_agg_type, value IN varchar2) return number is begin self.total := self.total || ',' || value; return odciconst.success; end; 
  member function odciaggregateterminate(self IN string_agg_type, returnvalue OUT varchar2, flags IN number) return number is begin returnvalue := ltrim(self.total, ','); return odciconst.success; end; 
  member function odciaggregatemerge(self IN OUT string_agg_type, ctx2 IN string_agg_type) return number is begin self.total := self.total || ctx2.total; return odciconst.success; end; 
end;
/ 
CREATE or replace FUNCTION  wm_concat_32767(input varchar2) RETURN varchar2 PARALLEL_ENABLE AGGREGATE USING string_agg_type; 
/

wm_concat_clob(...) creation based on this code from Michel Cadot:

create or replace type  stragg_type4  as object (
  result CLOB,
  static function ODCIAggregateInitialize (sctx IN OUT stragg_type4) return number,
  member function ODCIAggregateIterate (self IN OUT stragg_type4, value IN varchar2) return number,
  member function ODCIAggregateTerminate (self IN stragg_type4, returnValue OUT CLOB, flags IN number) return number,
  member function ODCIAggregateMerge (self IN OUT stragg_type4, ctx2 IN stragg_type4) return number
);
/

create or replace type body  stragg_type4  is

  static function ODCIAggregateInitialize (sctx IN OUT stragg_type4) return number is begin
    sctx := stragg_type4 (null);
    dbms_lob.createtemporary (lob_loc => sctx.result, cache   => TRUE, dur => dbms_lob.call);
    return ODCIConst.Success;
  end;

  member function ODCIAggregateIterate (self IN OUT stragg_type4, value IN varchar2) return number is begin
    self.result := self.result || ',' || value;
    return ODCIConst.Success;
  end;

  member function ODCIAggregateTerminate (self IN stragg_type4, returnValue OUT CLOB, flags IN number) return number is begin
    returnValue := ltrim (self.result, ',');
    return ODCIConst.Success;
  end;

  member function ODCIAggregateMerge (self IN OUT stragg_type4, ctx2 IN stragg_type4) return number is begin
    self.result := self.result || ctx2.result;
    return ODCIConst.Success;
  end;

end;
/

sho err

CREATE or replace FUNCTION wm_concat_clob(input varchar2) RETURN CLOB PARALLEL_ENABLE AGGREGATE USING stragg_type4;
/


来源:https://stackoverflow.com/questions/33212899/convert-wm-concat-to-listagg

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!