ErdDocs

Example schemas / Northwind Database

Northwind Database ER diagram

What is the Northwind database?

The Northwind database is a 13-table sample database Microsoft has shipped since the Access era, modeling a food-trading company: customers place orders, employees take them, shippers deliver them, and suppliers stock the product catalog. Those thirteen tables carry every teaching shape at once — a header/line order split, two junction tables, a self-referencing org chart and a natural-key era customer code — which is why three decades of database tutorials still reach for it.

Your schema

Hover a table to light up its relations, click to pin. Switch to Data Dictionary for the printable reference version of this schema.

What each table does

  • customers — The famous char(5) mnemonic key — ALFKI — from the era of natural keys.
  • orders — The header: who ordered, who took it, who ships it, three lifecycle dates.
  • order_details — The junction with payload — quantity, a frozen unit_price and a discount fraction.
  • products — The catalog, pointing at both a supplier and a category.
  • suppliers / categories — The two lookup tables the catalog points at — every product carries a supplier_id and a category_id.
  • employees — reports_to references employees itself — an org chart in one column.
  • shippers — The delivery companies; orders reference them through ship_via — the foreign key whose name breaks the _id convention.
  • territories / regions — Sales geography, two levels deep.
  • employee_territories — Pure many-to-many between employees and territories.
  • customer_demographics / customer_customer_demo — The vestigial pair: shipped empty for decades, kept because tutorials reference it.

Design decisions worth copying

  • customers.customer_id is char(5) and mnemonic (ALFKI, ANATR) — a natural key from 1994. Modern schemas use surrogate ids and keep the mnemonic as a unique column, but Northwind is the cleanest place to see why natural keys felt right and what makes them fragile: renaming a company breaks every reference.
  • orders.ship_via proves why declared foreign keys beat naming conventions: the column is declared as ship_via int REFERENCES shippers(shipper_id), and nothing in the name says shippers — only the constraint draws the line. A tool that matches *_id names loses this relation; paste the schema with the REFERENCES removed and watch the line disappear.
  • The original table is called "Order Details" — with a space — so every port either quotes the identifier forever or renames it. This rendition renames it to order_details, which is what most PostgreSQL and MySQL ports do; remember the space when a tutorial query fails against a port.
  • employees.reports_to is the canonical self-reference: one nullable column turns a flat table into a hierarchy, and the NULL at the top is the CEO. The same shape reappears in category trees and comment threads.

The SQL

PostgreSQL dialect; paste it into the tool above (it is already there), into your own database, or into a migration. Copy freely — example schemas are meant to be taken.

CREATE TABLE regions (
    region_id    serial      PRIMARY KEY,
    description  varchar(60) NOT NULL
);

CREATE TABLE territories (
    territory_id  varchar(20) PRIMARY KEY,
    description   varchar(60) NOT NULL,
    region_id     int         NOT NULL REFERENCES regions(region_id)
);

CREATE TABLE categories (
    category_id  serial       PRIMARY KEY,
    name         varchar(30)  NOT NULL,
    description  text
);

CREATE TABLE suppliers (
    supplier_id   serial       PRIMARY KEY,
    company_name  varchar(60)  NOT NULL,
    contact_name  varchar(60),
    city          varchar(30),
    country       varchar(30)
);

CREATE TABLE products (
    product_id        serial        PRIMARY KEY,
    name              varchar(60)   NOT NULL,
    supplier_id       int           REFERENCES suppliers(supplier_id),
    category_id       int           REFERENCES categories(category_id),
    quantity_per_unit varchar(30),
    unit_price        numeric(10,2),
    units_in_stock    smallint,
    discontinued      boolean       NOT NULL DEFAULT false
);
COMMENT ON COLUMN products.discontinued IS 'The original bit flag; kept rows, never deletes';

CREATE TABLE customers (
    customer_id   char(5)      PRIMARY KEY,
    company_name  varchar(60)  NOT NULL,
    contact_name  varchar(60),
    city          varchar(30),
    country       varchar(30)
);
COMMENT ON COLUMN customers.customer_id IS 'Mnemonic natural key: ALFKI for Alfreds Futterkiste';

CREATE TABLE customer_demographics (
    customer_type_id  char(10) PRIMARY KEY,
    customer_desc     text
);

CREATE TABLE customer_customer_demo (
    customer_id       char(5)  NOT NULL REFERENCES customers(customer_id),
    customer_type_id  char(10) NOT NULL REFERENCES customer_demographics(customer_type_id),
    PRIMARY KEY (customer_id, customer_type_id)
);

CREATE TABLE employees (
    employee_id  serial      PRIMARY KEY,
    last_name    varchar(20) NOT NULL,
    first_name   varchar(10) NOT NULL,
    title        varchar(30),
    hire_date    date,
    reports_to   int         REFERENCES employees(employee_id)
);
COMMENT ON COLUMN employees.reports_to IS 'Self-reference: the org chart lives inside the table';

CREATE TABLE employee_territories (
    employee_id   int         NOT NULL REFERENCES employees(employee_id),
    territory_id  varchar(20) NOT NULL REFERENCES territories(territory_id),
    PRIMARY KEY (employee_id, territory_id)
);

CREATE TABLE shippers (
    shipper_id    serial      PRIMARY KEY,
    company_name  varchar(40) NOT NULL,
    phone         varchar(24)
);

CREATE TABLE orders (
    order_id       serial        PRIMARY KEY,
    customer_id    char(5)       REFERENCES customers(customer_id),
    employee_id    int           REFERENCES employees(employee_id),
    order_date     date,
    required_date  date,
    shipped_date   date,
    ship_via       int           REFERENCES shippers(shipper_id),
    freight        numeric(10,2),
    ship_city      varchar(30),
    ship_country   varchar(30)
);
COMMENT ON COLUMN orders.ship_via IS 'An FK that does not end in _id — name conventions cannot find it, the constraint can';

CREATE TABLE order_details (
    order_id    int           NOT NULL REFERENCES orders(order_id),
    product_id  int           NOT NULL REFERENCES products(product_id),
    unit_price  numeric(10,2) NOT NULL,
    quantity    smallint      NOT NULL DEFAULT 1,
    discount    real          NOT NULL DEFAULT 0,
    PRIMARY KEY (order_id, product_id)
);
COMMENT ON TABLE order_details IS 'The original calls this "Order Details", space and all — renditions rename it';

FAQ

Where do I download the original Northwind database?

Microsoft publishes instnwnd.sql in the sql-server-samples repository on GitHub under the MIT license, and ports exist for PostgreSQL, MySQL and SQLite. The SQL on this page is our own PostgreSQL-style rendition of the classic structure — paste your port’s dump instead if the diagram should match it exactly.

How many tables does Northwind have?

Thirteen in the classic SQL Server version, including the two demographics tables that ship empty. Many ports trim those two, which is why tutorials disagree between 11 and 13 — check for customer_demographics before comparing counts.

Can I get the Northwind ER diagram as a PDF or PNG?

Yes — the Northwind diagram exports as PNG or SVG from the Export menu, and the data dictionary view prints to PDF with every table, column, key and relationship listed. Both run in the browser; nothing is uploaded.

More example schemas