🔧 ETL & Tabele Zewnętrzne

SQL*Loader, Data Pump, ORGANIZATION EXTERNAL

⚡ Kluczowe fakty do zapamiętania
  • ETL = Extract → Transform → Load. Najpierw pobierasz dane ze źródeł, potem czyścisz/przekształcasz, na końcu ładujesz do hurtowni. ELT (Cloud/Big Data) = odwrotna kolejność — załaduj surowe dane najpierw, przekształć potem w bazie.
  • Wykrywanie zmian (Change Detection): Snapshots = pełna kopia co N godzin (kosztowne). Activity Logs = czytaj redo log/binlog (efektywne, ale skomplikowane). Triggers/Difference Tables = trigger zapisuje zmiany do tabeli delta. Cooperative Sources = źródło ma kolumnę 'last_modified' lub flagę zmiany.
  • SQL*Loader dyrektywy ładowania: APPEND = dołącz wiersze (tabela może być niepusta). REPLACE = usuń wszystko (DELETE) + wstaw nowe. TRUNCATE = szybsze niż REPLACE (TRUNCATE zamiast DELETE). INSERT = tabela MUSI być pusta, inaczej błąd.
  • Pliki wyjściowe SQL*Loader: .log = log całej operacji (ile załadowano, ile odrzucono). .bad = wiersze odrzucone przez błędy danych lub naruszenie constraintów. .dsc (discard) = wiersze nie pasujące do żadnej klauzuli WHEN.
  • ORACLE_LOADER = tylko SELECT (read-only). Plik CSV jest widoczny jako tabela, ale nie możesz INSERT/UPDATE/DELETE. ORACLE_DATAPUMP = możesz zarówno pisać (CTAS tworzy plik) jak i czytać. Format binarny Oracle — nieczytelny bez Oracle.
  • Plik tabeli zewnętrznej musi być na SERWERZE BAZY DANYCH, nie na komputerze klienta. DIRECTORY to ścieżka na serwerze. Jeśli pliku nie ma → ORA-29913 przy SELECT.
  • Data Pump wymaga obiektu DIRECTORY — nie możesz podać bezpośredniej ścieżki jako stringa. Najpierw: CREATE DIRECTORY dump_dir AS '/ścieżka/', potem GRANT READ, WRITE ON DIRECTORY dump_dir TO użytkownik.

ETL — teoria

ETL = Extract → Transform → Load. Proces przenoszenia danych ze źródeł do hurtowni/docelowej bazy.

FazaCo robiKluczowe zagadnienia
ExtractPobierz dane ze źródełWybór źródeł, wykrywanie zmian
TransformOczyść, przekształć, wzbogaćDeduplikacja, ujednolicenie formatów, mapowanie semantyczne
LoadZaładuj do docelowej bazyPełne zastąpienie (full) vs przyrostowe (incremental)

Extract — wykrywanie zmian (Change Detection)

MetodaOpisWady
SnapshotsPełna kopia źródła — porównaj z poprzednią wersjąKosztowne dla dużych tabel
Activity logsCzytaj logi transakcyjne bazy (redo log, binlog)Format specyficzny dla DBMS
Difference tables / triggersTrigger zapisuje zmienione wiersze do tabeli deltaNarzut na DML; ingerencja w schemat źródła
Cooperative sourcesŹródło samo informuje o zmianach (timestamp, flaga)Wymaga współpracy z właścicielem źródła

Transform — czyszczenie i przekształcanie

KrokPrzykład
DeduplikacjaUsuń duplikaty wierszy (ROW_NUMBER + WHERE rn=1)
Normalizacja formatuUjednolicenie dat (DD-MM-YYYY → YYYY-MM-DD), kodowania
Mapowanie semantyczne"M"/"F" → "Male"/"Female"; kod kraju → nazwa
WzbogacanieLookup do tabeli referencyjnej, obliczenie pól pochodnych
WalidacjaOdrzucenie wierszy z brakującymi kluczami lub wartościami null

Load — typy ładowania

TypOpisKiedy
Full (pełne)TRUNCATE + INSERT — całkowite zastąpienie zawartości tabeliMała tabela lub brak możliwości wykrycia zmian
Incremental (przyrostowe)Dołącz tylko nowe/zmienione wiersze (INSERT/MERGE)Duże tabele, znane daty zmian

Porównanie narzędzi

