MOO WebTech
Projectprojectintermediate

Project Work Session 2

Second work session: completing child table inserts and initial idempotency testing.

80 minL44

Goals

By the end of this session you should have:

  • Data inserted into all child/dependent tables using subqueries for FK resolution
  • At least one INSERT INTO ... SELECT statement written and tested
  • The full script run successfully from scratch (first execution)
  • The full script run a second time with identical row counts (idempotency verified)

Session Structure

TimeActivity
0–10 minCommon issues from session 1
10–60 minIndependent work
60–75 minCheckpoint: run script twice, compare row counts
75–80 minWrap-up

Work Guidance

Priority 1 — Child table inserts with subquery FK resolution

After inserting parent/reference data in session 1, insert child records. Use subqueries for every FK value.

orders depends on customers:

SQL
-- ============================================================
-- orders  (FK: customer_id -> customers)
-- ============================================================
INSERT INTO orders (customer_id, order_date, status) VALUES
    ((SELECT customer_id FROM customers WHERE email = 'alice@shop.com'),
     '2026-01-10', 'delivered'),
    ((SELECT customer_id FROM customers WHERE email = 'boris@shop.com'),
     '2026-01-22', 'confirmed'),
    ((SELECT customer_id FROM customers WHERE email = 'clara@shop.com'),
     '2026-02-03', 'pending'),
    ((SELECT customer_id FROM customers WHERE email = 'alice@shop.com'),
     '2026-02-18', 'delivered'),
    ((SELECT customer_id FROM customers WHERE email = 'david@shop.com'),
     '2026-03-05', 'confirmed')
ON CONFLICT DO NOTHING;

-- verify
SELECT o.order_id, c.full_name, o.order_date, o.status
FROM orders o
JOIN customers c USING (customer_id);

order_items depends on both orders and products:

For order_items there is no natural UNIQUE key, so the INSERT can be run multiple times and will create duplicates. Handle this by relying on the TRUNCATE at the top of the script to clear the table before re-inserting.

SQL
-- ============================================================
-- order_items  (FK: order_id -> orders, product_id -> products)
-- Note: no UNIQUE constraint — idempotency is handled by TRUNCATE
-- ============================================================
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT
    (SELECT order_id FROM orders
     WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'alice@shop.com')
       AND order_date = '2026-01-10'),
    (SELECT product_id FROM products WHERE name = 'Laptop Pro 15'),
    1,
    1299.99;

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT
    (SELECT order_id FROM orders
     WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'alice@shop.com')
       AND order_date = '2026-01-10'),
    (SELECT product_id FROM products WHERE name = 'Wireless Mouse'),
    2,
    29.99;

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT
    (SELECT order_id FROM orders
     WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'boris@shop.com')
       AND order_date = '2026-01-22'),
    (SELECT product_id FROM products WHERE name = 'PostgreSQL for Beginners'),
    1,
    34.95;

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT
    (SELECT order_id FROM orders
     WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'clara@shop.com')
       AND order_date = '2026-02-03'),
    (SELECT product_id FROM products WHERE name = 'Running Shoes X200'),
    1,
    89.90;

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT
    (SELECT order_id FROM orders
     WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'david@shop.com')
       AND order_date = '2026-03-05'),
    (SELECT product_id FROM products WHERE name = 'Garden Hose 20m'),
    3,
    24.50;

Priority 2 — INSERT INTO ... SELECT

The order_summary table is a perfect candidate for INSERT INTO ... SELECT. Instead of writing each row manually, derive it from existing orders and order_items data:

SQL
-- ============================================================
-- order_summary  (INSERT INTO ... SELECT)
-- Aggregate from orders and order_items
-- ============================================================
INSERT INTO order_summary (customer_id, order_count, total_value)
SELECT
    o.customer_id,
    COUNT(DISTINCT o.order_id)                        AS order_count,
    COALESCE(SUM(oi.quantity * oi.unit_price), 0)     AS total_value
FROM orders o
LEFT JOIN order_items oi USING (order_id)
GROUP BY o.customer_id
ON CONFLICT (customer_id) DO NOTHING;

-- verify
SELECT c.full_name, os.order_count, os.total_value
FROM order_summary os
JOIN customers c USING (customer_id)
ORDER BY os.total_value DESC;

This is your required INSERT INTO ... SELECT statement. It does real work: it calculates aggregated data from two tables and inserts the results into a summary table.

Priority 3 — Idempotency test

Once your full script runs without errors, run the idempotency test:

SQL
-- Step 1: record current row counts
SELECT 'categories'    AS tbl, COUNT(*) FROM categories
UNION ALL SELECT 'customers',    COUNT(*) FROM customers
UNION ALL SELECT 'products',     COUNT(*) FROM products
UNION ALL SELECT 'orders',       COUNT(*) FROM orders
UNION ALL SELECT 'order_items',  COUNT(*) FROM order_items
UNION ALL SELECT 'order_summary', COUNT(*) FROM order_summary;

Now run your full script again from the top. Then run the same count query. The numbers must be identical.

If any count increased, you have a missing ON CONFLICT DO NOTHING or a table that is not in the TRUNCATE block.


Checkpoint Questions

The instructor will verify the following during the checkpoint:

  1. Run your full script on a clean database — go ahead.
  2. Run it again. Show me the row counts — are they the same?
  3. Show me the INSERT INTO ... SELECT statement. Explain what it inserts and where the data comes from.
  4. Open order_items. Is there a UNIQUE constraint? How does your script handle idempotency for a table without a natural unique key?
  5. Is there any hardcoded integer ID in your script? Show me a subquery that replaced one.

Troubleshooting

Row counts increase on the second run

A table is missing ON CONFLICT DO NOTHING and is not covered by the TRUNCATE block. Check every INSERT statement — every table with a UNIQUE constraint needs the ON CONFLICT clause.

INSERT INTO ... SELECT inserts zero rows

The source query (the SELECT part) returns no rows. Run the SELECT alone first and confirm it returns data:

SQL
SELECT
    o.customer_id,
    COUNT(DISTINCT o.order_id),
    COALESCE(SUM(oi.quantity * oi.unit_price), 0)
FROM orders o
LEFT JOIN order_items oi USING (order_id)
GROUP BY o.customer_id;

Subquery in VALUES returns more than one row

You have multiple rows in a parent table that match your WHERE clause. Make the WHERE clause more specific — use a column with a UNIQUE constraint:

SQL
-- Wrong: could match multiple rows
(SELECT order_id FROM orders WHERE customer_id = ...)

-- Better: use a combination that is truly unique
(SELECT order_id FROM orders
 WHERE customer_id = (SELECT customer_id FROM customers WHERE email = 'alice@shop.com')
   AND order_date = '2026-01-10')