📥 Multi-table INSERT

INSERT ALL, INSERT FIRST, CREATE TABLE AS SELECT

⚡ Kluczowe fakty do zapamiętania
  • INSERT ALL bezwarunkowy = każdy wiersz z SELECT trafia do WSZYSTKICH wymienionych tabel. Jak replikacja: jeden rekord pracownika → 3 tabele jednocześnie. Szybszy niż trzy osobne INSERT.
  • INSERT ALL warunkowy (WHEN) = każdy warunek jest sprawdzany NIEZALEŻNIE dla każdego wiersza. Jeden pracownik może trafić do WIELU tabel jeśli spełnia wiele warunków. Pułapka: jeśli salary=5000 i granica to 5000, może trafić do dwóch tabel!
  • INSERT FIRST = jak if-else. Wiersz trafia do PIERWSZEJ pasującej tabeli i już. Reszta warunków jest pomijana. Używaj gdy chcesz wzajemnie wykluczające się kategorie.
  • INSERT ALL ... SELECT * FROM DUAL = sposób na wstawienie wielu literałów bez tabeli źródłowej. DUAL zwraca 1 wiersz, więc każde INTO wykona się dokładnie raz.
  • CTAS (CREATE TABLE AS SELECT) = tworzy tabelę I wstawia dane w jednej operacji. Kopiuje strukturę kolumn i dane, ale NIE kopiuje constraintów (PK, FK, CHECK). WHERE 1=0 tworzy pustą kopię struktury.
  • WITH nazwa AS (SELECT ...) = CTE (Common Table Expression) = tymczasowa nazwa dla podzapytania. Można użyć wielokrotnie w jednym zapytaniu. Czytelniejszy i wydajniejszy zamiennik zagnieżdżonych podzapytań.

Porównanie wariantów INSERT

WariantZachowanie przy dopasowaniuKiedy używać
INSERT ALL bezwarunkowyWstawia do WSZYSTKICH tabel dla każdego wierszaReplikacja danych do wielu tabel
INSERT ALL warunkowySprawdza każdy WHEN niezależnie (może wstawić do wielu)Kategoryzacja, wiele kryteriów może być spełnionych naraz
INSERT FIRSTWstawia tylko do PIERWSZEJ pasującej tabeliWzajemnie wykluczające się kategorie

INSERT ALL — bezwarunkowy (do wszystkich tabel)

sql
-- Każdy wiersz z SELECT trafia do OBU tabel:
INSERT ALL
    INTO employees_backup VALUES (employee_id, first_name, last_name, salary)
    INTO employees_archive VALUES (employee_id, hire_date, department_id)
SELECT employee_id, first_name, last_name, salary, hire_date, department_id
FROM hr.employees;

INSERT ALL — warunkowy (WHEN)

sql
-- Pracownicy rozdzieleni do tabel wg pensji — UWAGA: jeśli salary = 5000,
-- wiersz może trafić do OBU tabel (warunki są niezależne)!
INSERT ALL
    WHEN salary < 5000 THEN
        INTO low_salary_emp VALUES (employee_id, first_name, salary)
    WHEN salary BETWEEN 5000 AND 10000 THEN
        INTO mid_salary_emp VALUES (employee_id, first_name, salary)
    WHEN salary > 10000 THEN
        INTO high_salary_emp VALUES (employee_id, first_name, salary)
    ELSE
        INTO other_emp VALUES (employee_id, first_name, salary)
SELECT employee_id, first_name, salary
FROM hr.employees;
⚠️
INSERT ALL — warunki nie są wzajemnie wykluczające!
Każdy WHEN jest sprawdzany niezależnie. Jeśli wiersz spełnia dwa warunki, trafi do obu tabel. Użyj INSERT FIRST gdy chcesz wzajemnie wykluczające się kategorie.
⚖️ Porównanie wynikówINSERT ALL (warunkowy) vs INSERT FIRST — salary=5000, granica WHEN < 5000 / WHEN <= 5000
INSERT ALL — każdy WHEN niezależny
TABELAEMP_IDSALARYTRAFIŁ?
t_low (< 5000)1015000NIE (5000 < 5000 = fałsz)
t_mid (≤ 5000)1015000TAK (5000 ≤ 5000 = prawda)
t_high (> 5000)1015000NIE (5000 > 5000 = fałsz)
INSERT FIRST — zatrzymaj się na pierwszym pasującym
TABELAEMP_IDSALARYTRAFIŁ?
t_low (< 5000)1015000NIE — sprawdzono, fałsz
t_mid (≤ 5000)1015000TAK — STOP, reszta pominięta
t_high (> 5000)1015000NIE — w ogóle nie sprawdzono
💡 Tu wynik jest TAKI SAM (oba: tylko t_mid). Ale gdyby warunki się nakładały (np. WHEN salary > 3000 i WHEN salary > 5000), INSERT ALL wstawiłby do obu, INSERT FIRST tylko do pierwszego pasującego.

