Skip to content

Data Models and Query Languages

These notes compare common data models, explain where each one fits, and introduce declarative queries, MapReduce, property graphs, and triple stores.

In the relational model, data is organized into relations, called tables in SQL. Each relation is an unordered collection of tuples, called rows in SQL.

Relations work well when data has a regular structure and relationships can be represented using foreign keys and joins.

Different applications have different requirements. Common reasons for using a non-relational database include:

  • A need for greater scalability, very large datasets, or high write throughput.
  • Specialized query operations that are not well supported by the relational model.
  • A need for a more flexible or expressive data model.

Object-oriented application code usually represents data as objects. These objects often map naturally to JSON documents, but not always to tables, rows, and columns.

This difference creates an object-relational mismatch: the application needs a translation layer between its objects and the relational database. Object-relational mapping tools such as Active Record and Hibernate reduce the boilerplate required for this translation.

A self-contained structure such as a résumé can fit naturally in one JSON document. Document databases such as MongoDB and CouchDB support this model.

In a relational database, the same résumé may be normalized into separate tables for the person, positions, education, and contact details. Those tables are then connected using foreign keys.

diagramsnet

The document model has good data locality for one-to-many tree structures because related information can be stored together. Relational databases are usually a better fit when many-to-one and many-to-many relationships are common and joins are important.

Choosing between document and relational models

Section titled “Choosing between document and relational models”

Consider relationships such as many people living in one region or working in one industry. In a relational model, records can refer to a shared row using a foreign key, and a join retrieves the related data.

Document databases are optimized for self-contained documents, so join support may be limited or implemented differently. If the database cannot perform the required join, application code may need multiple queries, which adds complexity.

The best model is often the one that makes the application code simpler:

  • A document-shaped application can read one document without joining several tables.
  • A highly connected application benefits from foreign keys and database joins.
  • Emulating joins with several application requests can add latency and consistency problems.
  • Other concerns—such as fault tolerance, concurrency, and consistency—also affect the choice.

Document databases are often called schemaless, but this can be misleading. The data still has a structure; the database simply may not enforce it.

ApproachMeaningSimilar idea
Schema-on-readApplication code interprets and validates the structure when reading data.Dynamic runtime checking
Schema-on-writeThe database checks written data against an explicit schema.Static type checking

Suppose an older document contains name, while new documents contain first_name and last_name.

With schema-on-read, old documents can remain unchanged. The application handles both versions when reading:

Read old and new name formats
const firstName = user.first_name ?? user.name?.split(" ")[0];

With a relational schema, a migration can add the new columns and update existing rows. Future writes then follow the new schema.

Schema-on-read is useful when:

  • Records are heterogeneous and do not all have the same structure.
  • Data comes from an external system whose format the application does not control.
  • A third-party webhook payload must be stored even when its fields evolve.

A document database can store an entire document together, which may improve performance when the application usually reads most of that document.

The same locality can become a disadvantage:

  • Reading one small field may still require loading a large document.
  • Some storage engines may rewrite a large portion of a document during an update.
  • Large documents can make frequently updated data expensive.

The boundary between relational and document databases is becoming less strict:

  • PostgreSQL and other relational databases support JSON data.
  • Some document databases support references, lookup operations, or client-side joins.

The models remain different, but many databases now borrow useful features from each other.

Imperative vs. declarative query languages

Section titled “Imperative vs. declarative query languages”

An imperative query describes the operations to perform and the order in which to perform them. For example, code can loop through every animal and add sharks to a result list.

A declarative query describes the required result and its conditions, but not the exact steps used to produce it.

Declarative SQL query
SELECT name
FROM animals
WHERE family = 'shark';

The database is free to choose indexes, join orders, and other execution details. This gives the query optimizer more opportunities to improve performance.

Declarative languages also appear outside databases. CSS selectors and XPath specify which HTML elements to select, while imperative JavaScript can loop through DOM elements to find the same result.

MapReduce is a programming model for processing large amounts of data across multiple machines. Datastores such as MongoDB and CouchDB have provided it as a way to run read-only processing across many documents.

It sits between fully declarative and fully imperative approaches: the developer supplies map and reduce functions, while the framework decides where, when, and in what order to run them.

