# Data Vault 2.0 example

> A worked Data Vault 2.0 example — hubs for customer, product and order business keys, links for their relationships, and satellites for descriptive history — with ER diagram, hash keys and SQL DDL.

Source: https://entitymodel.com/docs/examples/data-vault/

**Data Vault** is a modeling method for the integration layer of a data warehouse. It separates *business keys* (hubs), *relationships* (links) and *descriptive attributes over time* (satellites), so new sources and changes are added without reworking what's there. This example is a small raw vault for customers, products and orders.

## Design notes

- **Hubs hold business keys only.** `hub_customer` has one row per customer ID ever seen, with its hash key, first load time and source. Hubs are insert-only.
- **Links hold relationships.** `link_order_customer` and `link_order_product` connect hubs through their hash keys. Every relationship is a link, even one-to-many ones, so cardinality changes in the source don't change the model.
- **Satellites hold history.** Each satellite is keyed by its parent's hash key plus `load_dts`, and gets a new row only when `hash_diff` (a hash of its descriptive columns) changes.
- **Satellites can hang off links.** `sat_order_product` holds the quantity and price of each order line.
- **Every row records where and when it came from** (`record_source`, `load_dts`), which makes the vault auditable.
- **Hash keys** (`*_hk`) are computed from the business keys, so loads can run in parallel without lookups.

Business-facing star schemas or views are built on top of the vault; see the [star schema example](/docs/examples/star-schema/).

## Diagram

Data Vault 2.0 (Orders): 9 entities, 0 reference lists and 8 relationships. Each box is an entity with its attributes (PK marks the primary key); solid lines are relationships in crow's foot notation, dashed lines point to reference lists.

<!-- diagram -->

