Ładowanie dużych wolumenów danych do bazy danych to jedno z wyzwań dla programistów Oracle. Procesy ładujące w PL/SQL są łatwe do zarządzania i analizy ewentualnych błędów. Jednak - nawet dobrze napisane nie są tak szybkie jak czysty DML.
DML faktycznie jest szybki. Jednak ma też swoje bolączki. Jeśli ładujemy tysiące danych a jeden rekord spowoduje błąd to standardowo wszystko zostanie wycofane, nawet poprawne rekordy nie zostaną zapisane. Co gorsza - wprawdzie dostaniemy informację o błędzie, który przerwał nasz proces, niemniej nie będziemy wiedzieli, który rekord spowodował ten błąd. A przy wolumenach danych idących w setki tysięcy czy nawet miliony rekordów - znalezienie właściwego graniczy z cudem.
Nie mówiąc już o wydajności - być może czekaliśmy kilka godziny na załadowanie danych. Jeśli wystąpił błąd - musimy czekać kolejne godziny na wycofanie zmian!
Z tego też powodu często zaleca się jednak ładowanie danych za pomocą funkcji PL/SQL, gdzie w przypadku błędu dla jednego rekordu nie tracimy zapisu pozostałych (o ile tak sobie napiszemy :D) .
Ale spójrzmy na przykład.
Korzystam ze standardowego schematu HR (jak odblokować schemat HR).
Na potrzeby testu tworzę pustą tabelę myEmp, jako kopię tabeli Employees.
Następnie zmniejszę długość kolumny last_name na 9 znaków (w tabeli employees są 4 rekordy z last_name > 9) by wymusić podczas ładowania danych błąd.
-- create test empty table
create table myEmp as select * from employees where 1 = 2;
-- change column length to induce error (max lenght of the last_name column is 11)
alter table myEmp modify (last_name varchar2(9));
W tabeli źródłowej mamy 107 rekordów ( w moim przykładzie), z czego spodziewamy się 4 błędów.
select case when length(last_name ) <= 9 then ' ok rows' else ' errors' end typ, count(*)
from employees
group by case when length(last_name ) <= 9 then ' ok rows' else ' errors' end;
TYP COUNT(*)
-------- ----------
errors 4
ok rows 103
Spróbujmy załadować teraz dane:
-- load employee data - we expect some errors
insert into myEmp select * from employees;
Error starting at line : 11 in command -
insert into myEmp select * from employees
Error report -
ORA-12899: wartość zbyt duża dla kolumny "HR"."MYEMP"."LAST_NAME" (obecna: 10, maksymalna: 9)
select * from myEmp;
no rows selected
Ładowanie nie powiodło się. Do tabeli myEmp nie został zapisany ani jeden rekord.
Mamy oczywiście informację o błędzie:
ORA-12899: wartość zbyt duża dla kolumny "HR"."MYEMP"."LAST_NAME" (obecna: 10, maksymalna: 9)
lecz nie wiemy, którego rekordu błąd ten dotyczy! Szukanie takiego rekordu to, szczególnie w dużych tabelach, jak szukanie igły w stogu siana.
Czy w takim razie faktycznie jesteśmy skazani na powolny PL/SQL?
Na szczęście nie!
Dla DML został stworzony od wersji 10 mechanizm logowania: LOG ERROR.
Dzięki tej funkcjonalności nie dość, że dostaniemy pełną informację o wystąpieniu błędu to jeszcze pozostałe rekordy zostaną zapisane!
Przyjrzymy się składni:
LOG ERRORS [INTO [schema.]table] [('simple_expression')] [REJECT LIMIT integer|UNLIMITED]
TABLE
opcjonalne, nazwa tabeli logu. Jeśli nie podamy tego parametru - błędy zostaną zapisane do tabeli na której wykonywany był DML z przedrostkiem ERR$_
SIMPLE_EXPRESSION
tag, którym możemy oznaczyć dany proces DML, na przykład datę ładowania. Pamiętajmy, że tak jest typu varchar2(2000).
REJECT LIMIT
maksymalna ilość zalogowanych błędów zanim proces się przerwie. UNLIMITED - bez limitu.
Żeby móc skorzystać z LOG ERRORS musimy utworzyć tabelę, do której będziemy zapisywać błędy. Tabelę można utworzyć korzystając z procedury
-- create table for errors
-- syntax
DBMS_ERRLOG.CREATE_ERROR_LOG (
dml_table_name IN VARCHAR2, --orginal table name
err_log_table_name IN VARCHAR2 := NULL, -- err_log table name, default ERR$_<orginal_table_name>
err_log_table_owner IN VARCHAR2 := NULL, -- err_log table owner, default orginal table schema owner
err_log_table_space IN VARCHAR2 := NULL, -- err_log tablespace, default orginal table tablespace
skip_unsupported IN BOOLEAN := FALSE); -- TRUE column not supported will be skipped, FALSE - unsupported column type will cause the procedure to terminate (LONG, CLOB, BLOB, BFILE, ADT)
/
Najczęściej wywołamy po prostu procedurę z parametrem nazwy tabeli, do której tworzymy log.
-- Create the error logging table.
BEGIN
DBMS_ERRLOG.create_error_log (dml_table_name => 'myEmp');
END;
/
Tabela logów utworzona w ten sposób będzie miała nazwę ERR$_myEmp.
Załadujmy dane do tabeli myEmp z wykorzystaniem LOG ERRORS.
insert into myEmp
select * from employees
LOG ERRORS ('INS:' || to_char(sysdate,'ddmmyyyy hh24miss')) REJECT LIMIT UNLIMITED;
103 rows inserted.
Mamy 103 rekordy! Ale co z czterema błędnymi rekordami? Zostały zalogowane do tabeli błędów ERR$_myEmp. Mamy tam informację o tagu, błędzie oraz cały błędny rekord.
Podsumowując:
Jeśli potrzebujemy załadować i przetworzyć dane do systemu - najlepiej do tego nadadzą się funkcje ładujące napisane w PL/SQL z użyciem BULK COLLECT.
Jeśli zaś ładujemy dane i nie chcemy ich konwertować (może troszeczkę) ani przetwarzać (w tym momencie) to warto zastanowić się właśnie nad DML wraz z LOG ERRORS. Zaoszczędzi nam to sporo czasu a nie tracimy przy tym szczegółowych informacji o ewentualnych błędach.
Komentarze
Prześlij komentarz