📥 Multi-table INSERT
INSERT ALL, INSERT FIRST, CREATE TABLE AS SELECT
- →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=0tworzy 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
| Wariant | Zachowanie przy dopasowaniu | Kiedy używać |
|---|---|---|
INSERT ALL bezwarunkowy | Wstawia do WSZYSTKICH tabel dla każdego wiersza | Replikacja danych do wielu tabel |
INSERT ALL warunkowy | Sprawdza każdy WHEN niezależnie (może wstawić do wielu) | Kategoryzacja, wiele kryteriów może być spełnionych naraz |
INSERT FIRST | Wstawia tylko do PIERWSZEJ pasującej tabeli | Wzajemnie wykluczające się kategorie |
INSERT ALL — bezwarunkowy (do wszystkich tabel)
-- 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)
-- 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;| TABELA | EMP_ID | SALARY | TRAFIŁ? |
|---|---|---|---|
| t_low (< 5000) | 101 | 5000 | NIE (5000 < 5000 = fałsz) |
| t_mid (≤ 5000) | 101 | 5000 | TAK (5000 ≤ 5000 = prawda) |
| t_high (> 5000) | 101 | 5000 | NIE (5000 > 5000 = fałsz) |
| TABELA | EMP_ID | SALARY | TRAFIŁ? |
|---|---|---|---|
| t_low (< 5000) | 101 | 5000 | NIE — sprawdzono, fałsz |
| t_mid (≤ 5000) | 101 | 5000 | TAK — STOP, reszta pominięta |
| t_high (> 5000) | 101 | 5000 | NIE — w ogóle nie sprawdzono |
INSERT FIRST — tylko pierwsza pasująca tabela
-- 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;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).
-- 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;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)
-- 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
-- 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.
-- 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;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
-- 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
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?
INSERT FIRST vs INSERT ALL — która jest poprawna?
CTAS (CREATE TABLE AS SELECT) kopiuje z tabeli źródłowej...
INSERT ALL
INTO t1 (id, name) VALUES (1, 'Alice')
INTO t1 (id, name) VALUES (2, 'Bob')
SELECT * FROM DUAL;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;Ć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.