Northwind as a Neo4j graph model
A graph data model of Northwind Traders, the fictional specialty foods company, with its customers, orders, products, suppliers, and employees.
Open this model in toro4n
What the model shows
The model follows Neo4j's Northwind guide, the classic lesson in turning a relational database into a graph: entity tables become labels, and foreign keys and join tables become relationships. A customer purchases each order, an employee sells it, and a shipper ships it. Employees report to a manager and cover sales territories, which belong to regions. Products come from suppliers, belong to categories, and appear on orders.
If you know the relational schema, compare it with the lists below to see where each table and foreign key ended up.
Nodes and properties
Category
categoryId: STRING – unique, requiredcategoryName: STRINGdescription: STRINGpicture: STRING
Customer
customerId: STRING – unique, requiredcompanyName: STRINGcontactName: STRINGcontactTitle: STRINGaddress: STRINGcity: STRINGregion: STRINGpostalCode: STRINGcountry: STRINGphone: STRINGfax: STRING
Employee
employeeId: STRING – unique, requiredlastName: STRINGfirstName: STRINGtitle: STRINGtitleOfCourtesy: STRINGbirthDate: LOCAL DATETIMEhireDate: LOCAL DATETIMEaddress: STRINGcity: STRINGregion: STRINGpostalCode: STRINGcountry: STRINGhomePhone: STRINGextension: STRINGnotes: STRINGphotoPath: STRING
Order
orderId: STRING – unique, requiredorderDate: LOCAL DATETIMErequiredDate: LOCAL DATETIMEshippedDate: LOCAL DATETIMEfreight: FLOATshipName: STRINGshipAddress: STRINGshipCity: STRINGshipRegion: STRINGshipPostalCode: STRINGshipCountry: STRING
Product
productId: STRING – unique, requiredproductName: STRINGquantityPerUnit: STRINGunitPrice: FLOATunitsInStock: INTEGERunitsOnOrder: INTEGERreorderLevel: INTEGERdiscontinued: BOOLEAN
Region
regionId: STRING – unique, requiredregionDescription: STRING
Shipper
shipperId: STRING – unique, requiredcompanyName: STRINGphone: STRING
Supplier
supplierId: STRING – unique, requiredcompanyName: STRINGcontactName: STRINGcontactTitle: STRINGaddress: STRINGcity: STRINGregion: STRINGpostalCode: STRINGcountry: STRINGphone: STRINGfax: STRINGhomePage: STRING
Territory
territoryId: STRING – unique, requiredterritoryDescription: STRING
Relationships
(:Supplier)-[:SUPPLIES]->(:Product)(:Product)-[:PART_OF]->(:Category)(:Category)-[:PARENT]->(:Category)(:Customer)-[:PURCHASED]->(:Order)(:Order)-[:ORDERS]->(:Product)withunitPrice: FLOAT,quantity: INTEGER,discount: FLOAT(:Shipper)-[:SHIPS]->(:Order)(:Employee)-[:SOLD]->(:Order)(:Employee)-[:REPORTS_TO]->(:Employee)(:Employee)-[:IN_TERRITORY]->(:Territory)(:Territory)-[:IN_REGION]->(:Region)
Layers
- Organization: 3 nodes
- Sales: 3 nodes
- Catalog: 3 nodes
Modeling notes
- The Order Details join table became the ORDERS relationship. Its unitPrice, quantity, and discount columns are now properties on ORDERS.
- The supplierId and categoryId foreign keys became the SUPPLIES and PART_OF relationships, so Product needs no foreign key properties.
- PARENT runs from each subcategory to the category above it, so a single variable-length pattern finds the products in a category and all its subcategories.
- The reportsTo column became REPORTS_TO, a relationship from an employee to their manager.
- Each label's ID property, such as customerId, is unique and required. Its uniqueness constraint also creates an index, so MERGE on the ID stays fast.
Cypher
Here's the Cypher script toro4n generates for this model. It starts with property uniqueness and existence constraints, then creates every node and relationship, with each property's type name as a placeholder value. Existence constraints need Neo4j Enterprise Edition or Aura.
CREATE CONSTRAINT Supplier_supplierId_unique IF NOT EXISTS FOR (n:Supplier) REQUIRE n.supplierId IS UNIQUE;
CREATE CONSTRAINT Supplier_supplierId_required IF NOT EXISTS FOR (n:Supplier) REQUIRE n.supplierId IS NOT NULL;
CREATE CONSTRAINT Product_productId_unique IF NOT EXISTS FOR (n:Product) REQUIRE n.productId IS UNIQUE;
CREATE CONSTRAINT Product_productId_required IF NOT EXISTS FOR (n:Product) REQUIRE n.productId IS NOT NULL;
CREATE CONSTRAINT Category_categoryId_unique IF NOT EXISTS FOR (n:Category) REQUIRE n.categoryId IS UNIQUE;
CREATE CONSTRAINT Category_categoryId_required IF NOT EXISTS FOR (n:Category) REQUIRE n.categoryId IS NOT NULL;
CREATE CONSTRAINT Customer_customerId_unique IF NOT EXISTS FOR (n:Customer) REQUIRE n.customerId IS UNIQUE;
CREATE CONSTRAINT Customer_customerId_required IF NOT EXISTS FOR (n:Customer) REQUIRE n.customerId IS NOT NULL;
CREATE CONSTRAINT Order_orderId_unique IF NOT EXISTS FOR (n:Order) REQUIRE n.orderId IS UNIQUE;
CREATE CONSTRAINT Order_orderId_required IF NOT EXISTS FOR (n:Order) REQUIRE n.orderId IS NOT NULL;
CREATE CONSTRAINT Shipper_shipperId_unique IF NOT EXISTS FOR (n:Shipper) REQUIRE n.shipperId IS UNIQUE;
CREATE CONSTRAINT Shipper_shipperId_required IF NOT EXISTS FOR (n:Shipper) REQUIRE n.shipperId IS NOT NULL;
CREATE CONSTRAINT Employee_employeeId_unique IF NOT EXISTS FOR (n:Employee) REQUIRE n.employeeId IS UNIQUE;
CREATE CONSTRAINT Employee_employeeId_required IF NOT EXISTS FOR (n:Employee) REQUIRE n.employeeId IS NOT NULL;
CREATE CONSTRAINT Territory_territoryId_unique IF NOT EXISTS FOR (n:Territory) REQUIRE n.territoryId IS UNIQUE;
CREATE CONSTRAINT Territory_territoryId_required IF NOT EXISTS FOR (n:Territory) REQUIRE n.territoryId IS NOT NULL;
CREATE CONSTRAINT Region_regionId_unique IF NOT EXISTS FOR (n:Region) REQUIRE n.regionId IS UNIQUE;
CREATE CONSTRAINT Region_regionId_required IF NOT EXISTS FOR (n:Region) REQUIRE n.regionId IS NOT NULL;
CREATE (Supplier:Supplier {supplierId: "STRING", companyName: "STRING", contactName: "STRING", contactTitle: "STRING", address: "STRING", city: "STRING", region: "STRING", postalCode: "STRING", country: "STRING", phone: "STRING", fax: "STRING", homePage: "STRING"})
CREATE (Product:Product {productId: "STRING", productName: "STRING", quantityPerUnit: "STRING", unitPrice: "FLOAT", unitsInStock: "INTEGER", unitsOnOrder: "INTEGER", reorderLevel: "INTEGER", discontinued: "BOOLEAN"})
CREATE (Category:Category {categoryId: "STRING", categoryName: "STRING", description: "STRING", picture: "STRING"})
CREATE (Customer:Customer {customerId: "STRING", companyName: "STRING", contactName: "STRING", contactTitle: "STRING", address: "STRING", city: "STRING", region: "STRING", postalCode: "STRING", country: "STRING", phone: "STRING", fax: "STRING"})
CREATE (Order:Order {orderId: "STRING", orderDate: "LOCAL DATETIME", requiredDate: "LOCAL DATETIME", shippedDate: "LOCAL DATETIME", freight: "FLOAT", shipName: "STRING", shipAddress: "STRING", shipCity: "STRING", shipRegion: "STRING", shipPostalCode: "STRING", shipCountry: "STRING"})
CREATE (Shipper:Shipper {shipperId: "STRING", companyName: "STRING", phone: "STRING"})
CREATE (Employee:Employee {employeeId: "STRING", lastName: "STRING", firstName: "STRING", title: "STRING", titleOfCourtesy: "STRING", birthDate: "LOCAL DATETIME", hireDate: "LOCAL DATETIME", address: "STRING", city: "STRING", region: "STRING", postalCode: "STRING", country: "STRING", homePhone: "STRING", extension: "STRING", notes: "STRING", photoPath: "STRING"})
CREATE (Territory:Territory {territoryId: "STRING", territoryDescription: "STRING"})
CREATE (Region:Region {regionId: "STRING", regionDescription: "STRING"})
CREATE (Supplier)-[:SUPPLIES]->(Product)
CREATE (Product)-[:PART_OF]->(Category)
CREATE (Category)-[:PARENT]->(Category)
CREATE (Customer)-[:PURCHASED]->(Order)
CREATE (Order)-[:ORDERS {unitPrice: "FLOAT", quantity: "INTEGER", discount: "FLOAT"}]->(Product)
CREATE (Shipper)-[:SHIPS]->(Order)
CREATE (Employee)-[:SOLD]->(Order)
CREATE (Employee)-[:REPORTS_TO]->(Employee)
CREATE (Employee)-[:IN_TERRITORY]->(Territory)
CREATE (Territory)-[:IN_REGION]->(Region)
Source
The model is based on Neo4j's Northwind Graph guide and the Northwind CSV files. Download the model as northwind.json.