NarzędzieKierunekFormat danychUżycie
SQL*LoaderPlik → OracleCSV, delimited, fixed-widthMasowe ładowanie z plików tekstowych
Data Pump EXPDPOracle → plikBinarny Oracle (.dmp)Eksport schematów/tabel/całej bazy
Data Pump IMPDPPlik → OracleBinarny Oracle (.dmp)Import z pliku EXPDP
Tabele zewnętrznePlik ↔ OracleCSV lub binarny OracleCzytaj/pisz pliki jak tabele SQL

SQL*Loader — plik kontrolny

SQL*Loader ładuje dane z pliku tekstowego do tabeli Oracle. Konfiguracja w pliku .ctl.

Plik kontrolny (.ctl)
LOAD DATA
INFILE 'pracownicy.csv'           -- plik z danymi
APPEND                            -- dołącz do istniejących wierszy
INTO TABLE hr.employees_staging
FIELDS TERMINATED BY ','          -- separator pól
OPTIONALLY ENCLOSED BY '"'        -- opcjonalne cudzysłowy
TRAILING NULLCOLS                 -- brakujące kolumny na końcu = NULL
(
    employee_id  INTEGER EXTERNAL,
    first_name   CHAR(50),
    last_name    CHAR(50),
    email        CHAR(100),
    salary       DECIMAL EXTERNAL
)

Dyrektywy LOAD DATA

DyrektywaZachowanie
INSERTTabela musi być pusta (domyślne)
APPENDDołącz do istniejących danych
REPLACEDELETE FROM tabela, potem INSERT
TRUNCATETRUNCATE TABLE, potem INSERT (szybsze niż REPLACE)

SQL*Loader — ładowanie pozycyjne (stała szerokość kolumn)

Gdy plik nie ma separatorów — kolumny mają stałą pozycję (POSITION).

Plik kontrolny — fixed-width
LOAD DATA
INFILE 'employees_fixed.dat'
BADFILE 'bad_rows.bad'          -- wiersze odrzucone → ten plik
APPEND
INTO TABLE hr.employees_staging
(
    employee_id  POSITION(1:6)   INTEGER EXTERNAL,
    first_name   POSITION(7:26)  CHAR,
    last_name    POSITION(27:46) CHAR,
    salary       POSITION(47:54) DECIMAL EXTERNAL
)

Rozszerzone opcje linii komend SQL*Loader

OpcjaOpis
control=Plik kontrolny (.ctl)
log=Plik logu operacji
bad=Plik z wierszami odrzuconymi (błędy formatu/constrainty)
data=Nadpisuje INFILE z pliku kontrolnego
discard=Plik z wierszami nie pasującymi do żadnego WHEN
discardmax=Maks. liczba odrzuconych wierszy zanim zatrzyma się ładowanie
skip=Pomiń N pierwszych wierszy pliku (np. nagłówek)
load=Załaduj maksymalnie N wierszy
rows=Liczba wierszy na jeden COMMIT (domyślnie 64)
direct=TRUEDirect Path Load — szybszy, omija redo log

Klauzula WHEN — ładowanie warunkowe

sql
LOAD DATA
INFILE 'dane.csv'
INTO TABLE high_salary_emp WHEN (salary > '10000')
FIELDS TERMINATED BY ','
(employee_id, first_name, salary)

INTO TABLE low_salary_emp WHEN (salary <= '10000')
FIELDS TERMINATED BY ','
(employee_id, first_name, salary)

Uruchomienie SQL*Loader z linii komend

Terminal / CMD
sqlldr userid=hr/password@orcl control=load.ctl log=load.log bad=bad.txt

-- Najważniejsze parametry:
-- control=  plik kontrolny
-- log=      plik z logiem operacji
-- bad=      wiersze odrzucone (błędy) → zapisane do pliku .bad
-- discard=  wiersze nie pasujące do żadnej klauzuli WHEN → plik .dsc
-- rows=     liczba wierszy na jeden COMMIT (domyślnie 64)
-- direct=   TRUE = Direct Path Load (szybszy, omija redo log)

SQL*Loader — tryby ładowania

TrybMetodaSzybkośćConstrainty
Conventional PathStandardowy INSERTWolniejszySprawdzane na bieżąco
Direct PathBezpośredni zapis na blokiSzybszySprawdzane po załadowaniu

Data Pump — EXPDP / IMPDP

