Конвертация табличных типов из Oracle в PostgreSQL с помощью массивов

Oracle предлагает удобный механизм для работы с табличными типами данных. К сожалению, в других базах данных это может стать проблемой. PostgreSQL, например, не поддерживает табличные типы, поэтому вопрос о том, как именно преобразовывать такие типы, очень важен.

Существует несколько способов преобразования табличных типов данных в PostgreSQL. Первый — преобразование с помощью временных таблиц. Это решение довольно простое, но у него есть один большой недостаток — оно сильно загромождает код и делает его очень сложным для чтения. Ведь простые операции будут заменены запросами из временной таблицы.

Второй вариант — преобразование табличных типов с помощью массивов в PostgreSQL.

В этой статье описаны основные возможности и реализация преобразования табличных типов с помощью массивов в PostgreSQL. Чтобы лучше понять принципы преобразования, давайте преобразуем следующий пример:

 CREATE TYPE employee AS OBJECT (
    id NUMBER,
    Name VARCHAR(300)
    );
 CREATE TYPE employees_tab IS TABLE OF employee;

 CREATE OR REPLACE PROCEDURE hire(EMPLOYEES in out employees_tab, id NUMBER, Name VARCHAR) AS
 NEW_EMPLOYEES employees_tab := employees_tab();
 BEGIN
    EMPLOYEES.Extend(1);
    EMPLOYEES(EMPLOYEES.count) := employee(id, Name);
    FOR i IN EMPLOYEES.first..EMPLOYEES.last
    LOOP
       INSERT INTO emp_tab values (EMPLOYEES(i).id, EMPLOYEES(i).Name);
    END LOOP;
    INSERT INTO EMP_TAB SELECT * FROM TABLE(EMPLOYEES);
    NEW_EMPLOYEES.Extend(EMPLOYEES.count);
    NEW_EMPLOYEES.Delete;
 END;

Ниже приведен пример преобразования этой процедуры с использованием массивов в PostgreSQL:

 CREATE TYPE EMPLOYEE AS(id DOUBLE PRECISION,Name VARCHAR(300));
 -- CREATE TYPE EMPLOYEES_TAB IS TABLE OF employee;
 CREATE OR REPLACE PROCEDURE HIRE(INOUT EMPLOYEES EMPLOYEE[] ,
 ID DOUBLE PRECISION, NAME VARCHAR(4000))
 LANGUAGE plpgsql
    AS $$
    DECLARE
    EMPLOYEES_REC  EMPLOYEE;
    NEW_EMPLOYEES  EMPLOYEE[] default array[]::EMPLOYEE[] ;
 BEGIN
    EMPLOYEES[swf_array_length(EMPLOYEES)+1] := null;
 EMPLOYEES[swf_array_length(EMPLOYEES)] := row(ID,NAME);
    FOR i IN array_lower(EMPLOYEES,1) .. array_upper(EMPLOYEES,1)
    LOOP
       EMPLOYEES_REC := EMPLOYEES[i];
       INSERT INTO EMP_TAB  values(EMPLOYEES_REC.ID, EMPLOYEES_REC.NAME);
       EMPLOYEES[i] := EMPLOYEES_REC;
    END LOOP;
    INSERT INTO EMP_TAB  SELECT * FROM unnest(EMPLOYEES);
    for i in 1 .. swf_array_length(EMPLOYEES) loop
       NEW_EMPLOYEES[swf_array_length(NEW_EMPLOYEES)+1] := null;
    end loop;
    NEW_EMPLOYEES := array[]:: EMPLOYEE[];
 END; $$;

Чтобы лучше понять механизм преобразования, давайте рассмотрим преобразование каждого метода из примера по отдельности:

Преобразование типов данных таблицы

Сами табличные типы данных не преобразуются. Вместо них в коде будут использоваться массивы того типа, из которого состоит данный тип таблицы:

Oracle PostgreSQL
employees_tab EMPLOYEE[]

Объявление и инициализация переменных табличного типа

Вместо того чтобы инициализировать переменную в Oracle, в PostgreSQL мы заполняем ее пустым массивом заданного типа:

Oracle PostgreSQL
new_employees employees_tab := employees_tab(); NEW_EMPLOYEES EMPLOYEE[] default array[]::EMPLOYEE[];

