Northwind as a Neo4j graph model
Northwind is the standard example of turning a relational database into a graph. Here it is drawn as a graph data model: nine labels, ten relationship types, every property with its Neo4j type, unique keys, and the complete Cypher to create it.
Open this model in toro4n
What the model shows
The model follows Neo4j's rules for going from tables to a graph: each table becomes a label, foreign keys become relationships and are dropped as properties, and the join table between orders and products becomes the ORDERS relationship with its own properties (unit price, quantity, discount). Customers purchase orders, orders are shipped by shippers, employees report to employees, and products belong to categories and come from suppliers.
It is a good first model to learn Neo4j modeling on, because the relational version is so widely known, and a realistic test case for a modeling tool: three layers (Organization, Sales, Catalog), a key per label marked unique and required, and notes that explain the modeling choices.
In numbers: 9 nodes with 9 labels, 10 relationships of 10 types, 3 layers, and 5 notes. The model is a schema: each node stands for a label or a label combination, and each property shows its Neo4j type and, where one helps, an example. The Cypher below creates one node per label combination and one relationship per connection, with the example or the type name as the value, so it runs as it is.
Nodes and properties
Each property is written as the app draws it under the node: key, type, and example, with the Neo4j constraints that apply to it.
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 and notes
- Organization: 3 nodes
- Sales: 3 nodes
- Catalog: 3 nodes
The notes in the model:
- Northwind – Neo4j's standard example of turning a relational database into a graph.
Each node here stands for a label; its properties show the Cypher type.
Sources: guides.neo4j.com/northwind and data.neo4j.com/northwind/*.csv - From the join table order-details: its columns became properties on the relationship.
- Foreign keys (supplierId, categoryId) are relationships now, not properties.
- The manager is another Employee: the reportsTo column became a relationship to itself.
- Uniqueness constraint on every ...Id property, as in Neo4j's guide.
Cypher
Exactly what toro4n exports for this model: a constraint for every unique or required property, then one CREATE statement per node and per relationship, ready to run in Neo4j.
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 data comes from Neo4j's guide Northwind Graph and Neo4j's Northwind data files. The model file is northwind.json, and the app's own copy opens with this link.