Guides
Graph vs. SQL
Most data problems are solved by a relational database. Some are solved better by a graph database. A few require both at the same time. Ontology Workbench is designed to handle all three cases from a single canonical model.
The core difference
Relational (SQL) databases organize data into tables with rows and columns. Relationships between tables are expressed through foreign keys and joins. SQL databases excel at structured, uniform data with well-defined schemas — financial records, product catalogs, user accounts.
Graph databases store data as nodes and edges. Every relationship is a first-class citizen with its own identity and (optionally) its own properties. Graph databases excel at highly connected data where the relationships themselves carry meaning — social networks, knowledge graphs, supply chains, fraud detection.
The distinction isn't about which is "better." It's about which structure fits the shape of your data.
Where they diverge
| Concern | Relational (SQL) | Graph |
|---|---|---|
| Data shape | Uniform rows in tables | Heterogeneous nodes and edges |
| Relationship storage | Foreign keys + join tables | Native edges with properties |
| Query style | Joins across tables | Traversals across edges |
| Best for | Structured, tabular records | Connected, relationship-heavy data |
| Examples | PostgreSQL, MySQL, SQLite | Neo4j, Amazon Neptune, ArangoDB |
| ORM/query layer | Prisma, Drizzle, SQLAlchemy | Cypher, Gremlin, AQL |
Relationships as data
In a relational schema, a many-to-many relationship between User and Project requires a join table (UserProject) with foreign keys. Querying who worked on what requires an explicit join.
In a graph schema, that same relationship is an edge — (User)-[:WORKED_ON]->(Project) — and the edge can carry its own properties like role or startDate. Traversing "all projects this user contributed to" is a single path query, not a multi-table join.
OWB supports relationship properties natively across all export targets. When you define a relationship in the workbench and attach properties to it:
- Graph exports (Neo4j, Neptune, ArangoDB, DGraph) emit them as native edge attributes.
- SQL exports (Prisma, Drizzle, SQLAlchemy) handle them based on cardinality:
- N:1 and 1:N — property columns are added alongside the foreign key on the FK-holding side of the relationship.
- 1:1 — property columns are added alongside the FK on the target side.
- N:M — an explicit join table (or association object in SQLAlchemy) is generated with the property columns included.
Modeling for both
The OWB data model is database-agnostic by design. You define entity types, properties, and relationships once. The same model then exports to your target environment.
SQL export path
Export to Prisma, Drizzle, or SQLAlchemy for use with PostgreSQL, MySQL, or SQLite. Entity types become tables. N:1 and 1:N relationships become FK columns; N:M relationships always produce an explicit join table. Relationship properties are emitted as columns alongside the FK or in the join table, so no schema data is lost. Abstract types are skipped — their properties are inherited by concrete subtypes.
Graph export path
Export to Neo4j (Cypher), Amazon Neptune (Gremlin), ArangoDB (JSON Schema), or DGraph (DQL) for native graph databases. Entity types become node labels. Relationships become typed edges. Relationship properties become edge attributes.
Semantic web path
Export to OWL/XML for knowledge graph platforms, reasoners, or linked data pipelines. Entity types become OWL classes. Relationships become object properties. Abstract types and inheritance are expressed natively as owl:SubClassOf axioms.
Using both at the same time
Some architectures deliberately split storage across a relational database and a graph database. A common pattern:
- SQL layer: canonical records, transactions, financial data — anywhere ACID compliance and structured queries matter
- Graph layer: relationship traversal, recommendations, fraud detection, lineage — anywhere the connections between records are the point
OWB supports this directly. Export your model to Prisma and Neo4j from the same source. The entity types stay consistent; only the target syntax changes. If your team's schema evolves, you update one model and re-export both targets — no manual synchronization between two separate schema files.
Example
Given an Author and Book entity type connected by a WROTE relationship with a year property:
Prisma (SQL) — WROTE is N:M with a year property, so an explicit join model is generated
model Author {
id String @id @default(cuid())
name String
wrotes Wrote[] @relation("WroteSource")
}
model Book {
id String @id @default(cuid())
title String
wrotes Wrote[] @relation("WroteTarget")
}
model Wrote {
authorId String
bookId String
year Int?
author Author @relation("WroteSource", fields: [authorId], references: [id])
book Book @relation("WroteTarget", fields: [bookId], references: [id])
@@id([authorId, bookId])
}Neo4j Cypher — emits the relationship as a native edge with the year property
CREATE CONSTRAINT FOR (n:Author) REQUIRE n.id IS UNIQUE;
CREATE CONSTRAINT FOR (n:Book) REQUIRE n.id IS UNIQUE;
// WROTE (Author → Book)
// Properties: year (Integer)
// Pattern:
// MATCH (s:Author {id: $sourceId}), (t:Book {id: $targetId})
// MERGE (s)-[r:WROTE {year: $year}]->(t)
// RETURN rSame model. Two targets. One source of truth — year is available in both the SQL join table and as a graph edge attribute.
Inheritance and abstract types
OWB supports entity inheritance — you can define an abstract Asset type with shared properties and have Image, Video, and Document extend it.
- SQL exports (Prisma, Drizzle, SQLAlchemy): abstract types are not mapped to tables. Concrete subtypes include all inherited properties directly. SQLAlchemy generates abstract types as Python mixin classes that concrete models inherit from.
- Neo4j: concrete nodes carry multiple labels — a
Carthat extendsVehiclegets labels:Car:Vehicle, preserving the inheritance chain in the graph. - Neptune, ArangoDB, DGraph: inheritance is flattened — inherited fields are written directly on the concrete type, with a comment noting the source.
- OWL/XML: inheritance is expressed natively as
owl:SubClassOfaxioms, which is the semantically richest representation.
Abstract types are never instantiated as rows or nodes — they exist solely to share property definitions across concrete subtypes.
Choosing your export
If you're unsure which path to use:
- Starting a new project with a relational database? Use Prisma or Drizzle. Both generate type-safe query clients from the schema.
- Need Python / Django / FastAPI? Use SQLAlchemy for a mature, production-ready ORM.
- Working with highly connected data or building recommendations / graph search? Use Neo4j or Amazon Neptune (Gremlin).
- Using ArangoDB or DGraph? Export directly to their native schema formats.
- Building on top of a knowledge graph platform or need semantic reasoning? Use OWL/XML.
- Running both SQL and graph? Export both from the same model and keep them in sync.
All exports are available from the Export button in the workbench toolbar, or via the REST API.