And look, basic INSERT, UPDATE, and DELETE are cute. They’re the training wheels of SQL. But at some point you stop gently adding rows and start doing things like loading three tables at once, rewriting history, and bringing dropped tables back from the dead like a mildly confused necromancer.
In this lesson you will:
- Use explicit
DEFAULTvalues inINSERTandUPDATEstatements. - Describe the features of multi-table inserts.
- Use these types of multi-table inserts:
- unconditional
INSERT ALL - conditional
INSERT ALL - conditional
INSERT FIRST - pivoting inserts
- Merge rows in a table with
MERGE. - Perform flashback operations.
- Track changes made to data over a period of time.
1. Explicit DEFAULT Values in INSERT and UPDATE
And look, defaults in tables are like emergency snacks in your desk drawer: they’re great, but only if you actually use them on purpose.
Normally, a column’s default value only appears when you omit that column. With the DEFAULT keyword you can say, “no, seriously, use the default here.”
- You can use
DEFAULTanywhere a normal value would go. - If the column has a default defined, Oracle uses it.
- If it doesn’t, Oracle cheerfully gives you
NULL.
Example INSERT:
INSERT INTO deptm3 (department_id, department_name, manager_id)
VALUES (300, 'Engineering', DEFAULT);
department_id= 300department_name= 'Engineering'manager_id= its default, orNULLif there isn’t one
Example UPDATE:
UPDATE deptm3
SET manager_id = DEFAULT
WHERE department_id = 10;
If manager_id has no default in the table definition, this effectively sets it to NULL. So DEFAULT is either a well-defined rule or a very fancy way of writing “I dunno, make it empty.”
2. Multi-Table Inserts: One SELECT, Many Targets
Multi-table inserts are Oracle’s way of saying, “What if a single query could explode into several tables at once?” It’s very efficient and very dangerous if you weren’t paying attention.
They are heavily used in data warehousing to:
- Pull data from one or more source tables.
- Push it into several target tables in one DML statement.
- Avoid writing 14 separate
INSERTstatements or a proceduralIF/THENjungle.
You’ll see four flavors:
- Unconditional
INSERT ALL - Conditional
INSERT ALL - Conditional
INSERT FIRST - Pivoting inserts
2.1 Unconditional INSERT ALL (copy everything everywhere)
In the e‑commerce example, we have three customers—Tom, Dick, and Harry—each with their own order table. A single purchase row should be copied into all three tables.
This is an unconditional multi-table insert, also called INSERT ALL:
INSERT ALL
INTO sal_history (employee_id, hire_date, salary, manager_id)
INTO mgr_history (employee_id, hire_date, salary, manager_id)
SELECT employee_id, hire_date, salary, manager_id
FROM employees
WHERE employee_id > 200;
- If the
SELECTreturns 6 rows and there are 2 targets, you get 12 inserts. - With 3 targets and 100 rows, you’d get 300 rows inserted.
Every target table gets every row. No conditions. No nuance. Pure duplication.
2.2 Conditional INSERT ALL (rows can hit multiple tables)
Now we add conditions. Each INTO clause can have a WHEN condition, which means a row may go into:
- zero tables,
- one table, or
- several tables.
Pattern:
INSERT ALL
WHEN hire_date < DATE '2015-01-01' THEN
INTO emp_history (emp_id, hire_date, salary)
VALUES (empno, hdate, sal)
WHEN commission_pct IS NOT NULL THEN
INTO emp_sales (emp_id, salary, commission_pct)
VALUES (empno, sal, comm)
SELECT employee_id AS empno,
hire_date AS hdate,
salary AS sal,
commission_pct AS comm
FROM employees;
Key detail: all matching conditions fire.
- If an employee was hired before 2015 and has a commission, that row goes into both
emp_historyandemp_sales. - In the demo, 25 source rows turned into 49 inserted rows, because some were inserted into multiple tables.
So INSERT ALL really means: “insert into all the tables whose conditions you match, possibly more than once.”
2.3 Conditional INSERT FIRST (each row finds exactly one home)
Now we change the rules: each row should pick one target, based on the first condition it satisfies.
That’s INSERT FIRST.
Example: bucket salaries into low, mid, and high tables:
INSERT FIRST
WHEN salary < 5000 THEN
INTO sal_low (employee_id, salary) VALUES (empno, sal)
WHEN salary BETWEEN 5000 AND 10000 THEN
INTO sal_mid (employee_id, salary) VALUES (empno, sal)
ELSE
INTO sal_high (employee_id, salary) VALUES (empno, sal)
SELECT employee_id AS empno,
salary AS sal
FROM employees;
- Oracle checks the
WHENclauses in order. - The first one that matches wins; the row goes only into that target.
- The demo’s
SELECTreturned 107 rows, and—this time—107 rows were inserted total.
This is basically salary sorting-hat logic: every row gets exactly one house.
2.4 Pivoting Inserts (columns → rows)
Now we get weird in a fun way: we take one row that has many columns, and turn it into many rows in the target.
Imagine a source table with weekly sales summarized per day:
WEEK_ID MON TUE WED THU FRI
------- --- --- --- --- ---
1 10 15 12 20 18
We want a target with one row per day:
WEEK_ID DAY_NAME QTY_SOLD
------- -------- --------
1 MON 10
1 TUE 15
...
Pivoting insert:
INSERT ALL
INTO emp_sales_info (week_id, day_name, qty_sold)
VALUES (week_id, 'MON', mon)
INTO emp_sales_info (week_id, day_name, qty_sold)
VALUES (week_id, 'TUE', tue)
INTO emp_sales_info (week_id, day_name, qty_sold)
VALUES (week_id, 'WED', wed)
INTO emp_sales_info (week_id, day_name, qty_sold)
VALUES (week_id, 'THU', thu)
INTO emp_sales_info (week_id, day_name, qty_sold)
VALUES (week_id, 'FRI', fri)
SELECT week_id,
mon, tue, wed, thu, fri
FROM sales_week_data;
- One input row becomes five output rows.
- Now aggregations like “total sales per week” become easy:
SELECT week_id,
SUM(qty_sold) AS week_total
FROM emp_sales_info
GROUP BY week_id;
This is Oracle politely fixing a non-normalized design while pretending nothing is wrong.
3. MERGE: Conditional Update / Insert / Delete (Upsert on steroids)
The MERGE statement is what you use when you’re tired of writing “check if it exists, then update, otherwise insert” logic in three different places.
- If a row matches (based on a join condition), you can
UPDATEit (and optionallyDELETEit). - If it doesn’t match, you can
INSERTit.
Pattern:
MERGE INTO emp_hist h
USING employees e
ON (h.employee_id = e.employee_id)
WHEN MATCHED THEN
UPDATE SET
h.first_name = e.first_name,
h.last_name = e.last_name,
h.email = e.email
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, email)
VALUES (e.employee_id, e.first_name, e.last_name, e.email);
In the demo:
EMP_HISTalready had 107 employees but with some missing/old data.- They manually broke some rows (e.g., mangled Jennifer Whalen’s email, removed Eleni Zlotkey).
- Running
MERGE: - Updated existing rows to match
employees. - Inserted missing rows like Eleni.
- Reported “107 rows merged,” syncing history with the current truth.
You can also add a DELETE clause inside WHEN MATCHED to remove rows matching some condition (e.g., anyone with a commission), turning MERGE into the DML equivalent of “fix this table or get rid of it.”
4. Flashback Table: Undo for Grown-Ups
And look, everyone eventually drops the wrong table. Oracle’s flashback features exist because that moment should be embarrassing, not career-ending.
FLASHBACK TABLE lets you:
- Recover a dropped table from the Recycle Bin.
- Restore a table to a previous point in time or SCN.
- Bring back data, indexes, and constraints in one go.
Example: recovering a dropped table
-- Table is created
CREATE TABLE emp2 AS SELECT * FROM employees WHERE 1 = 0;
-- Table is dropped (it goes to the Recycle Bin)
DROP TABLE emp2;
-- Recover it
FLASHBACK TABLE emp2 TO BEFORE DROP;
After flashback:
- The table structure (and data, if it had any) is back.
- The Recycle Bin entry disappears.
This is essentially “Control+Z for tables,” as long as you didn’t purge the bin.
5. Tracking Data Changes Over Time
Sometimes you don’t want to revert the table; you just want to peek into the past and see how bad your decisions were.
Oracle can show you previous committed versions of a row using undo data, as long as that undo hasn’t been overwritten.
5.1 Row version history with VERSIONS BETWEEN
In the demo, they kept changing Steven King’s salary:
- 24000 → 1 → 10 → back to 24000.
Then they queried the history:
SELECT
VERSIONS_STARTTIME AS start_time,
VERSIONS_ENDTIME AS end_time,
salary
FROM employees
VERSIONS BETWEEN SCN MINVALUE AND MAXVALUE
WHERE employee_id = 100;
This shows:
- Each salary value.
- When it became valid.
- When it stopped being valid (current version has no end time).
It’s like a security camera for row values.
5.2 Time-based flashback with AS OF
You can also query “what did this row look like N minutes ago?” using AS OF and INTERVAL:
SELECT salary
FROM employees3
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' MINUTE)
WHERE last_name = 'Chung';
As long as the undo for that time window still exists, you can:
- See the previous salary.
- Compare old vs. new values.
- Prove, with receipts, that someone really did set a salary to 1.
6. What You Should Now Be Able to Do
By the end of this lesson, you should be able to:
- Specify explicit default values in
INSERTandUPDATEstatements usingDEFAULT. - Describe how multi-table inserts work and when to use them.
- Use:
- unconditional
INSERT ALL, - conditional
INSERT ALL, - conditional
INSERT FIRST, and - pivoting inserts.
- Use
MERGEto conditionally update, insert, and optionally delete rows. - Perform flashback operations to recover tables or restore them to earlier states.
- Track changes made to data over time using row version queries and time-based flashback.
And if all of this feels like a ridiculous amount of power, that’s because it is. You can now duplicate data across multiple tables, rewrite history, and resurrect dropped objects—so please, use these features more responsibly than the demo script that set Steven King’s salary to $1 on purpose.