Операторы BULK COLLECT / FORALL с динамическим запросом и именем таблицы - Oracle PL / SQL

Мне нужна помощь в оптимизации этого запроса для использования операторов массового сбора и сбора данных. Я создал резервные таблицы (BCK_xxxx) для копирования всех данных из исходных таблиц (ORIG_xxx), но у меня возникают проблемы с преобразованием их в массовый сбор. Большинство примеров, которые я видел в BULK collect, уже включают определение имени и структуры таблицы с использованием% rowtype. Однако у меня есть сотни таблиц для резервного копирования, поэтому мне нужно, чтобы мой запрос, в частности, имя таблицы было динамическим. Это мой исходный запрос, который вставляет / удаляет данные один за другим без массового сбора и занимает много времени:

DECLARE
--select all table names from backup tables (ex: BCK_tablename)
CURSOR cur_temp_tbl IS
    SELECT table_name
    FROM all_tables 
    WHERE OWNER = 'BCKUP'
    ORDER BY 1;  

--select all table names from original tables (ex: ORIG_tablename)
 CURSOR cur_original_tbl IS
    SELECT table_name
    FROM all_tables 
    WHERE OWNER = 'ORIG'
    ORDER BY 1;    
    l_tbl_nm VARCHAR2(30 CHAR);

BEGIN
    --first loop to delete all tables from backup
    FOR a IN cur_temp_tbl LOOP
       l_tbl_nm := a.table_name;                                    
       EXECUTE IMMEDIATE 'DELETE FROM '||  l_tbl_nm;
       l_deleted_cnt :=  l_deleted_cnt +1;            
    END LOOP;

    --second loop to insert data from original to backup        
    FOR b IN cur_original_tbl LOOP            
         l_tbl_nm := b.table_name;   
        CASE
          WHEN INSTR(l_tbl_nm,'ORIG_') > 0 THEN
          l_tbl_nm := REPLACE(l_tbl_nm,'ORIG_','BCK_');
          ELSE
           l_tbl_nm := 'BCK_' || l_tbl_nm;
        END CASE;  

        EXECUTE IMMEDIATE 'INSERT INTO '  || l_tbl_nm || ' SELECT * FROM ' || b.table_name;
        l_inserted_cnt :=  l_inserted_cnt +1;
    END LOOP; 

    dbms_output.put_line('Deleted/truncated tables from backup :' ||l_deleted_cnt);
    dbms_output.put_line('No of tables inserted with data from original to backup :' ||l_inserted_cnt);
EXCEPTION
 WHEN OTHERS THEN
 dbms_output.put_line(SQLERRM);
 dbms_output.put_line(l_tbl_nm);
END;

Я подумываю включить приведенный ниже код для добавления после второго цикла, но у меня проблемы с объявлением курсора cur_tbl и типа данных TABLE «l_tbl_data». Я не могу использовать rowtype, поскольку имя таблицы должно быть динамическим и будет меняться на каждой итерации моего второго цикла, в котором будут перечислены все имена таблиц из исходной таблицы:

TYPE CurTblTyp  IS REF CURSOR;
cur_tbl    CurTblTyp; 
TYPE l_tbl_t IS TABLE OF tablename.%ROWTYPE;
l_tbl_data l_tbl_t ;

OPEN cur_tbl FOR  'SELECT * FROM  :s ' USING b.table_name;
FETCH cur_tbl BULK COLLECT INTO l_tbl_data LIMIT 5000;
EXIT WHEN cur_tbl%NOTFOUND;     
CLOSE cur_tbl;         

FORALL i IN 1 .. l_tbl_data .count
EXECUTE IMMEDIATE 'insert into '||l_tbl_nm||' values (:1)' USING 
l_tbl_data(i);

Надеюсь, вы поможете мне и подскажете, как я могу сделать этот код намного проще. Большое спасибо.


person mariozelda    schedule 12.05.2017    source источник


Ответы (3)


Похоже, вы хотите удалить все строки из существующих резервных таблиц, а затем повторно скопировать все содержимое из исходных таблиц в резервные таблицы. Если это правильно, использование DELETE для удаления и любая операция цикла для вставки будет медленной.

Во-первых, чтобы удалить данные, используйте TRUNCATE. Поскольку вы собираетесь переселить, используйте параметр REUSE STORAGE. Это наиболее эффективный способ удалить все строки из таблицы.

TRUNCATE TABLE <backup table> REUSE STORAGE;

Во-вторых, для повторного заселения просто INSERT с SELECT.

INSERT INTO <backup table> SELECT * FROM <orig table>;

Вы можете использовать их в своих циклах, выполняя цикл за таблицей. Нет необходимости перемещать курсор по строкам таблицы, так как это будет быстрее.

Если у вас есть новая таблица, вы можете сделать что-то подобное с CTAS ...

CREATE TABLE <backup table> AS SELECT * FROM <orig_table>;
person unleashed    schedule 12.05.2017
comment
спасибо .. Я уже использовал ctas при создании таблиц резервных копий. Я знаком с использованием truncate, но не могу использовать его здесь, поскольку это оператор ddl (автоматическая фиксация). Я также создам процедуру, аналогичную этой, но она удалит данные из исходной таблицы и вставит записи из резервной таблицы ... использование усечения здесь довольно рискованно, если я не проверил / проверю, что все данные скопированы на резервная таблица. есть ли другой способ использовать удаление? или есть что-то вроде массового удаления без автоматической фиксации, например усечения? - person mariozelda; 12.05.2017
comment
Причина, по которой вы хотите использовать truncate, заключается в том, что вы не будете заполнять журнал повторов всеми удалениями. Да, вы действительно хотите быть осторожными, потому что нет отката от усечения. Но это, по сути, ваш выбор: удаление и усечение. - person unleashed; 12.05.2017

В дополнение к параметрам удаления и усечения существует третий вариант: это переименование / удаление. Вы переименовываете старые резервные копии таблиц, воссоздаете новые резервные копии (CTAS). Если создание - вставка прошло успешно, вы отбрасываете переименованные таблицы, если новая резервная копия терпит неудачу, вы переименовываете предыдущие старые резервные копии в исходные имена резервных копий. Вы в основном меняете временное использование дискового пространства на журналы повторного выполнения.

Вам не нужна массовая обработка, CTAS по-прежнему быстрее, чем массовая обработка.

person Belayer    schedule 13.05.2017

Вы использовали FORCE DELETE? Он был впервые представлен Oracle Master J.B.E. Он используется для удаления данных и игнорирует ограничение, которое может иметь таблица, и работает намного быстрее, чем другие операторы удаления.

FORCE DELETE FROM <table_name>;
person chupangga alolino    schedule 15.05.2017
comment
Это не похоже на правильный синтаксис. Вы можете предоставить какую-либо документацию по этому поводу? - person Jon Heller; 15.05.2017