Конвертация метода Count

Для преобразования метода Count была разработана дополнительная функция swf_array_length, которая будет автоматически сгенерирована и создана в базе данных PostgreSQL в процессе преобразования.

Oracle PostgreSQL
EMPLOYEES.count swf_array_length(EMPLOYEES)

Расширение преобразования методов

Вместо метода Extend мы просто добавляем пустой элемент в конец массива.

Oracle PostgreSQL
EMPLOYEES.Extend(1); EMPLOYEES[swf_array_length(EMPLOYEES)+1] := null;
EMPLOYEES.Extend(n); for i in 1 .. n loop EMPLOYEES[swf_array_length(EMPLOYEES) + 1] := null; end loop;

Преобразование методов Last и First

Для преобразования a.first и a.last используйте array_lower(a ,1) и array_upper(a,1) соответственно.

Oracle PostgreSQL
FOR i IN EMPLOYEES.first..EMPLOYEES.last FOR i IN array_lower(EMPLOYEES,1) .. array_upper(EMPLOYEES,1)

Инициализация элемента TYPE IS TABLE

Чтобы инициализировать элемент массива, мы используем конструктор row().

Oracle PostgreSQL
EMPLOYEES(EMPLOYEES.count) := employee(id, Name); EMPLOYEES[swf_array_length(EMPLOYEES)] := row(ID,NAME);

Преобразование метода удаления

Вместо того, чтобы полностью удалять все строки из переменной, мы просто заполняем переменную пустым массивом в PostgreSQL.

Oracle PostgreSQL
NEW_EMPLOYEES.Delete; NEW_EMPLOYEES := array[]:: EMPLOYEE[];

Преобразование функции Table()

Вместо функции TABLE, которая позволяет нам ссылаться на табличную переменную как на таблицу, в PostgreSQL используется функция UNNEST. Вместо псевдоколонки column_value — unnest в PostgreSQL.

Oracle PostgreSQL
SELECT * FROM TABLE(EMPLOYEES) SELECT * FROM unnest(EMPLOYEES)

Доступ полей к составному элементу TYPE IS TABLE

Oracle позволяет ссылаться на поле составного элемента массива по номеру элемента (EMPLOYEES( i ).id). Это невозможно сделать для PostgreSQL версии 13 и ниже. В этом случае для доступа к полю массива необходимо сначала поместить этот элемент в новую переменную, а затем обратиться к его полю:

Oracle PostgreSQL
EMPLOYEES(i).id DECLARE EMPLOYEES_REC EMPLOYEE; EMPLOYEES_REC := EMPLOYEES[i]; EMPLOYEES_REC.ID

Для версии PostgreSQL 14 и выше разрешен доступ к элементу по его индексу:

Oracle PostgreSQL
EMPLOYEES(i).id EMPLOYEES[i].id

Это решение не только позволяет эффективно конвертировать табличные типы, но и делает код гораздо более точным по сравнению с конвертацией с использованием временных таблиц. Также этот механизм можно использовать при преобразовании типов VARRAYS.

Разработчики, знакомые с работой табличных типов в Oracle и типов массивов в PostgreSQL, могли заметить, что между ними есть одно существенное различие — переменные табличного типа могут иметь пустые элементы в середине (пустые строки), в то время как в PostgreSQL все элементы массива должны идти по порядку. Этой проблемы можно избежать небольшим изменением логики в SQL-коде.
С помощью этого решения можно конвертировать не только такие простые случаи, но и более сложные, когда к элементу обращаются в массиве массивов и т. д.

Наш инструмент поддерживает преобразование табличных типов как с временными таблицами, так и с массивами (по умолчанию). Для того чтобы использовать решение с массивами, воспользуйтесь параметром TABLE_TYPE_CONVERSION=Arrays в разделе [POSTGRE] ini-файла (как установить параметры в Конвертум Мастере). В этом случае вместо типов будут использоваться массивы, а типы не будут создаваться в PostgreSQL. По умолчанию для этого параметра используется значение "Arrays". Пустые значения равны значению "Arrays". Если параметр имеет значение "Tables", то будут использоваться временные таблицы.


Если у вас есть другие вопросы, пожалуйста, свяжитесь с нами: support@convertum.ru