INSERT FIRST — tylko pierwsza pasująca tabela

sql
-- Wiersz trafia tylko do PIERWSZEJ tabeli z pasującym WHEN:
INSERT FIRST
    WHEN salary > 10000 THEN
        INTO high_earners VALUES (employee_id, first_name, salary)
    WHEN salary > 5000 THEN
        INTO mid_earners VALUES (employee_id, first_name, salary)  -- NIE dostanie tych > 10000
    ELSE
        INTO low_earners VALUES (employee_id, first_name, salary)
SELECT employee_id, first_name, salary
FROM hr.employees;
Kiedy INSERT FIRST, kiedy INSERT ALL?
INSERT FIRST = jak if-else if-else (jeden wynik)
INSERT ALL warunkowy = jak if-if-if (można trafić do wielu tabel)
INSERT ALL bezwarunkowy = kopiuj do wszystkich, zawsze

CREATE TABLE AS SELECT (CTAS)

Tworzy nową tabelę i wypełnia ją danymi z SELECT w jednej operacji. Kopiuje strukturę kolumn i dane, ale NIE kopiuje constraintów (PK, FK, NOT NULL nie jest przenoszony).

sql
-- Kopia tabeli z danymi:
CREATE TABLE employees_backup AS
    SELECT * FROM hr.employees;

-- Pusta kopia struktury (WHERE 1=0 = zawsze fałsz → 0 wierszy):
CREATE TABLE employees_empty AS
    SELECT * FROM hr.employees WHERE 1 = 0;

-- Tabela z przetransformowanymi danymi:
CREATE TABLE emp_summary AS
    SELECT d.department_name,
           COUNT(*) AS liczba_prac,
           AVG(e.salary) AS srednia_pensja
    FROM hr.employees e
    JOIN hr.departments d ON e.department_id = d.department_id
    GROUP BY d.department_name;

-- Z NOLOGGING (szybsze, bez redo log, tylko w trybie DBA):
CREATE TABLE big_table NOLOGGING AS
    SELECT * FROM source_table;
🚨
CTAS nie kopiuje constraintów!
CREATE TABLE backup AS SELECT * FROM employees — nowa tabela NIE ma PRIMARY KEY ani NOT NULL (poza NOT NULL na kolumnach nieNULLable — te są zachowane).
Musisz ręcznie dodać: ALTER TABLE backup ADD PRIMARY KEY (employee_id);

INSERT ALL — transformacja kolumn na wiersze (pivot → unpivot)

sql
-- Tabela zawiera 3 kolumny kwartałów — INSERT ALL "rozkłada" je na wiersze:
-- Zakładamy: tabela roczna_sprzedaz (produkt_id, q1, q2, q3, q4)
INSERT ALL
    INTO sprzedaz_kwartalna (produkt_id, kwartal, kwota) VALUES (produkt_id, 1, q1)
    INTO sprzedaz_kwartalna (produkt_id, kwartal, kwota) VALUES (produkt_id, 2, q2)
    INTO sprzedaz_kwartalna (produkt_id, kwartal, kwota) VALUES (produkt_id, 3, q3)
    INTO sprzedaz_kwartalna (produkt_id, kwartal, kwota) VALUES (produkt_id, 4, q4)
SELECT produkt_id, q1, q2, q3, q4
FROM roczna_sprzedaz;

-- Każdy wiersz źródłowy → 4 wiersze docelowe (jeden per kwartał)

INSERT ALL z DUAL — wstawianie literałów bez tabeli źródłowej

sql
-- Wstaw wiele wierszy literal w jednym poleceniu:
INSERT ALL
    INTO departments (department_id, department_name, location_id) VALUES (280, 'HR', 1700)
    INTO departments (department_id, department_name, location_id) VALUES (290, 'Marketing', 1800)
    INTO departments (department_id, department_name, location_id) VALUES (300, 'IT', 1900)
SELECT * FROM DUAL;
-- DUAL zwraca dokładnie 1 wiersz → każda klauzula INTO wykona się raz

COMMIT;

WITH — Common Table Expressions (CTE)

Klauzula WITH definiuje tymczasowe nazwy dla podzapytań — czytelniejsza alternatywa dla zagnieżdżonych podzapytań. Można odwołać się wielokrotnie do tej samej CTE.