**[Open this model in EntityModel.com](https://entitymodel.com/#/share/v1.5Vrrbts2FH4VgvvTAoorOc7F_jU3dtpguXS1uwEdAoEWjyMisqiSVDKvKOCn8YP5SQZSkiVZchynaetuQIDEhzTPhd_Hcw6Zz3jMxYQo3MEQKqamE04hAIGtdMC9AyEZD3HHsbDksfAAd7CvVCQ7r14VvtPw-ARbOCQTPYESRdw7EgcKW1gxFWhhjyiC_tBC1GzY6MWVoCDkS2xhLm5IyP4hymjCfbPsRbYsgr_JJApAYgtTkJ5gUTqxi1bWFOQeGbVozAXyYqn4BIS0UCQ4jT0lEQkp4kZxB_nxSJqJo1iyEKREtzCVFgpYeJsMCAiMUdJnkbTMlyVREARMQTJjadAdIJ9JxcW0gS28DBt2Gja2MImVz0XVN2xhqYiKJe5gEkWC3wHFFvYEEAXUNTvTtJuHe46zZ-8Pbbtjfhq2bX_EFo4j-qh5ZqMYSNz563O2R348crMI6cgyGQVk6qajb-MROslHk5121TTSg1KRkBJhLOUBF67HqZb_st8_7B6fVvbpKgQk-D2KIN-UUtAtBHcgGuhS_0KpVzqORCnBRrEqm56t4fq32MIBGUGAOzgzF73V0tTUwfD92eUbbGEm3UiwCRFT9xamuKNEDBYW8ClmAmj2uWz3WyJ9xMdI-VAyV1uWghJ3cHvc9BazOTj4i1U1cVRr4us1JqYT642LQ_YphnpTXxfMQ2PBJ8bohLFlc08-DPYcu9Xc3y_aG3BCXapkwdhzTijqGVFq6fDsoj8Ydi_e4U2R-9OH0FigzbknEo2ZkAppLcnG5vYUYdtMYPuxaJkAjwvqpodPbt57I0eDTL4azVUDc42emDSWZwP-cq05KGJPGXhroF1bmAggCQ1k0RhNmvQsqefMu-XgIylz1D04Oe4_SJlUYQmC6MXgtw8v11Mk_U6ZIalxX0OQPIoth4wWs_mRV4xPpnZUp_YZQJ-rH_b7e1fv3-xd7L0-__CsSP6R0AQRNZiCyRNgabJaPSiv0qFHQtI5Pdw_PHwQkkZZ5Uysh6KZWwaiMeh5YOjZR3Qxm7ftYjwSlaOqymeF4OBqzyDEtm2n-byn6Y_EoPR51EhqpO1QqOumBIYPFBbnLLxNAPmE-qLdbXUdu4LM97pMA13cpcBU3CSfZa1x73MUBcQDipjahNT66qJs8vPXGCOu_HIhWk6TB3QfFrM5IVWcP5ZaW5LoKVXWehW1VdLWPNlE1Z-CN5oC64iztrgo8GbrGqPVbfUOmo-iDRDPXxYcPERMoReaSsmEgIXwQNFRcqEOls9agMCxM17M5nbzuzFi-6Jqy_JJx9cN48nIHIpLSrAQ0GUmTVWcXQ43VuE9iCCkECrk-Syg1dbl_0BGVwd1W0ZKovI8QEERFsgqJwdE5Rmht5z1SFY2Tw96R_sVVi4XzDmGuG6LFZtAA3VRCPemDuNhMEX3WaPl6zxC2XiMPJ-ENyB_RPv8DY77rbRuwpkOkquDVLDAJOBeItuEr_XXA8V7II8H8SRcyd9HR2AvZvPWqDbFms81e3GZyDdVrBFj1WB0Q4IGLLgrVQwwISwoaOqnn1c11K5IQvJr-slcXhUJAzcTCFVh5cFSsrp2vuA5n5JATdEbHtCd6fkHyxu-1RMhO_8fPBCyfLD1eXBwcnTart4EZOvVHge71v__9Awv5EavTRazOaV1FcAKX7PAPpKuuZIrc_PtoSFA6VwgCm64mBaPhFy0nk_dKCICgnJJITVwWYlH50xqoLIiiXr9k7OL7rnmvgCPpbf-TQtLj-jFm6VtaDXa7Z24ClnP1iT7P8jVpDTcmqldp3nqHFcvSMxqPhC6JntbSMaej4hEyaX_5lT9A-5O_kMUtket1mI2d9rVDiF9dVkN6yATP57Cg7dn7971exUVybPMigKgqKvqw1gToObQOe4cNDuOUwqQ4ooELpnwuJRwh1qMupn4KbR2WoeN4-aOtMmbmL22Vc6ZvXWnvOYBq1sg8zjvl3VngV7wpADXTfzLDvoUE6PKPBaac3en--X_ENvbxHYWs_l-KZNm21FY_fdcVOql11O8FLE4ZNWE-iFkO59Qt2uJi-y71ssWXsKLGDaP5cvHh-W_CNTfBSsibkCtPFh4RFAWkkBvSQdfdpzkvw_uiaBuXrDoBl2boV_WoSQHis5CbOERo0yAp60kQX3HtsrEa6MJ2E2YgDnPtyu3c7LoxTZOFuS75GfxCmD1_umhvczP3J91K6HgwxYe5uJd8rGQOgpeJouMoB60a662vga1vUxhjcvZGEWvp98WvLnbNTtc375_xRZ_Z5837XSFtHUd0JM5-52draNu7qomcb2nNdytZfbOulzc5WsL-wwEEZ7Pirk5_TPk6Upf_gU)** to explore it, change it and export it. Nothing is uploaded: the model travels in the link. Or [download model.json](/docs/examples/data-vault/model.json).

## Entities

| Entity | Type | Description | Primary Key | Display Attribute | Attributes |
| --- | --- | --- | --- | --- | --- |
| Hub Customer | Entity | One row per customer business key, ever. Never updated. | customer_hk | customer_bk | 4 |
| Hub Order | Entity | One row per order business key. | order_hk | order_bk | 4 |
| Hub Product | Entity | One row per product business key (SKU). | product_hk | product_bk | 4 |
| Link Order Customer | Entity | Relates an order to the customer who placed it. | order_customer_hk | load_dts | 5 |
| Link Order Product | Entity | Relates an order to each product on it (the order line). | order_product_hk | load_dts | 6 |
| Sat Customer Details | Entity | Customer attributes over time. A new row only when the hash diff changes. | customer_hk | customer_name | 7 |
| Sat Order Details | Entity | Order header attributes over time, such as status changes. | order_hk | order_status | 7 |
| Sat Order Product | Entity | Attributes of an order line (on the link): quantity and price. | order_product_hk | quantity | 6 |
| Sat Product Details | Entity | Product attributes over time. | product_hk | product_name | 7 |

## Attributes

### Hub Customer

One row per customer business key, ever. Never updated.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| customer_hk | STRING | PK, Required |  | 9f2c…e1 | Hash of the business key. |
| customer_bk | STRING | Label, Required |  | CUS-104233 | Business key from the source. |
| load_dts | TIMESTAMP | Required |  | 2026-11-03T02:00:00Z | When the key was first loaded. |
| record_source | STRING | Required |  | crm.customers |  |

### Hub Order

One row per order business key.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| order_hk | STRING | PK, Required |  | c07d…90 |  |
| order_bk | STRING | Label, Required |  | SO-2026-000123 |  |
| load_dts | TIMESTAMP | Required |  | 2026-11-03T02:00:00Z |  |
| record_source | STRING | Required |  | shop.orders |  |

### Hub Product

One row per product business key (SKU).

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| product_hk | STRING | PK, Required |  | 41ab…7c |  |
| product_bk | STRING | Label, Required |  | TEE-ORG-M-BLU |  |
| load_dts | TIMESTAMP | Required |  | 2026-11-03T02:00:00Z |  |
| record_source | STRING | Required |  | erp.items |  |

### Link Order Customer

Relates an order to the customer who placed it.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| order_customer_hk | STRING | PK, Required |  | 5d3e…aa | Hash of both business keys. |
| order_hk | STRING | Required |  | c07d…90 |  |
| customer_hk | STRING | Required |  | 9f2c…e1 |  |
| load_dts | TIMESTAMP | Label, Required |  | 2026-11-03T02:00:00Z |  |
| record_source | STRING | Required |  | shop.orders |  |

### Link Order Product

Relates an order to each product on it (the order line).

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| order_product_hk | STRING | PK, Required |  | e81f…02 |  |
| order_hk | STRING | Required |  | c07d…90 |  |
| product_hk | STRING | Required |  | 41ab…7c |  |
| line_number | INT | Required |  | 1 | Dependent child key. |
| load_dts | TIMESTAMP | Label, Required |  | 2026-11-03T02:00:00Z |  |
| record_source | STRING | Required |  | shop.order_lines |  |

### Sat Customer Details

Customer attributes over time. A new row only when the hash diff changes.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| customer_hk | STRING | PK, Required |  | 9f2c…e1 |  |
| load_dts | TIMESTAMP | PK, Required |  | 2026-11-03T02:00:00Z |  |
| hash_diff | STRING | Required |  | 77e0…4b | Hash of the descriptive columns. |
| customer_name | STRING | Label, PII |  | Ana Silva |  |
| email | STRING | PII |  | ana@example.com |  |
| segment | STRING |  |  | Loyalty Gold |  |
| record_source | STRING | Required |  | crm.customers |  |

### Sat Order Details

Order header attributes over time, such as status changes.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| order_hk | STRING | PK, Required |  | c07d…90 |  |
| load_dts | TIMESTAMP | PK, Required |  | 2026-11-03T02:00:00Z |  |
| hash_diff | STRING | Required |  | 0b44…19 |  |
| order_status | STRING | Label |  | SHIPPED |  |
| ordered_at | TIMESTAMP |  |  | 2026-11-02T18:52:11Z |  |
| total_amount | DECIMAL(12,2) |  |  | 146.82 |  |
| record_source | STRING | Required |  | shop.orders |  |

### Sat Order Product

Attributes of an order line (on the link): quantity and price.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| order_product_hk | STRING | PK, Required |  | e81f…02 |  |
| load_dts | TIMESTAMP | PK, Required |  | 2026-11-03T02:00:00Z |  |
| hash_diff | STRING | Required |  | 9a01…3e |  |
| quantity | INT | Label |  | 2 |  |
| unit_price | DECIMAL(12,2) |  |  | 24.99 |  |
| record_source | STRING | Required |  | shop.order_lines |  |

### Sat Product Details

Product attributes over time.

| Attribute | Data Type | Flags | Reference | Example | Description |
| --- | --- | --- | --- | --- | --- |
| product_hk | STRING | PK, Required |  | 41ab…7c |  |
| load_dts | TIMESTAMP | PK, Required |  | 2026-11-03T02:00:00Z |  |
| hash_diff | STRING | Required |  | 1c9a…dd |  |
| product_name | STRING | Label |  | Organic Tee |  |
| category | STRING |  |  | Apparel |  |
| list_price | DECIMAL(12,2) |  |  | 24.99 |  |
| record_source | STRING | Required |  | erp.items |  |

## Relationships

| Relationship | Source | Target | Cardinality | Forward Label | Reverse Label | Key | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| links_order | Link Order Customer | Hub Order | N:1 | Links | Linked In | link_order_customer.order_hk |  |
| links_customer | Link Order Customer | Hub Customer | N:1 | Links | Linked In | link_order_customer.customer_hk |  |
| line_order | Link Order Product | Hub Order | N:1 | Links | Linked In | link_order_product.order_hk |  |
| line_product | Link Order Product | Hub Product | N:1 | Links | Linked In | link_order_product.product_hk |  |
| describes_customer | Sat Customer Details | Hub Customer | N:1 | Describes | Described By | sat_customer_details.customer_hk |  |
| describes_product | Sat Product Details | Hub Product | N:1 | Describes | Described By | sat_product_details.product_hk |  |
| describes_order | Sat Order Details | Hub Order | N:1 | Describes | Described By | sat_order_details.order_hk |  |
| describes_line | Sat Order Product | Link Order Product | N:1 | Describes | Described By | sat_order_product.order_product_hk |  |

## SQL DDL (PostgreSQL)

Generated from the model. In the app, Export → Code also writes MySQL, Snowflake and Databricks DDL, DBML, dbt `schema.yml` and data contracts.

```sql
-- Data Vault 2.0 (Orders) — data model
-- PostgreSQL DDL, generated 2026-11-03
-- Made with EntityModel.com

CREATE TABLE hub_customer (
  customer_hk TEXT NOT NULL,
  customer_bk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  record_source TEXT NOT NULL,
  PRIMARY KEY (customer_hk)
);
COMMENT ON TABLE hub_customer IS 'One row per customer business key, ever. Never updated.';
COMMENT ON COLUMN hub_customer.customer_hk IS 'Hash of the business key.';
COMMENT ON COLUMN hub_customer.customer_bk IS 'Business key from the source.';
COMMENT ON COLUMN hub_customer.load_dts IS 'When the key was first loaded.';

CREATE TABLE hub_product (
  product_hk TEXT NOT NULL,
  product_bk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  record_source TEXT NOT NULL,
  PRIMARY KEY (product_hk)
);
COMMENT ON TABLE hub_product IS 'One row per product business key (SKU).';

CREATE TABLE hub_order (
  order_hk TEXT NOT NULL,
  order_bk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  record_source TEXT NOT NULL,
  PRIMARY KEY (order_hk)
);
COMMENT ON TABLE hub_order IS 'One row per order business key.';

CREATE TABLE link_order_customer (
  order_customer_hk TEXT NOT NULL,
  order_hk TEXT NOT NULL,
  customer_hk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  record_source TEXT NOT NULL,
  PRIMARY KEY (order_customer_hk)
);
COMMENT ON TABLE link_order_customer IS 'Relates an order to the customer who placed it.';
COMMENT ON COLUMN link_order_customer.order_customer_hk IS 'Hash of both business keys.';

CREATE TABLE link_order_product (
  order_product_hk TEXT NOT NULL,
  order_hk TEXT NOT NULL,
  product_hk TEXT NOT NULL,
  line_number INTEGER NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  record_source TEXT NOT NULL,
  PRIMARY KEY (order_product_hk)
);
COMMENT ON TABLE link_order_product IS 'Relates an order to each product on it (the order line).';
COMMENT ON COLUMN link_order_product.line_number IS 'Dependent child key.';

CREATE TABLE sat_customer_details (
  customer_hk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  hash_diff TEXT NOT NULL,
  customer_name TEXT,
  email TEXT,
  segment TEXT,
  record_source TEXT NOT NULL,
  PRIMARY KEY (customer_hk, load_dts)
);
COMMENT ON TABLE sat_customer_details IS 'Customer attributes over time. A new row only when the hash diff changes.';
COMMENT ON COLUMN sat_customer_details.hash_diff IS 'Hash of the descriptive columns.';

CREATE TABLE sat_product_details (
  product_hk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  hash_diff TEXT NOT NULL,
  product_name TEXT,
  category TEXT,
  list_price NUMERIC(12,2),
  record_source TEXT NOT NULL,
  PRIMARY KEY (product_hk, load_dts)
);
COMMENT ON TABLE sat_product_details IS 'Product attributes over time.';

CREATE TABLE sat_order_details (
  order_hk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  hash_diff TEXT NOT NULL,
  order_status TEXT,
  ordered_at TIMESTAMP,
  total_amount NUMERIC(12,2),
  record_source TEXT NOT NULL,
  PRIMARY KEY (order_hk, load_dts)
);
COMMENT ON TABLE sat_order_details IS 'Order header attributes over time, such as status changes.';

CREATE TABLE sat_order_product (
  order_product_hk TEXT NOT NULL,
  load_dts TIMESTAMP NOT NULL,
  hash_diff TEXT NOT NULL,
  quantity INTEGER,
  unit_price NUMERIC(12,2),
  record_source TEXT NOT NULL,
  PRIMARY KEY (order_product_hk, load_dts)
);
COMMENT ON TABLE sat_order_product IS 'Attributes of an order line (on the link): quantity and price.';

-- Keys between tables
ALTER TABLE hub_customer ADD CONSTRAINT uq_hub_customer_customer_bk UNIQUE (customer_bk);
ALTER TABLE hub_product ADD CONSTRAINT uq_hub_product_product_bk UNIQUE (product_bk);
ALTER TABLE hub_order ADD CONSTRAINT uq_hub_order_order_bk UNIQUE (order_bk);
ALTER TABLE link_order_customer ADD CONSTRAINT fk_links_order FOREIGN KEY (order_hk) REFERENCES hub_order (order_hk);
ALTER TABLE link_order_customer ADD CONSTRAINT fk_links_customer FOREIGN KEY (customer_hk) REFERENCES hub_customer (customer_hk);
ALTER TABLE link_order_product ADD CONSTRAINT fk_line_order FOREIGN KEY (order_hk) REFERENCES hub_order (order_hk);
ALTER TABLE link_order_product ADD CONSTRAINT fk_line_product FOREIGN KEY (product_hk) REFERENCES hub_product (product_hk);
ALTER TABLE sat_customer_details ADD CONSTRAINT fk_describes_customer FOREIGN KEY (customer_hk) REFERENCES hub_customer (customer_hk);
ALTER TABLE sat_product_details ADD CONSTRAINT fk_describes_product FOREIGN KEY (product_hk) REFERENCES hub_product (product_hk);
ALTER TABLE sat_order_details ADD CONSTRAINT fk_describes_order FOREIGN KEY (order_hk) REFERENCES hub_order (order_hk);
ALTER TABLE sat_order_product ADD CONSTRAINT fk_describes_line FOREIGN KEY (order_product_hk) REFERENCES link_order_product (order_product_hk);
```

## Mermaid

Paste into a `mermaid` code block on GitHub, GitLab or Notion to show the diagram there.

```mermaid
---
title: Data Vault 2.0 (Orders)
---
%% Made with EntityModel.com
erDiagram
  hub_customer {
    string customer_hk PK "Hash of the business key."
    string customer_bk "Business key from the source."
    timestamp load_dts "When the key was first loaded."
    string record_source "Record Source"
  }
  hub_product {
    string product_hk PK "Product Hk"
    string product_bk "Product Bk"
    timestamp load_dts "Load Dts"
    string record_source "Record Source"
  }
  hub_order {
    string order_hk PK "Order Hk"
    string order_bk "Order Bk"
    timestamp load_dts "Load Dts"
    string record_source "Record Source"
  }
  link_order_customer {
    string order_customer_hk PK "Hash of both business keys."
    string order_hk FK "Order Hk"
    string customer_hk FK "Customer Hk"
    timestamp load_dts "Load Dts"
    string record_source "Record Source"
  }
  link_order_product {
    string order_product_hk PK "Order Product Hk"
    string order_hk FK "Order Hk"
    string product_hk FK "Product Hk"
    int line_number "Dependent child key."
    timestamp load_dts "Load Dts"
    string record_source "Record Source"
  }
  sat_customer_details {
    string customer_hk PK, FK "Customer Hk"
    timestamp load_dts PK "Load Dts"
    string hash_diff "Hash of the descriptive columns."
    string customer_name "Customer Name"
    string email "Email"
    string segment "Segment"
    string record_source "Record Source"
  }
  sat_product_details {
    string product_hk PK, FK "Product Hk"
    timestamp load_dts PK "Load Dts"
    string hash_diff "Hash Diff"
    string product_name "Product Name"
    string category "Category"
    decimal list_price "List Price"
    string record_source "Record Source"
  }
  sat_order_details {
    string order_hk PK, FK "Order Hk"
    timestamp load_dts PK "Load Dts"
    string hash_diff "Hash Diff"
    string order_status "Order Status"
    timestamp ordered_at "Ordered At"
    decimal total_amount "Total Amount"
    string record_source "Record Source"
  }
  sat_order_product {
    string order_product_hk PK, FK "Order Product Hk"
    timestamp load_dts PK "Load Dts"
    string hash_diff "Hash Diff"
    int quantity "Quantity"
    decimal unit_price "Unit Price"
    string record_source "Record Source"
  }
  link_order_customer }o--|| hub_order : "Links"
  link_order_customer }o--|| hub_customer : "Links"
  link_order_product }o--|| hub_order : "Links"
  link_order_product }o--|| hub_product : "Links"
  sat_customer_details }o--|| hub_customer : "Describes"
  sat_product_details }o--|| hub_product : "Describes"
  sat_order_details }o--|| hub_order : "Describes"
  sat_order_product }o--|| link_order_product : "Describes"
```

## Frequently asked questions

### What are hubs, links and satellites in Data Vault?

Hubs store unique business keys, links store relationships between hubs, and satellites store descriptive attributes and their history for a hub or a link. Together they separate identity, relationships and context.

### When should you use Data Vault instead of a star schema?

Data Vault suits the integration layer when you combine many changing sources and need full history and auditability. Star schemas suit the presentation layer that analysts query. Many warehouses use both: a vault for integration, stars built on top for reporting.
