Wadi Market online store
Wadi Market (fictional) · 8 tables · 35,996 rows · version 1
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
Sample queries
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;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;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;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;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).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| customer_id | integer | no | PK | Unique number for each registration. Numbers are assigned in signup order. |
| first_name | text | no | Given name (for business accounts, the contact person). | |
| last_name | text | no | Family name. | |
| company_name | text | yes | Company the business account belongs to. NULL for consumer accounts. | |
| text | yes | unique | Email address as typed at signup. NULL when not provided. Not normalised: case and spaces vary. | |
| phone | text | yes | Mobile number in +9665… format. NULL when not provided. | |
| city | text | yes | Home city as typed at signup. NULL when unknown; a few values have inconsistent case or spaces. | |
| region | text | yes | Region of Saudi Arabia the city is in (Central, Eastern, Western, Northern, Southern). NULL when the city is unknown. | |
| signup_date | date | no | Date the account was created. | |
| segment | text | no | consumer (individual shopper) or business (company account). | |
| marketing_opt_in | boolean | no | TRUE if the customer agreed to receive marketing messages. | |
| referred_by_customer_id | integer | yes | customers.customer_id | The 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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| category_id | integer | no | PK | Unique number for each category. |
| category_name | text | no | unique | Category name shown in the store. |
| parent_category_id | integer | yes | categories.category_id | The 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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| product_id | integer | no | PK | Unique number for each product. |
| sku | text | no | unique | Stock-keeping unit code, e.g. WM-ELE-1001. |
| product_name | text | no | Name shown in the store. | |
| category_id | integer | no | categories.category_id | The (lowest-level) category the product belongs to. |
| brand | text | yes | Brand name. NULL for unbranded items. | |
| unit_price | numeric(12,2) | no | Current list price in SAR (VAT included). Past orders may have been sold at a different price. | |
| unit_cost | numeric(12,2) | yes | Current purchase cost per unit in SAR. NULL when not recorded. | |
| launched_on | date | no | Date the product went on sale. | |
| discontinued | boolean | no | TRUE 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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| order_id | integer | no | PK | Unique order number (starts at 100001). |
| customer_id | integer | no | customers.customer_id | Customer who placed the order. |
| ordered_at | timestamp | no | Date and time the order was placed (Riyadh local time). | |
| channel | text | no | Where the order was placed: web (website), app (mobile app) or marketplace (third-party marketplace). | |
| status | text | no | pending (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_code | text | yes | Promotional code used, e.g. RAMADAN15 or WHITEFRIDAY20. NULL when no coupon. Typed by the customer, so case and spaces vary. | |
| shipping_fee | numeric(12,2) | no | Delivery fee charged in SAR (0 for free shipping). Free over SAR 200; the fee rose from 19 to 25 on 2024-07-01. | |
| delivery_city | text | no | City 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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| order_id | integer | no | PK orders.order_id | Order this line belongs to. |
| line_no | smallint | no | PK | Line number within the order (1, 2, 3 …). |
| product_id | integer | no | products.product_id | Product ordered. Each product appears at most once per order. |
| quantity | integer | no | Units ordered. | |
| unit_price | numeric(12,2) | no | Price per unit in SAR at the time of the order (may differ from products.unit_price). | |
| discount_amount | numeric(12,2) | no | Total 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).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| payment_id | integer | no | PK | Unique number for each payment transaction. |
| order_id | integer | no | orders.order_id | Order being paid for. |
| paid_at | timestamp | no | When the transaction was attempted (for cash on delivery: when the courier collected the cash). | |
| method | text | no | mada (Saudi debit card), card (credit card), apple_pay, cash_on_delivery or bank_transfer. | |
| amount | numeric(12,2) | no | Amount of the transaction in SAR. | |
| status | text | no | captured (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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| return_id | integer | no | PK | Unique number for each return line. |
| order_id | integer | no | orders.order_id | Order the product came from. |
| product_id | integer | no | products.product_id | Product returned. |
| returned_on | date | no | Date the return was received at the warehouse. | |
| quantity | integer | no | Units returned (never more than were ordered). | |
| reason | text | no | Why it came back: damaged, wrong_item, not_as_described, changed_mind, late_delivery or size_or_fit. | |
| refund_amount | numeric(12,2) | yes | Amount 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.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| shipment_id | integer | no | PK | Unique number for each parcel. |
| order_id | integer | no | orders.order_id | Order the parcel belongs to. |
| carrier | text | no | Courier company (fictional): Wadi Express (in-house, big cities only), Najm Couriers, Sahara Logistics or Falak Post. | |
| shipped_at | timestamp | no | When the parcel left the warehouse. | |
| promised_date | date | no | Delivery date promised to the customer when the order was placed. | |
| delivered_at | timestamp | yes | When the parcel was delivered. NULL if not delivered (yet). |