SQL Quest

Lessons, hints, explained solutions, the in-browser PostgreSQL editor and practice answer checks in your browser are available. Graded assessments, recorded progress from answer checks and new certificates aren't available yet.

Wadi Market online store

Wadi Market (fictional) · 8 tables · 35,996 rows · version 1

Open in the playground

Eight related tables from a realistic online shop: customers (with referrals), a three-level category tree, products, orders, order lines, payments, returns and shipments. Three years of trading with growth, Ramadan, Eid and White Friday peaks and a summer slowdown. The data is deliberately imperfect: duplicate customer registrations, missing values, failed and split payments, partial returns and late deliveries. Use it for everything from simple filters to cohort analysis and business reporting. All amounts are in Saudi riyals (SAR).

Relationships

customers800PKcustomer_idFKreferred_by_customer_idfirst_namelast_namecompany_nameemailphonecityregion+ 3 more columnscategories18PKcategory_idFKparent_category_idcategory_nameproducts61PKproduct_idFKcategory_idskuproduct_namebrandunit_priceunit_costlaunched_ondiscontinuedorders6,221PKorder_idFKcustomer_idordered_atchannelstatuscoupon_codeshipping_feedelivery_cityorder_items14,983PKorder_idPKline_noFKproduct_idquantityunit_pricediscount_amountpayments6,980PKpayment_idFKorder_idpaid_atmethodamountstatusreturns769PKreturn_idFKorder_idFKproduct_idreturned_onquantityreasonrefund_amountshipments6,164PKshipment_idFKorder_idcarriershipped_atpromised_datedelivered_at
Arrows point from a foreign key to the table it references. PK = primary key, FK = foreign key. Numbers are row counts.

Sample queries

Monthly revenue from completed orders
SELECT date_trunc('month', o.ordered_at)::date AS month,
       count(DISTINCT o.order_id) AS orders,
       sum(oi.quantity * oi.unit_price - oi.discount_amount) AS item_revenue
FROM orders AS o
JOIN order_items AS oi ON oi.order_id = o.order_id
WHERE o.status IN ('delivered', 'shipped', 'paid')
GROUP BY month
ORDER BY month;
Order totals (lines + shipping)
SELECT o.order_id, o.ordered_at, o.status,
       sum(oi.quantity * oi.unit_price - oi.discount_amount) + o.shipping_fee AS order_total
FROM orders AS o
JOIN order_items AS oi ON oi.order_id = o.order_id
GROUP BY o.order_id
ORDER BY o.ordered_at DESC
LIMIT 20;
Customers who never ordered
SELECT c.customer_id, c.first_name, c.last_name, c.signup_date
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL
ORDER BY c.signup_date;
Best-selling top-level categories
SELECT top.category_name AS department,
       sum(oi.quantity) AS units
FROM order_items AS oi
JOIN products AS p ON p.product_id = oi.product_id
JOIN categories AS leaf ON leaf.category_id = p.category_id
LEFT JOIN categories AS mid ON mid.category_id = leaf.parent_category_id
JOIN categories AS top ON top.category_id = coalesce(mid.parent_category_id, mid.category_id, leaf.category_id)
GROUP BY top.category_name
ORDER BY units DESC;
Payment attempts per order
SELECT order_id,
       count(*) AS payment_rows,
       count(*) FILTER (WHERE status = 'captured') AS captured_rows,
       sum(amount) FILTER (WHERE status = 'captured') AS captured_amount
FROM payments
GROUP BY order_id
HAVING count(*) > 1
ORDER BY payment_rows DESC, order_id
LIMIT 20;

Data dictionary

customers

800 rows · primary key (customer_id)

Everyone who has registered an account with Wadi Market, consumers and business buyers. One row per registration (a person who registered twice has two rows).

ColumnTypeNull?KeyDescription
customer_idintegerno PKUnique number for each registration. Numbers are assigned in signup order.
first_nametextnoGiven name (for business accounts, the contact person).
last_nametextnoFamily name.
company_nametextyesCompany the business account belongs to. NULL for consumer accounts.
emailtextyesuniqueEmail address as typed at signup. NULL when not provided. Not normalised: case and spaces vary.
phonetextyesMobile number in +9665… format. NULL when not provided.
citytextyesHome city as typed at signup. NULL when unknown; a few values have inconsistent case or spaces.
regiontextyesRegion of Saudi Arabia the city is in (Central, Eastern, Western, Northern, Southern). NULL when the city is unknown.
signup_datedatenoDate the account was created.
segmenttextnoconsumer (individual shopper) or business (company account).
marketing_opt_inbooleannoTRUE if the customer agreed to receive marketing messages.
referred_by_customer_idintegeryes customers.customer_idThe existing customer who referred this one (a self-reference). NULL when not referred.

categories

18 rows · primary key (category_id)

