Определите "WM_CONCAT (col)" как "LISTAGG (col, ',')" для Oracle Migration - PullRequest
2 голосов
/ 08 октября 2019

Мы находимся в процессе перехода с Oracle 10g на 18c для наших баз данных среды. Чтобы усложнить ситуацию, не все среды планируется переносить одновременно, поэтому приложение должно поддерживать оба варианта на некоторое время. Одна из обнаруженных несовместимостей заключается в том, что WM_Concat поддерживается в 10g, но не в 18c, а ListAgg (новая эквивалентная функция) поддерживается в 18c, но не в 10g. Таким образом, я ищу реализацию, которая в настоящее время будет работать в обеих версиях базы данных.

Я думаю, что wm_concat(myColumn) в 10g эквивалентно listagg(myColumn, ',') в 18c, поэтому я бы хотелопределить wm_concat(myColumn) как функцию в новых базах данных 18c, которая проходит к listagg(myColumn, ',') за сценой и возвращает результат. Таким образом, приложение может безопасно продолжать использовать wm_concat как обычно в базах данных 10g и 18c до тех пор, пока все среды не перейдут в режим 18c, после чего приложение можно поменять местами на использование listagg и временную пользовательскую функцию wm_concatудален из баз данных 18c, завершив миграцию.

Подводя итог, можно сказать, как правильно определить wm_concat, чтобы wm_concat(myColumn) вел себя точно так же, как listagg(myColumn, ',') в запросе?

1 Ответ

6 голосов
/ 08 октября 2019

[TL; DR] Вы не можете реализовать пользовательскую версию WM_CONCAT в Oracle 18c, чтобы вести себя точно так же, как LISTAGG, но вы можете приблизиться к пользовательской функции агрегирования.


LISTAGG имеет синтаксис :

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 <> fiddle здесь


Обновление :

Если мы перейдем к неэффективной функции агрегирования, которая хранит все значения в коллекции и затем вызовет 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 <> fiddle здесь

...