diagramsnet

  1. The map function processes each input and emits a key-value pair.
  2. The framework groups all values that have the same key.
  3. The reduce function combines each group into a smaller result.

Map and reduce functions should be pure functions: they use only their input, do not run additional database queries, and have no side effects. This lets the framework run them on any machine, in different orders, and retry them after failures.

  • MapReduce is a low-level model for distributed execution.
  • Coordinating map and reduce functions is often harder than writing one query.
  • A declarative query gives the optimizer more freedom to improve execution.

Graph models are useful when many-to-many relationships are common and connections are as important as the data itself. A graph contains:

  • Vertices: Nodes or entities.
  • Edges: Relationships between vertices.

Examples include:

  • Social graph: People are vertices; friendships are edges.
  • Web graph: Pages are vertices; hyperlinks are edges.
  • Transport network: Junctions are vertices; roads or railway lines are edges.

Graph algorithms include shortest-path search and PageRank. A graph can also contain different types of vertices and edges. For example, a social application might connect people, locations, events, check-ins, posts, and comments in one graph.

The following sample graph will be used in the queries below. It mixes people, a company, and a city in the same graph:

plantuml

A property graph contains:

  • A unique identifier and key-value properties for each vertex.
  • A unique identifier, tail vertex, head vertex, label, and properties for each edge.

Any vertex can connect to another vertex. Applications can follow incoming and outgoing edges, filter by relationship labels, and extend the graph with new vertex or edge types.

Cypher, created for Neo4j, is a declarative query language for property graphs.

Create part of the sample graph:

Create vertices and relationships
CREATE (alice:Person {name: 'Alice'}),
(bob:Person {name: 'Bob'}),
(acme:Company {name: 'Acme'}),
(alice)-[:KNOWS]->(bob),
(alice)-[:WORKS_AT]->(acme),
(bob)-[:WORKS_AT]->(acme);

Find Alice’s direct friends:

Find direct friends
MATCH (:Person {name: 'Alice'})-[:KNOWS]->(friend:Person)
RETURN friend.name;

Find people who work at the same company as Alice:

Find colleagues
MATCH (:Person {name: 'Alice'})-[:WORKS_AT]->(company)<-[:WORKS_AT]-(colleague)
RETURN colleague.name, company.name;

Find the shortest chain of KNOWS relationships from Alice to Carol:

Find a shortest path
MATCH path = shortestPath(
(:Person {name: 'Alice'})-[:KNOWS*]-(:Person {name: 'Carol'})
)
RETURN path;

A triple store represents information as three-part statements:

subject → predicate → object
Jim → likes → bananas

The subject acts like a graph vertex. The object can be:

  • A value, where the predicate and object form a property of the subject.
  • Another vertex, where the predicate acts as the edge between them.

SPARQL is a declarative query language for triple stores that use the RDF data model. SPARQL predates Cypher, although both support graph-style pattern matching.

The same sample facts can be visualized as RDF triples. Literal values such as "Alice" describe a subject, while resource objects such as ex:Acme connect two subjects:

plantuml

Find the names of people Alice knows:

Find people Alice knows
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?friendName
WHERE {
ex:alice foaf:knows ?friend .
?friend foaf:name ?friendName .
}

Find all people who work at Acme:

Find Acme employees
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?personName
WHERE {
?person ex:worksAt ex:Acme ;
foaf:name ?personName .
}
ORDER BY ?personName
ModelBest fitMain trade-off
DocumentMostly self-contained, tree-shaped dataCross-document relationships can be harder
RelationalRegular data with joins and constraintsObject-shaped data may need translation and normalization
GraphHighly connected data and relationship traversalLess natural for simple document or tabular workloads

Document and graph databases often do not enforce one fixed schema, which can help applications adapt to changing requirements. Each model also has its own query approaches, including SQL, aggregation pipelines, Cypher, SPARQL, Datalog, and MapReduce.

No single model fits every problem. Some domains need specialized storage and queries—for example, genome sequence matching or scientific analysis across hundreds of petabytes. At that scale, custom data models may be necessary to keep queries practical and hardware costs under control.