Eksport
-- Eksport całego schematu HR:
expdp hr/password@orcl     DIRECTORY=DATA_PUMP_DIR     DUMPFILE=hr_backup.dmp     LOGFILE=expdp_hr.log     SCHEMAS=HR

-- Eksport konkretnych tabel:
expdp hr/password@orcl     DIRECTORY=DATA_PUMP_DIR     DUMPFILE=tables.dmp     TABLES=HR.EMPLOYEES,HR.DEPARTMENTS

-- Eksport całej bazy (wymaga DBA):
expdp system/password FULL=Y DUMPFILE=full_backup.dmp DIRECTORY=DATA_PUMP_DIR
Import
-- Import schematu:
impdp hr/password@orcl     DIRECTORY=DATA_PUMP_DIR     DUMPFILE=hr_backup.dmp     SCHEMAS=HR

-- Import z remapowaniem schematu (HR → HR_TEST):
impdp system/password     DIRECTORY=DATA_PUMP_DIR     DUMPFILE=hr_backup.dmp     REMAP_SCHEMA=HR:HR_TEST

-- Import tylko wybranych tabel:
impdp hr/password     DIRECTORY=DATA_PUMP_DIR     DUMPFILE=hr_backup.dmp     TABLES=HR.EMPLOYEES

DIRECTORY — obiekt Oracle dla ścieżki

sql
-- Data Pump wymaga obiektu DIRECTORY (nie bezpośredniej ścieżki):
CREATE DIRECTORY data_pump_dir AS '/oracle/backup/';
GRANT READ, WRITE ON DIRECTORY data_pump_dir TO hr;

-- Sprawdzenie istniejących direktorios:
SELECT directory_name, directory_path FROM dba_directories;

Tabele zewnętrzne — ORACLE_LOADER

Pozwala czytać pliki CSV jak tabele Oracle — bez ładowania danych do bazy. Dane "wirtualne".

sql
CREATE TABLE ext_employees (
    employee_id  NUMBER,
    first_name   VARCHAR2(50),
    last_name    VARCHAR2(50),
    salary       NUMBER
)
ORGANIZATION EXTERNAL (
    TYPE ORACLE_LOADER                   -- sterownik: czytanie pliku tekstowego
    DEFAULT DIRECTORY ext_data_dir       -- katalog z plikiem
    ACCESS PARAMETERS (
        RECORDS DELIMITED BY NEWLINE
        FIELDS TERMINATED BY ','
        OPTIONALLY ENCLOSED BY '"'
        MISSING FIELD VALUES ARE NULL
        (employee_id, first_name, last_name, salary)
    )
    LOCATION ('employees.csv')           -- nazwa pliku (w katalogu DIRECTORY)
)
REJECT LIMIT UNLIMITED;                 -- nie przerywaj przy błędach w wierszach

Tabela zewnętrzna — PARALLEL i opcje logowania

sql
CREATE TABLE ext_employees_parallel (
    employee_id  NUMBER,
    first_name   VARCHAR2(50),
    last_name    VARCHAR2(50),
    salary       NUMBER
)
ORGANIZATION EXTERNAL (
    TYPE ORACLE_LOADER
    DEFAULT DIRECTORY ext_data_dir
    ACCESS PARAMETERS (
        RECORDS DELIMITED BY NEWLINE
        NOBADFILE                         -- nie twórz pliku .bad (domyślnie tworzony)
        NOLOGFILE                         -- nie twórz pliku logu (domyślnie tworzony)
        FIELDS TERMINATED BY ','
        OPTIONALLY ENCLOSED BY '"'
        MISSING FIELD VALUES ARE NULL
        (employee_id, first_name, last_name, salary)
    )
    LOCATION ('employees.csv')
)
PARALLEL 4                               -- czytaj plik równolegle (4 procesy)
REJECT LIMIT UNLIMITED;

ALTER TABLE — modyfikacja tabeli zewnętrznej

Tabelę zewnętrzną można modyfikować bez usuwania i tworzenia od nowa.

sql
-- Zmiana ACCESS PARAMETERS (nowy separator):
ALTER TABLE ext_employees ACCESS PARAMETERS (
    RECORDS DELIMITED BY NEWLINE
    FIELDS TERMINATED BY ';'
    MISSING FIELD VALUES ARE NULL
    (employee_id, first_name, last_name, salary)
);

-- Zmiana domyślnego katalogu:
ALTER TABLE ext_employees DEFAULT DIRECTORY new_data_dir;