Product category tree with up to three levels. Top-level departments have no parent.

ColumnTypeNull?KeyDescription
category_idintegerno PKUnique number for each category.
category_nametextnouniqueCategory name shown in the store.
parent_category_idintegeryes categories.category_idThe category one level up. NULL for the top-level departments.

products

61 rows · primary key (product_id)

Items in the catalogue, including discontinued ones. Prices are today's list prices.

ColumnTypeNull?KeyDescription
product_idintegerno PKUnique number for each product.
skutextnouniqueStock-keeping unit code, e.g. WM-ELE-1001.
product_nametextnoName shown in the store.
category_idintegerno categories.category_idThe (lowest-level) category the product belongs to.
brandtextyesBrand name. NULL for unbranded items.
unit_pricenumeric(12,2)noCurrent list price in SAR (VAT included). Past orders may have been sold at a different price.
unit_costnumeric(12,2)yesCurrent purchase cost per unit in SAR. NULL when not recorded.
launched_ondatenoDate the product went on sale.
discontinuedbooleannoTRUE if the product is no longer sold.

orders

6,221 rows · primary key (order_id)

One row per order. The order's value is not stored: add up its lines in order_items and add shipping_fee.

ColumnTypeNull?KeyDescription
order_idintegerno PKUnique order number (starts at 100001).
customer_idintegerno customers.customer_idCustomer who placed the order.
ordered_attimestampnoDate and time the order was placed (Riyadh local time).
channeltextnoWhere the order was placed: web (website), app (mobile app) or marketplace (third-party marketplace).
statustextnopending (awaiting payment or, for cash on delivery, dispatch), paid (paid, not shipped yet), shipped (on its way), delivered, cancelled, or returned (everything sent back and refunded).
coupon_codetextyesPromotional code used, e.g. RAMADAN15 or WHITEFRIDAY20. NULL when no coupon. Typed by the customer, so case and spaces vary.
shipping_feenumeric(12,2)noDelivery fee charged in SAR (0 for free shipping). Free over SAR 200; the fee rose from 19 to 25 on 2024-07-01.
delivery_citytextnoCity the order was delivered to (usually, not always, the customer's home city).

order_items

14,983 rows · primary key (order_id, line_no)

Order lines: one row per product in an order. Line value = quantity × unit_price − discount_amount.

ColumnTypeNull?KeyDescription
order_idintegerno PK orders.order_idOrder this line belongs to.
line_nosmallintno PKLine number within the order (1, 2, 3 …).
product_idintegerno products.product_idProduct ordered. Each product appears at most once per order.
quantityintegernoUnits ordered.
unit_pricenumeric(12,2)noPrice per unit in SAR at the time of the order (may differ from products.unit_price).
discount_amountnumeric(12,2)noTotal discount on this line in SAR (coupon or sale), not per unit. 0 when none.

payments

6,980 rows · primary key (payment_id)

Payment transactions. An order can have none (unpaid cash on delivery), one, or several (failed attempts, split payments).

ColumnTypeNull?KeyDescription
payment_idintegerno PKUnique number for each payment transaction.
order_idintegerno orders.order_idOrder being paid for.
paid_attimestampnoWhen the transaction was attempted (for cash on delivery: when the courier collected the cash).
methodtextnomada (Saudi debit card), card (credit card), apple_pay, cash_on_delivery or bank_transfer.
amountnumeric(12,2)noAmount of the transaction in SAR.
statustextnocaptured (money received), failed (declined, nothing collected) or refunded (was received, later paid back in full).

returns

769 rows · primary key (return_id)

Products sent back by customers. One row per returned product per return; an order can be partly or fully returned.

ColumnTypeNull?KeyDescription
return_idintegerno PKUnique number for each return line.
order_idintegerno orders.order_idOrder the product came from.
product_idintegerno products.product_idProduct returned.
returned_ondatenoDate the return was received at the warehouse.
quantityintegernoUnits returned (never more than were ordered).
reasontextnoWhy it came back: damaged, wrong_item, not_as_described, changed_mind, late_delivery or size_or_fit.
refund_amountnumeric(12,2)yesAmount refunded for the returned units in SAR (shipping not included). NULL while the refund is still being processed.

shipments

6,164 rows · primary key (shipment_id)

Parcels sent to customers. Most orders ship in one parcel; some are split into two.

ColumnTypeNull?KeyDescription
shipment_idintegerno PKUnique number for each parcel.
order_idintegerno orders.order_idOrder the parcel belongs to.
carriertextnoCourier company (fictional): Wadi Express (in-house, big cities only), Najm Couriers, Sahara Logistics or Falak Post.
shipped_attimestampnoWhen the parcel left the warehouse.
promised_datedatenoDelivery date promised to the customer when the order was placed.
delivered_attimestampyesWhen the parcel was delivered. NULL if not delivered (yet).