sql
-- Podstawowa składnia WITH:
WITH nazwa_cte AS (
    SELECT ...
    FROM ...
    WHERE ...
)
SELECT *
FROM nazwa_cte
WHERE ...;

-- Wielokrotne CTE (rozdzielone przecinkami):
WITH
    dept_stats AS (
        SELECT department_id,
               AVG(salary) AS avg_sal,
               MAX(salary) AS max_sal
        FROM hr.employees
        GROUP BY department_id
    ),
    high_depts AS (
        SELECT department_id
        FROM dept_stats
        WHERE avg_sal > 8000
    )
SELECT e.last_name, e.salary, e.department_id
FROM hr.employees e
JOIN high_depts h ON e.department_id = h.department_id;

-- CTE jako źródło w INSERT:
WITH new_emps AS (
    SELECT employee_id, first_name, last_name, salary
    FROM hr.employees_staging
    WHERE hire_date > SYSDATE - 30
)
INSERT INTO hr.employees_recent
SELECT * FROM new_emps;
WITH vs podzapytanie
WITH jest równoważne podzapytaniu w FROM, ale:
1. Można go użyć wiele razy w tym samym SELECT bez duplikowania kodu.
2. Oracle może zmaterializować wynik CTE (obliczyć raz, użyć wiele razy).
3. Kod jest czytelniejszy — CTE ma nazwę opisującą intencję.

Praktyczny wzorzec — staging → target

sql
-- Typowy ETL: wczytaj do staging, potem INSERT do tabel docelowych
CREATE TABLE staging_orders AS SELECT * FROM raw_orders WHERE 1=0;

-- Załaduj dane do staging...

-- Rozdziel do tabel wg regionu:
INSERT ALL
    WHEN region_id = 1 THEN INTO orders_europe VALUES (order_id, customer_id, amount)
    WHEN region_id = 2 THEN INTO orders_americas VALUES (order_id, customer_id, amount)
    ELSE INTO orders_other VALUES (order_id, customer_id, amount)
SELECT order_id, customer_id, amount, region_id
FROM staging_orders;

COMMIT;

Pytania egzaminacyjne

🔤
Pytanie ABCD

Pracownik ma salary = 5000. Zapytanie: INSERT ALL WHEN salary < 5000 THEN INTO t1 WHEN salary <= 5000 THEN INTO t2 ELSE INTO t3 SELECT ... Do której tabeli trafi?

🔤
Pytanie ABCD

INSERT FIRST vs INSERT ALL — która jest poprawna?

🔤
Pytanie ABCD

CTAS (CREATE TABLE AS SELECT) kopiuje z tabeli źródłowej...

🔍 Czy to się wykona?
INSERT ALL
    INTO t1 (id, name) VALUES (1, 'Alice')
    INTO t1 (id, name) VALUES (2, 'Bob')
SELECT * FROM DUAL;
🔍 Czy to się wykona?
INSERT FIRST
    WHEN salary > 10000 THEN INTO high VALUES (employee_id, salary)
    WHEN salary > 5000  THEN INTO mid  VALUES (employee_id, salary)
    WHEN salary > 10000 THEN INTO high VALUES (employee_id, salary)
SELECT employee_id, salary FROM hr.employees;
🔎 Znajdź błąd — wskaż niepoprawną linię
1INSERT ALL
2 WHEN salary > 10000 THEN INTO high_sal (employee_id, salary) VALUES (employee_id, salary)
3 WHEN salary > 5000 THEN INTO mid_sal (employee_id, salary) VALUES (employee_id, salary)
4 ELSE INTO low_sal (employee_id, salary) VALUES (employee_id, salary);

Ćwiczenia

💪

Ćwiczenie 1 — INSERT ALL bezwarunkowy

Utwórz dwie tabele: emp_names (employee_id, first_name, last_name) i emp_salaries (employee_id, salary, department_id). Użyj INSERT ALL, aby wstawić dane dla wszystkich pracowników do obu tabel jednocześnie.

💪

Ćwiczenie 2 — INSERT FIRST: kategoryzacja pensji

Utwórz 3 tabele dla pracowników wg pensji: high_earners (>10000), mid_earners (5000-10000), low_earners (<5000). Użyj INSERT FIRST, aby każdy pracownik trafił do dokładnie jednej tabeli.

💪

Ćwiczenie 3 — CTAS: tabela podsumowująca

Utwórz tabelę dept_stats zawierającą: department_name, liczba pracowników, średnią pensję (zaokrągloną do 2 miejsc), minimalną i maksymalną pensję. Użyj CTAS z JOIN i GROUP BY.