-- Zmiana pliku źródłowego:
ALTER TABLE ext_employees LOCATION ('employees_2024.csv');

-- Zmiana katalogu i pliku razem:
ALTER TABLE ext_employees DEFAULT DIRECTORY new_dir
    LOCATION ('new_file.csv');

-- Dodanie kolumny (jak zwykła ALTER TABLE):
ALTER TABLE ext_employees ADD (department_id NUMBER);

-- Modyfikacja kolumny:
ALTER TABLE ext_employees MODIFY (first_name VARCHAR2(100));

-- Usunięcie kolumny:
ALTER TABLE ext_employees DROP COLUMN department_id;
⚠️
ALTER TABLE dla tabeli zewnętrznej — ograniczenia
Nie można ADD CONSTRAINT (PK, FK, NOT NULL) do tabeli zewnętrznej.
Nie można UPDATE/INSERT/DELETE — tabela zewnętrzna ORACLE_LOADER jest tylko do odczytu.
Plik musi być na serwerze bazy danych — nie na komputerze klienta.

Tabele zewnętrzne — ORACLE_DATAPUMP

Pozwala zapisywać wyniki zapytań do binarnych plików Oracle i czytać je jak tabelę. Przenaszalne między bazami.

sql
-- Zapis do pliku binarnego:
CREATE TABLE ext_emp_dump
ORGANIZATION EXTERNAL (
    TYPE ORACLE_DATAPUMP
    DEFAULT DIRECTORY ext_data_dir
    LOCATION ('emp_dump.dmp')
)
AS SELECT employee_id, first_name, last_name, salary
   FROM hr.employees;

-- Odczyt z pliku:
SELECT * FROM ext_emp_dump WHERE salary > 5000;
ORACLE_LOADER vs ORACLE_DATAPUMP
ORACLE_LOADER — czyta CSV/tekstowe (tylko SELECT, nie INSERT)
ORACLE_DATAPUMP — binarny format Oracle, można zarówno pisać (CTAS) jak i czytać
Tabele zewnętrzne są read-only dla zapytań SQL — nie możesz INSERT/UPDATE/DELETE do pliku przez ORACLE_LOADER.
🚨
Tabele zewnętrzne — plik musi istnieć!
Oracle nie tworzy pliku automatycznie przy CREATE dla ORACLE_LOADER. Jeśli plik z LOCATION nie istnieje na serwerze — zapytanie SELECT zakończy się błędem ORA-29913. Plik musi być dostępny na serwerze bazy danych (nie na kliencie!).

Pytania egzaminacyjne

🔤
Pytanie ABCD

Do pliku .bad (BADFILE) w SQL*Loader trafiają wiersze...

🔤
Pytanie ABCD

Co się stanie gdy użyjesz SQL*Loader z dyrektywą INSERT a tabela ma już dane?

🔤
Pytanie ABCD

Tabela zewnętrzna z ORACLE_LOADER — które operacje są dozwolone?

🔤
Pytanie ABCD

Dlaczego Data Pump wymaga obiektu DIRECTORY zamiast bezpośredniej ścieżki?

🔍 Czy to się wykona?
INSERT INTO ext_employees (employee_id, first_name)
VALUES (999, 'Test');
🔎 Znajdź błąd — wskaż niepoprawną linię
1expdp hr/password
2 DUMPFILE=backup.dmp
3 SCHEMAS=HR
4 LOGFILE=export.log

Ćwiczenia

💪

Ćwiczenie 1 — Plik kontrolny SQL*Loader

Napisz plik kontrolny SQL*Loader do załadowania pliku departments.csv (pola: department_id, department_name, location_id oddzielone przecinkami) do tabeli departments_staging. Użyj APPEND, obsługę cudzysłowów i brakujących kolumn.

💪

Ćwiczenie 2 — Tabela zewnętrzna ORACLE_LOADER

Utwórz tabelę zewnętrzną ext_departments odczytującą plik departments.csv (department_id NUMBER, department_name VARCHAR2(100), location_id NUMBER). Plik jest w katalogu EXT_DIR. Ustaw REJECT LIMIT UNLIMITED.

💪

Ćwiczenie 3 — Data Pump: eksport i import schematu

Napisz polecenia: (1) utwórz obiekt DIRECTORY wskazujący na /oracle/dumps/, (2) wyeksportuj schemat HR do pliku hr_export.dmp, (3) zaimportuj go do schematu HR_BACKUP (remap).