Points I explain out loud in the interview:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | -- ====================================================================== -- Q: Design a schema for B2B orders with per-customer price lists that are valid -- from-to. -- -- Points I explain out loud in the interview: -- - EXCLUDE with daterange: the database forbids two price lists that overlap in -- time for the same customer. With a check in code there would be a race -- condition between two concurrent admins. -- - Partial unique index: one draft cart per customer, but unlimited completed -- orders. Same for the ERP external_ref, which makes the sync idempotent -- (INSERT ... ON CONFLICT). -- - Snapshot of price and VAT on the line: if the price list changes, the -- invoice does not change. -- - ON DELETE RESTRICT for customers with history, CASCADE only for "children" -- that make no sense on their own. -- - In Django: ExclusionConstraint (django.contrib.postgres), -- UniqueConstraint(condition=Q(...)), CheckConstraint. -- ====================================================================== CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE TABLE customers ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, vat_number varchar(20) NOT NULL UNIQUE, name text NOT NULL, kind text NOT NULL CHECK (kind IN ('b2b', 'b2c')), deleted_at timestamptz ); CREATE TABLE products ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, sku varchar(40) NOT NULL UNIQUE, name text NOT NULL, vat_rate numeric(4,2) NOT NULL CHECK (vat_rate >= 0) ); CREATE TABLE price_lists ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customer_id bigint NOT NULL REFERENCES customers(id) ON DELETE RESTRICT, currency char(3) NOT NULL DEFAULT 'EUR', valid daterange NOT NULL, -- overlapping price lists for the same customer are not allowed EXCLUDE USING gist (customer_id WITH =, valid WITH &&) ); CREATE TABLE price_list_items ( price_list_id bigint NOT NULL REFERENCES price_lists(id) ON DELETE CASCADE, product_id bigint NOT NULL REFERENCES products(id), unit_cents integer NOT NULL CHECK (unit_cents >= 0), PRIMARY KEY (price_list_id, product_id) ); CREATE TABLE orders ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customer_id bigint NOT NULL REFERENCES customers(id), status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','placed','paid','shipped','cancelled')), external_ref varchar(64), -- ERP code total_cents bigint NOT NULL DEFAULT 0 CHECK (total_cents >= 0), created_at timestamptz NOT NULL DEFAULT now() ); -- only one open cart (draft) per customer: partial unique index CREATE UNIQUE INDEX ux_orders_one_draft ON orders (customer_id) WHERE status = 'draft'; CREATE UNIQUE INDEX ux_orders_external_ref ON orders (external_ref) WHERE external_ref IS NOT NULL; CREATE INDEX ix_orders_cust_created ON orders (customer_id, created_at DESC); CREATE TABLE order_lines ( order_id bigint NOT NULL REFERENCES orders(id) ON DELETE CASCADE, line_no smallint NOT NULL, product_id bigint NOT NULL REFERENCES products(id), qty integer NOT NULL CHECK (qty > 0), unit_cents integer NOT NULL, -- snapshot of the price at the time of the order vat_rate numeric(4,2) NOT NULL, -- snapshot of VAT PRIMARY KEY (order_id, line_no) ); |