|
| 1 | +-- ACME ERP — intentionally ambiguous multi-schema enterprise model. |
| 2 | +-- Designed to stress DeepSQL: duplicate business concepts, overloaded column |
| 3 | +-- names (status/id/name/amount), soft-delete vs status filters, missing FKs, |
| 4 | +-- and selective index gaps for workload recommendations. |
| 5 | +-- |
| 6 | +-- Schemas: crm, sales, finance, inventory, hr |
| 7 | + |
| 8 | +CREATE EXTENSION IF NOT EXISTS pg_stat_statements; |
| 9 | + |
| 10 | +CREATE SCHEMA IF NOT EXISTS crm; |
| 11 | +CREATE SCHEMA IF NOT EXISTS sales; |
| 12 | +CREATE SCHEMA IF NOT EXISTS finance; |
| 13 | +CREATE SCHEMA IF NOT EXISTS inventory; |
| 14 | +CREATE SCHEMA IF NOT EXISTS hr; |
| 15 | + |
| 16 | +-- ── CRM: "accounts" are customers in sales-speak ─────────────────────────── |
| 17 | +CREATE TABLE crm.accounts ( |
| 18 | + id BIGSERIAL PRIMARY KEY, |
| 19 | + account_number VARCHAR(32) NOT NULL UNIQUE, |
| 20 | + name VARCHAR(200) NOT NULL, |
| 21 | + status VARCHAR(20) NOT NULL, -- PROSPECT | ACTIVE | CHURNED |
| 22 | + tier VARCHAR(20) NOT NULL, -- SMB | MID | ENTERPRISE |
| 23 | + email VARCHAR(200), |
| 24 | + phone VARCHAR(40), |
| 25 | + country_code CHAR(2) NOT NULL, |
| 26 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 27 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 28 | + is_deleted BOOLEAN NOT NULL DEFAULT FALSE |
| 29 | +); |
| 30 | + |
| 31 | +CREATE TABLE crm.contacts ( |
| 32 | + id BIGSERIAL PRIMARY KEY, |
| 33 | + account_id BIGINT NOT NULL, -- intentional: no FK declared |
| 34 | + name VARCHAR(200) NOT NULL, |
| 35 | + email VARCHAR(200), |
| 36 | + role_title VARCHAR(120), |
| 37 | + status VARCHAR(20) NOT NULL, -- ACTIVE | INACTIVE |
| 38 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 39 | +); |
| 40 | + |
| 41 | +-- ── Sales: parallel "customers" concept (same people as crm.accounts) ───── |
| 42 | +CREATE TABLE sales.customers ( |
| 43 | + id BIGSERIAL PRIMARY KEY, |
| 44 | + crm_account_id BIGINT, -- soft link, often null historically |
| 45 | + customer_code VARCHAR(32) NOT NULL UNIQUE, |
| 46 | + name VARCHAR(200) NOT NULL, |
| 47 | + status VARCHAR(20) NOT NULL, -- ACTIVE | INACTIVE | BLOCKED |
| 48 | + email VARCHAR(200), |
| 49 | + ssn_last4 CHAR(4), -- PII |
| 50 | + country_code CHAR(2) NOT NULL, |
| 51 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 52 | +); |
| 53 | + |
| 54 | +CREATE TABLE sales.products ( |
| 55 | + id BIGSERIAL PRIMARY KEY, |
| 56 | + sku VARCHAR(40) NOT NULL UNIQUE, |
| 57 | + name VARCHAR(200) NOT NULL, |
| 58 | + status VARCHAR(20) NOT NULL, -- ACTIVE | DISCONTINUED |
| 59 | + unit_price NUMERIC(12,2) NOT NULL, |
| 60 | + category VARCHAR(80) NOT NULL, |
| 61 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 62 | +); |
| 63 | + |
| 64 | +CREATE TABLE sales.orders ( |
| 65 | + id BIGSERIAL PRIMARY KEY, |
| 66 | + customer_id BIGINT NOT NULL, |
| 67 | + order_number VARCHAR(40) NOT NULL UNIQUE, |
| 68 | + status VARCHAR(20) NOT NULL, -- PLACED | SHIPPED | DELIVERED | CANCELLED | RETURNED |
| 69 | + channel VARCHAR(20) NOT NULL, -- WEB | STORE | PARTNER |
| 70 | + placed_at TIMESTAMPTZ NOT NULL, |
| 71 | + shipped_at TIMESTAMPTZ, |
| 72 | + total_amount NUMERIC(14,2) NOT NULL, |
| 73 | + currency CHAR(3) NOT NULL DEFAULT 'USD', |
| 74 | + is_test BOOLEAN NOT NULL DEFAULT FALSE |
| 75 | +); |
| 76 | + |
| 77 | +CREATE TABLE sales.order_lines ( |
| 78 | + id BIGSERIAL PRIMARY KEY, |
| 79 | + order_id BIGINT NOT NULL, |
| 80 | + product_id BIGINT NOT NULL, |
| 81 | + quantity INT NOT NULL, |
| 82 | + unit_price NUMERIC(12,2) NOT NULL, |
| 83 | + line_amount NUMERIC(14,2) NOT NULL, |
| 84 | + status VARCHAR(20) NOT NULL -- OPEN | FULFILLED | CANCELLED |
| 85 | +); |
| 86 | + |
| 87 | +CREATE TABLE sales.order_header ( -- LEGACY alias table — same grain as orders |
| 88 | + id BIGSERIAL PRIMARY KEY, |
| 89 | + cust_id BIGINT NOT NULL, |
| 90 | + hdr_status VARCHAR(20) NOT NULL, |
| 91 | + order_dt DATE NOT NULL, |
| 92 | + amount NUMERIC(14,2) NOT NULL |
| 93 | +); |
| 94 | + |
| 95 | +-- ── Finance ──────────────────────────────────────────────────────────────── |
| 96 | +CREATE TABLE finance.invoices ( |
| 97 | + id BIGSERIAL PRIMARY KEY, |
| 98 | + invoice_number VARCHAR(40) NOT NULL UNIQUE, |
| 99 | + customer_id BIGINT NOT NULL, -- sales.customers.id (undeclared) |
| 100 | + order_id BIGINT, |
| 101 | + status VARCHAR(20) NOT NULL, -- DRAFT | OPEN | PAID | VOID | DISPUTED |
| 102 | + amount NUMERIC(14,2) NOT NULL, |
| 103 | + tax_amount NUMERIC(14,2) NOT NULL DEFAULT 0, |
| 104 | + issued_at TIMESTAMPTZ NOT NULL, |
| 105 | + due_at TIMESTAMPTZ NOT NULL, |
| 106 | + paid_at TIMESTAMPTZ |
| 107 | +); |
| 108 | + |
| 109 | +CREATE TABLE finance.payments ( |
| 110 | + id BIGSERIAL PRIMARY KEY, |
| 111 | + payment_number VARCHAR(40) NOT NULL UNIQUE, |
| 112 | + invoice_id BIGINT NOT NULL, |
| 113 | + amount NUMERIC(14,2) NOT NULL, |
| 114 | + status VARCHAR(20) NOT NULL, -- PENDING | CLEARED | FAILED | REVERSED |
| 115 | + method VARCHAR(20) NOT NULL, -- CARD | ACH | WIRE | CHECK |
| 116 | + paid_at TIMESTAMPTZ NOT NULL |
| 117 | +); |
| 118 | + |
| 119 | +CREATE TABLE finance.payment_orders ( -- ambiguous with sales.orders |
| 120 | + id BIGSERIAL PRIMARY KEY, |
| 121 | + vendor_name VARCHAR(200) NOT NULL, |
| 122 | + status VARCHAR(20) NOT NULL, -- SCHEDULED | SENT | COMPLETED | CANCELLED |
| 123 | + amount NUMERIC(14,2) NOT NULL, |
| 124 | + scheduled_at TIMESTAMPTZ NOT NULL, |
| 125 | + completed_at TIMESTAMPTZ |
| 126 | +); |
| 127 | + |
| 128 | +CREATE TABLE finance.gl_entries ( |
| 129 | + id BIGSERIAL PRIMARY KEY, |
| 130 | + account_code VARCHAR(32) NOT NULL, |
| 131 | + name VARCHAR(200) NOT NULL, |
| 132 | + status VARCHAR(20) NOT NULL, -- POSTED | PENDING | REVERSED |
| 133 | + amount NUMERIC(16,2) NOT NULL, |
| 134 | + posted_at TIMESTAMPTZ NOT NULL, |
| 135 | + cost_center VARCHAR(40) |
| 136 | +); |
| 137 | + |
| 138 | +-- ── Inventory (product masters overlap sales.products) ───────────────────── |
| 139 | +CREATE TABLE inventory.items ( |
| 140 | + id BIGSERIAL PRIMARY KEY, |
| 141 | + item_code VARCHAR(40) NOT NULL UNIQUE, |
| 142 | + name VARCHAR(200) NOT NULL, |
| 143 | + status VARCHAR(20) NOT NULL, -- ACTIVE | HOLD | OBSOLETE |
| 144 | + sku_ref VARCHAR(40), -- often matches sales.products.sku |
| 145 | + unit_cost NUMERIC(12,2) NOT NULL, |
| 146 | + reorder_point INT NOT NULL DEFAULT 10, |
| 147 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 148 | +); |
| 149 | + |
| 150 | +CREATE TABLE inventory.warehouses ( |
| 151 | + id BIGSERIAL PRIMARY KEY, |
| 152 | + code VARCHAR(20) NOT NULL UNIQUE, |
| 153 | + name VARCHAR(120) NOT NULL, |
| 154 | + status VARCHAR(20) NOT NULL, |
| 155 | + region VARCHAR(40) NOT NULL |
| 156 | +); |
| 157 | + |
| 158 | +CREATE TABLE inventory.stock_moves ( |
| 159 | + id BIGSERIAL PRIMARY KEY, |
| 160 | + item_id BIGINT NOT NULL, |
| 161 | + warehouse_id BIGINT NOT NULL, |
| 162 | + status VARCHAR(20) NOT NULL, -- PENDING | COMPLETE | CANCELLED |
| 163 | + quantity INT NOT NULL, |
| 164 | + move_type VARCHAR(20) NOT NULL, -- IN | OUT | ADJUST |
| 165 | + moved_at TIMESTAMPTZ NOT NULL, |
| 166 | + ref_order_id BIGINT -- may point at sales.orders |
| 167 | +); |
| 168 | + |
| 169 | +CREATE TABLE inventory.product_master ( -- another product synonym |
| 170 | + id BIGSERIAL PRIMARY KEY, |
| 171 | + name VARCHAR(200) NOT NULL, |
| 172 | + status VARCHAR(20) NOT NULL, |
| 173 | + amount NUMERIC(12,2) NOT NULL, -- "list price" overloaded as amount |
| 174 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 175 | +); |
| 176 | + |
| 177 | +-- ── HR (sensitive) ───────────────────────────────────────────────────────── |
| 178 | +CREATE TABLE hr.employees ( |
| 179 | + id BIGSERIAL PRIMARY KEY, |
| 180 | + employee_number VARCHAR(32) NOT NULL UNIQUE, |
| 181 | + name VARCHAR(200) NOT NULL, |
| 182 | + email VARCHAR(200) NOT NULL, |
| 183 | + status VARCHAR(20) NOT NULL, -- ACTIVE | LEAVE | TERMINATED |
| 184 | + department VARCHAR(80) NOT NULL, |
| 185 | + title VARCHAR(120) NOT NULL, |
| 186 | + manager_id BIGINT, |
| 187 | + hire_date DATE NOT NULL, |
| 188 | + salary NUMERIC(12,2) NOT NULL, -- highly sensitive |
| 189 | + ssn VARCHAR(11), -- PII |
| 190 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 191 | +); |
| 192 | + |
| 193 | +CREATE TABLE hr.payroll_runs ( |
| 194 | + id BIGSERIAL PRIMARY KEY, |
| 195 | + employee_id BIGINT NOT NULL, |
| 196 | + status VARCHAR(20) NOT NULL, -- DRAFT | APPROVED | PAID | VOID |
| 197 | + amount NUMERIC(12,2) NOT NULL, |
| 198 | + period_start DATE NOT NULL, |
| 199 | + period_end DATE NOT NULL, |
| 200 | + paid_at TIMESTAMPTZ |
| 201 | +); |
| 202 | + |
| 203 | +CREATE TABLE hr.departments ( |
| 204 | + id BIGSERIAL PRIMARY KEY, |
| 205 | + name VARCHAR(120) NOT NULL UNIQUE, |
| 206 | + status VARCHAR(20) NOT NULL, |
| 207 | + cost_center VARCHAR(40) |
| 208 | +); |
| 209 | + |
| 210 | +-- Sparse helpful indexes; deliberate gaps on hot filters (status, placed_at range, |
| 211 | +-- product_id, invoice customer_id) so the advisor has something to recommend. |
| 212 | +CREATE INDEX idx_crm_accounts_status ON crm.accounts (status); |
| 213 | +CREATE INDEX idx_sales_customers_code ON sales.customers (customer_code); |
| 214 | +CREATE INDEX idx_sales_orders_customer ON sales.orders (customer_id); |
| 215 | +CREATE INDEX idx_sales_orders_number ON sales.orders (order_number); |
| 216 | +CREATE INDEX idx_finance_invoices_number ON finance.invoices (invoice_number); |
| 217 | +CREATE INDEX idx_inventory_items_code ON inventory.items (item_code); |
| 218 | +CREATE INDEX idx_hr_employees_number ON hr.employees (employee_number); |
| 219 | + |
| 220 | +-- Legacy / redundant index noise (unused-looking after workload) |
| 221 | +CREATE INDEX idx_sales_orders_currency ON sales.orders (currency); |
| 222 | +CREATE INDEX idx_sales_products_created ON sales.products (created_at); |
0 commit comments