Much application development today is done in object-oriented programming languages, which leads to a common criticism of the SQL data model: if data is stored in relational tables, an awkward translation layer is required between the objects in the application code and the database model of tables, rows, and columns. The disconnect between the models is sometimes called an impedance mismatch.
Note
The term impedance mismatch is borrowed from electronics. Every electric circuit has a certain impedance (resistance to alternating current) on its inputs and outputs. When you connect one circuit’s output to another one’s input, the power transfer across the connection is maximized if the output and input impedances of the two circuits match. An impedance mismatch can lead to signal reflections and other troubles.
Object-relational mapping
Object-relational mapping (ORM) frameworks like ActiveRecord and Hibernate reduce the amount of boilerplate code required for this translation layer, but they are often criticized. Some commonly cited problems are as follows:
-
ORMs are complex and can’t completely hide the differences between the two models, so developers still end up having to think about both the relational and the object representations of the data.
-
ORMs are generally used only for OLTP app development (see “Characterizing Transaction Processing and Analytics”); data engineers making the data available for analytics purposes need to work with the underlying relational representation, so the design of the relational schema still matters when using an ORM.
-
Many ORMs work only with relational OLTP databases. Organizations with diverse data systems such as search engines, graph databases, and NoSQL systems might find ORM support lacking.
-
Some ORMs automatically generate relational schemas, but these might be awkward for users who are directly accessing the relational data, and they might be inefficient on the underlying database. Customizing the ORM’s schema and query generation can be complex and negate the benefit of using the ORM in the first place.
-
ORMs make it easy to accidentally write inefficient queries. An example of this is the N+1 query problem. For instance, say you want to display a list of user comments on a page, so you perform one query that returns N comments, each containing the ID of its author. To show the name of each comment’s author, you need to look up the ID in the users table. In handwritten SQL, you would probably perform this join in the query and return the author name along with each comment. However, with an ORM, you might end up making a separate query on the users table for each of the N comments to look up its author, resulting in N+1 database queries in total, which is slower than performing the join in the database. To avoid this problem, you may need to tell the ORM to fetch the author information at the same time as fetching the comments.
Nevertheless, ORMs also have advantages:
-
For data that is well suited to a relational model, some kind of translation between the persistent relational and in-memory object representation is inevitable, and ORMs reduce the amount of boilerplate code required for this translation. Complicated queries may still need to be handled outside of the ORM, but the ORM can help with the simple and repetitive cases.
-
Some ORMs help with caching the results of database queries, which can help reduce the load on the database.
-
ORMs can also help with managing schema migrations and other administrative activities.
The document data model for one-to-many relationships
Not all data lends itself well to a relational representation. Let’s look at an example to explore a limitation of the relational model. Figure 3-1 illustrates how a résumé (a LinkedIn profile) could be expressed in a relational schema. The profile as a whole can be identified by a unique identifier, user_id. Fields like first_name and last_name appear exactly once per user, so they can be modeled as columns on the users table.
Most people have had more than one job (position) in their career, and people may have varying numbers of periods of education and any number of pieces of contact information. One way of representing such one-to-many relationships is to put positions, education, and contact information in separate tables, each with a foreign-key reference to the users table, as in Figure 3-1.
Another way of representing the same information, which is perhaps more natural and maps more closely to an object structure in application code, is as a JSON document, as shown in Example 3-1.
A diagram illustrating a relational database schema representing a LinkedIn profile, with tables for users, positions, education, regions, and contact information showing one-to-many relationships.
Figure 3-1. Using a relational schema to represent a LinkedIn profile
Example 3-1. Representing a LinkedIn profile as a JSON document
{
"user_id": 251,
"first_name": "Barack",
"last_name": "Obama",
"headline": "Former President of the United States of America",
"region_id": "us:91",
"photo_url": "/p/7/000/253/05b/308dd6e.jpg",
"positions": [
{"job_title": "President", "organization": "United States of America"},
{"job_title": "US Senator (D-IL)", "organization": "United States Senate"}
],
"education": [
{"school_name": "Harvard University", "start": 1988, "end": 1991},
{"school_name": "Columbia University", "start": 1981, "end": 1983}
],
"contact_info": {
"website": "https://barackobama.com",
"x": "https://x.com/barackobama"
}
}
Some developers feel that the JSON model reduces the impedance mismatch between the application code and the storage layer. The lack of a schema is often cited as an advantage too; we will discuss this in “Schema flexibility in the document model”. However, there are also problems with JSON as a data encoding format.
The JSON representation has better locality than the multi-table schema in Figure 3-1 (see “Data locality for reads and writes”). If you want to fetch a profile in the relational example, you need to either perform multiple queries (query each table by user_id) or perform a messy multiway join between the users table and its subordinate tables. In the JSON representation, all the relevant information is in one place, making the query both faster and simpler.
The one-to-many relationships from the user profile to the user’s positions, educational history, and contact information imply a tree structure in the data, and the JSON representation makes this tree structure explicit (see Figure 3-2).
Diagram illustrating a tree structure showing one-to-many relationships between a user profile and various attributes like positions and educational history.
Figure 3-2. One-to-many relationships forming a tree structure
Note
A one-to-many relationship is sometimes called one-to-few, since a résumé typically has a small number of positions. If you have a genuinely large number of related items—say, comments on a celebrity’s social media post, of which there could be many thousands—embedding them all in the same document may be too unwieldy, so the relational approach in Figure 3-1 is preferable.