为Oracle迁移定义“ WM_CONCAT(col)”为“ LISTAGG(col,',')”

时间:2019-10-07 21:39:48

标签: oracle database-migration listagg wm-concat

我们正在从Oracle 10g迁移到环境数据库的18c。为了使事情复杂化,并非计划立即迁移所有环境,因此该应用程序必须同时支持这两种环境。发现的不兼容性之一是在10g中支持WM_Concat,但在18c中不支持,而在18c中却支持ListAgg(新的等效函数),但不支持10g。因此,我正在寻找一种暂时可以在两个数据库版本中使用的实现。

我的想法是10g中的wm_concat(myColumn)与18c中的listagg(myColumn, ',')等效,因此我想将wm_concat(myColumn)定义为传递的新18c数据库中的函数到幕后的listagg(myColumn, ',')并返回结果。这样,应用程序就可以安全地继续在10g和18c数据库上正常使用wm_concat,直到所有环境都在18c上为止,之后可以将应用程序交换为使用listagg和临时自定义{{ 1}}函数可以从18c数据库中删除,从而完成迁移。

总而言之,定义wm_concat以使wm_concat的行为与查询中的wm_concat(myColumn)完全一样的正确方法是什么?

1 个答案:

答案 0 :(得分:6)

[TL; DR]您无法在Oracle 18c中实现WM_CONCAT的自定义版本,使其行为与LISTAGG完全相同,但是您可以通过用户定义的聚合函数来达到目的。 / p>


LISTAGG has the syntax

  

LISTAGG function

WM_CONCAT的语法为:

  

WM_CONCAT( expr )

您会发现WM_CONCAT缺乏指定分隔符或ORDER BY子句的能力。

如果要在更高版本中重新定义WM_CONCAT,则可能最终会使用用户定义的聚合函数:

用户定义的对象

CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
  g_string  VARCHAR2(32767),

  STATIC FUNCTION ODCIAggregateInitialize(
    sctx  IN OUT  t_string_agg
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(
    self   IN OUT  t_string_agg,
    value  IN      VARCHAR2
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(
    self         IN   t_string_agg,
    returnValue  OUT  VARCHAR2,
    flags        IN   NUMBER
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(
    self  IN OUT  t_string_agg,
    ctx2  IN      t_string_agg
  ) RETURN NUMBER
);
/

用户定义的对象主体

CREATE OR REPLACE TYPE BODY t_string_agg IS
  STATIC FUNCTION ODCIAggregateInitialize(
    sctx  IN OUT  t_string_agg
  ) RETURN NUMBER
  IS
  BEGIN
    sctx := t_string_agg(NULL);
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(
    self   IN OUT  t_string_agg,
    value  IN      VARCHAR2
  ) RETURN NUMBER
  IS
  BEGIN
    SELF.g_string := self.g_string || ',' || value;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(
    self         IN   t_string_agg,
    returnValue  OUT  VARCHAR2,
    flags        IN   NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    returnValue := SUBSTR( SELF.g_string, 2 );
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(
    self  IN OUT  t_string_agg,
    ctx2  IN      t_string_agg
  ) RETURN NUMBER
  IS
  BEGIN
    SELF.g_string := SELF.g_string || ctx2.g_string;
    RETURN ODCIConst.Success;
  END;
END;
/

用户定义的聚合功能

CREATE OR REPLACE FUNCTION wm_concat (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/

测试数据

CREATE TABLE test_data ( id, value ) AS
  SELECT 1, 'C' FROM DUAL UNION ALL
  SELECT 1, 'A' FROM DUAL UNION ALL
  SELECT 1, 'B' FROM DUAL UNION ALL
  SELECT 2, 'D' FROM DUAL UNION ALL
  SELECT 2, 'E' FROM DUAL;

测试查询

SELECT id,
       wm_concat( value ) AS wm_concat,
       LISTAGG( value, ',' ) WITHIN GROUP ( ORDER BY ROWNUM ) AS listagg
FROM   test_data
GROUP BY id;

输出

ID | WM_CONCAT | LISTAGG
-: | :-------- | :------
 1 | C,B,A     | C,A,B  
 2 | D,E       | D,E    

您可以看到输出的顺序是不同的;因此您可以获得接近但不完全匹配的结果。

db <>提琴here


更新

如果我们使用效率低下的聚合函数将所有值存储在集合中,然后调用LISTAGG,那么我们可以更进一步:

CREATE TYPE stringlist IS TABLE OF VARCHAR2(4000);

CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
  strings stringlist,

  STATIC FUNCTION ODCIAggregateInitialize(
    sctx  IN OUT  t_string_agg
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(
    self   IN OUT  t_string_agg,
    value  IN      VARCHAR2
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(
    self         IN   t_string_agg,
    returnValue  OUT  VARCHAR2,
    flags        IN   NUMBER
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(
    self  IN OUT  t_string_agg,
    ctx2  IN      t_string_agg
  ) RETURN NUMBER
);
/

CREATE OR REPLACE TYPE BODY t_string_agg IS
  STATIC FUNCTION ODCIAggregateInitialize(
    sctx  IN OUT  t_string_agg
  ) RETURN NUMBER
  IS
  BEGIN
    sctx := t_string_agg( stringlist() );
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(
    self   IN OUT  t_string_agg,
    value  IN      VARCHAR2
  ) RETURN NUMBER
  IS
  BEGIN
    SELF.strings.EXTEND;
    SELF.strings( SELF.strings.COUNT ) := value;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(
    self         IN   t_string_agg,
    returnValue  OUT  VARCHAR2,
    flags        IN   NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    SELECT LISTAGG( column_value, ',' ) WITHIN GROUP ( ORDER BY column_value )
    INTO   returnValue
    FROM   TABLE( SELF.strings );
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(
    self  IN OUT  t_string_agg,
    ctx2  IN      t_string_agg
  ) RETURN NUMBER
  IS
  BEGIN
    SELF.strings := SELF.strings MULTISET UNION ALL ctx2.strings;
    RETURN ODCIConst.Success;
  END;
END;
/

CREATE OR REPLACE FUNCTION wm_concat (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/

然后:

SELECT id,
       wm_concat( value ) AS wm_concat,
       LISTAGG( value, ',' ) WITHIN GROUP ( ORDER BY value ) AS listagg
FROM   test_data
GROUP BY id;

输出:

ID | WM_CONCAT | LISTAGG
-: | :-------- | :------
 1 | A,B,C     | A,B,C  
 2 | D,E       | D,E    

如果(且仅当)您要按字母顺序对值排序时,它将提供与LISTAGG相同的输出;您不能指定其他顺序。它还需要从PL / SQL到SQL的上下文切换,以在最后一步执行聚合,因此它可能比纯PL / SQL聚合函数要慢,并且它将集合保存在内存中并继续扩展它,因此可以随着集合的增长(或在并行系统中合并)会增加额外的开销,这将进一步降低它的速度。

因此,它仍然不是LISTAGG,但如果您愿意忍受性能方面的问题,它就会尽可能地接近您。

db <>提琴here