9.7. One-to-One Relationships
Table of Contents
Understanding One-to-One Relationships
One-to-one relationships are a specific way to connect data between two tables in a relational database. In this chapter, you will learn what they are, why they are useful, and how to design and query them with clear examples.
What Is a One-to-One Relationship?
A one-to-one relationship connects two tables so that each row in the first table is related to at most one row in the second table, and each row in the second table is related to at most one row in the first.
You can think of it as "exactly one or zero related row on each side".
Examples from real applications:
| Table A | Table B | Relationship idea |
|---|---|---|
users | user_profiles | Each user has at most one profile |
persons | passports | Each person has at most one passport |
employees | employee_salaries | Each employee has at most one salary row |
customers | customer_settings | Each customer has at most one settings row |
If a one-to-one relationship is mandatory, then each row in table A must have exactly one row in table B, and vice versa. If it is optional, then some rows in table A might not have a related row in table B.
Core definition
In a one-to-one relationship, if row $a$ from table A is related to row $b$ from table B, then:
- $a$ is not related to any other row from B, and
- $b$ is not related to any other row from A.
When Should You Use a One-to-One Relationship?
One-to-one relationships are less common than one-to-many, but they are very useful in some situations.
Separating Optional or Rare Data
Sometimes, most rows do not need certain columns. You can move these columns into a separate table.
Example:
- Table
userscontains basic info:id,email,password_hash. - Only a small percentage of users fill out detailed profile information like
bio,date_of_birth,avatar_url.
Instead of having many NULL columns in users, you can:
- Keep
usersfor core information. - Create
user_profilesfor extra information, in a one-to-one relationship.
This makes reads and writes on the users table smaller and can be better for performance.
Separating Sensitive Information
You might want to separate sensitive or restricted data from general data.
Example:
employeestable: public or commonly used data likeid,name,department_id.employee_private_infotable:ssn,tax_id,medical_info.
You can tighten permissions on employee_private_info so only some parts of your application or some database users can read it.
Splitting Very Large Tables
If one table is growing "wide" with many columns, some of which are rarely used, you can split it into two tables connected with a one-to-one relationship.
Example:
orderstable: core fields used all the time likeid,user_id,status,total_amount.order_metadatatable: rarely needed extra fields likenotes,internal_comments,tracking_history_json.
This can keep the main table smaller and faster to query.
Design Options for One-to-One Relationships
There are two main patterns to model a one-to-one relationship:
- Shared primary key pattern.
- Unique foreign key pattern.
You should understand both and know when to use each.
Pattern 1: Shared Primary Key
In the shared primary key pattern, the second table uses the same value as the primary key of the first table.
Example: users and user_profiles.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
CREATE TABLE user_profiles (
user_id INTEGER PRIMARY KEY REFERENCES users(id),
full_name TEXT,
bio TEXT,
date_of_birth DATE
);Here:
users.idis the primary key ofusers.user_profiles.user_idis:- a primary key of
user_profiles, - a foreign key that references
users(id).
Since user_id is primary key in user_profiles, there can be at most one profile row for each user.
This pattern is very strict. It makes the relationship very clear:
- One profile belongs to exactly one user.
- A profile cannot exist without a user.
You can insert a user first, then optionally create a profile later.
Key points:
| Feature | Description |
|---|---|
| Identity | Profile uses the same ID as the user |
| Enforces max one profile per user | Yes, by primary key |
| Can a profile exist without a user? | No, foreign key prevents it |
| Typical use | Strong ownership, same lifecycle as parent |
Pattern 2: Unique Foreign Key
In the unique foreign key pattern, the second table has its own primary key, but also has a foreign key that is marked as UNIQUE.
Example:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE REFERENCES users(id),
full_name TEXT,
bio TEXT,
date_of_birth DATE
);Here:
user_profiles.idis the primary key ofuser_profiles.user_profiles.user_idis:- a foreign key pointing to
users.id, - declared as
UNIQUE.
The UNIQUE constraint on user_id ensures that one user can have at most one profile row.
This pattern allows the child table to have its own identity (its own id). It can be useful if:
- The profile table may be referenced by other tables.
- You want a separate
idthat is not tied directly to the parent table.
Key points:
| Feature | Description |
|---|---|
| Identity | Child table has its own primary key |
| Enforces max one profile per user | Yes, due to UNIQUE (user_id) |
| Can a profile exist without a user? | No, foreign key prevents it |
| Typical use | Slightly more flexible, extra relationships |
Important rule
To enforce a one-to-one relationship:
- Either make the foreign key column a
PRIMARY KEYin the child table, - Or add a
UNIQUEconstraint on the foreign key column in the child table.
If you forget theUNIQUEorPRIMARY KEYconstraint, the relationship becomes one-to-many.
Choosing the Parent and Child Table
In a one-to-one relationship, you still think in terms of a parent and a child.
Usually:
- The parent table represents the main entity.
Example:users,employees,persons. - The child table contains additional data about that entity.
Example:user_profiles,employee_private_info.
Typical rules:
- The parent is created first.
- The child row can be created later, updated independently, or sometimes deleted independently.
- The child row references the parent with a foreign key.
You choose which table is the parent based on the meaning in your application, not just on the database structure.
Examples of One-to-One Relationship Designs
Example 1: User and User Profile
This is a common design in web applications.
Shared primary key version:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
CREATE TABLE user_profiles (
user_id INTEGER PRIMARY KEY REFERENCES users(id),
full_name TEXT,
bio TEXT,
avatar_url TEXT
);Usage:
- Create a user:
INSERT INTO users (email, password_hash)
VALUES ('alice@example.com', 'hash123')
RETURNING id;
Assume this returns id = 1.
- Later, create a profile for that user:
INSERT INTO user_profiles (user_id, full_name, bio, avatar_url)
VALUES (1, 'Alice Doe', 'Loves cats and coding', 'https://example.com/avatar1.png');
There can never be a second row in user_profiles with user_id = 1, because user_id is a primary key.
Example 2: Person and Passport
A real world example for strong one-to-one.
CREATE TABLE persons (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE passports (
person_id INTEGER PRIMARY KEY REFERENCES persons(id),
passport_number TEXT NOT NULL UNIQUE,
country_code CHAR(2) NOT NULL
);Here:
- Each
passports.person_idis both a primary key and a foreign key. - Each person can have at most one passport row in this table.
Enforcing Optional vs Mandatory One-to-One
A one-to-one relationship can be:
- Optional: The child row does not have to exist.
- Mandatory: The child row must exist.
Optional One-to-One
This is the default in most designs. The foreign key in the child table can be NULL or missing row.
Example:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE user_profiles (
user_id INTEGER PRIMARY KEY REFERENCES users(id),
full_name TEXT
-- no NOT NULL here makes the relationship optional in practice
);
Actually, the relationship is optional because you do not need to create a row in user_profiles at all. A user without a profile simply has no row in user_profiles.
Mandatory One-to-One
If you want to guarantee that every parent has a child row, the database cannot directly enforce this in a simple way across two tables. The foreign key ensures the child references a valid parent, but it does not ensure that each parent has a child.
Common approaches:
- Use shared primary key and always create both rows at the same time inside a transaction.
- Use a database constraint such as a trigger to check that every parent has a child.
- In many real systems, this rule is enforced at the application level, not by the database.
For beginners, it is usually enough to:
- Understand that the database naturally supports "optional" one-to-one.
- Recognize that "mandatory" one-to-one needs extra work, usually in the application code.
Querying One-to-One Relationships
A one-to-one relationship is queried with JOINs, similar to one-to-many relationships, but you expect at most one row on each side.
Basic JOIN
Example with users and user_profiles:
SELECT
u.id,
u.email,
p.full_name,
p.bio
FROM users AS u
LEFT JOIN user_profiles AS p
ON p.user_id = u.id
WHERE u.id = 1;Explanation:
LEFT JOINreturns the user even if there is no profile.p.full_nameandp.biowill beNULLif the user has no profile.
If you only want users that have a profile, you can use INNER JOIN:
SELECT
u.id,
u.email,
p.full_name
FROM users AS u
INNER JOIN user_profiles AS p
ON p.user_id = u.id;Checking Whether the Child Exists
You can check whether each user has a profile:
SELECT
u.id,
u.email,
(p.user_id IS NOT NULL) AS has_profile
FROM users AS u
LEFT JOIN user_profiles AS p
ON p.user_id = u.id;Example result:
| id | has_profile | |
|---|---|---|
| 1 | alice@example.com | true |
| 2 | bob@example.com | false |
| 3 | carol@example.com | true |
Inserting, Updating, and Deleting in One-to-One
Inserting Parent and Child
- Insert parent, get its ID.
- Insert child using that ID as the foreign key.
Example:
-- Insert parent
INSERT INTO users (email, password_hash)
VALUES ('bob@example.com', 'hash456')
RETURNING id;
Assume the returned id is 2.
-- Insert child
INSERT INTO user_profiles (user_id, full_name)
VALUES (2, 'Bob Smith');Updating the Child
Updating the child is like updating any other table.
UPDATE user_profiles
SET bio = 'Backend developer and musician'
WHERE user_id = 2;Deleting Parent or Child
Deleting the child is simple:
DELETE FROM user_profiles
WHERE user_id = 2;Deleting the parent can cause referential integrity problems if the child still exists. The foreign key constraint controls what happens.
Common options:
ON DELETE RESTRICT(default in many databases): block deletion if child exists.ON DELETE CASCADE: automatically delete the child when the parent is deleted.ON DELETE SET NULL: set the foreign key column toNULLin the child when the parent is deleted, which is not possible if the column is also a primary key.
Example with cascading delete:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE user_profiles (
user_id INTEGER PRIMARY KEY
REFERENCES users(id) ON DELETE CASCADE,
full_name TEXT
);Now:
DELETE FROM users WHERE id = 1;
This will automatically delete the user_profiles row where user_id = 1 if it exists.
Foreign key delete rule
If you delete a parent row that has a child row and there is no ON DELETE rule, the database will reject the delete to protect data integrity.
Common safe choice: ON DELETE CASCADE when the child data belongs strictly to the parent.
One-to-One vs One-to-Many: Design Considerations
Sometimes a relationship that looks one-to-one can become one-to-many in the future. It is important to think about this when designing your schema.
Example: Employee and Address
You might first think:
- Each employee has one address.
So you design:
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE employee_addresses (
employee_id INTEGER PRIMARY KEY REFERENCES employees(id),
address_line TEXT NOT NULL,
city TEXT NOT NULL,
country TEXT NOT NULL
);Later, the business rules change:
- Employees can have multiple addresses, for example, home and office.
Now your one-to-one is no longer correct. You need a one-to-many relationship.
New design:
CREATE TABLE employee_addresses (
id SERIAL PRIMARY KEY,
employee_id INTEGER NOT NULL REFERENCES employees(id),
address_type TEXT NOT NULL,
address_line TEXT NOT NULL,
city TEXT NOT NULL,
country TEXT NOT NULL
);
There is no UNIQUE on employee_id, so each employee can have multiple rows in employee_addresses.
Lesson:
- Be careful when you think a relationship is one-to-one. Ask whether it might become one-to-many later.
- If you are not sure, it may be safer to design a one-to-many relationship. You can still treat it as one-to-one on the application side for now.
Performance Notes
One-to-one relationships have some performance characteristics you should know.
Table Width and Query Speed
Splitting frequently used and rarely used columns into separate tables can:
- Make the main table smaller,
- Make index pages smaller,
- Improve cache hit rates in the database,
- Potentially improve query speed for common queries that need only the main table.
However, this benefit comes at the cost of extra JOINs when you do need the extra data.
Indexes
The primary key and unique constraints used in one-to-one designs automatically create indexes, which are helpful for lookups.
Typical useful indexes:
- On the child table, the foreign key column is already indexed because it is either
PRIMARY KEYorUNIQUE. - On the parent table, the primary key index is used when joining.
You usually do not need extra indexes just to support a one-to-one relationship.
Summary
In this chapter, you learned:
- What a one-to-one relationship is, and how it differs from one-to-many.
- When it makes sense to use one-to-one, for example to separate optional, sensitive, or rarely used data.
- Two main design patterns:
- Shared primary key in the child table.
- Unique foreign key in the child table.
- How to query one-to-one relationships using
JOIN. - How to insert, update, and delete data in a one-to-one relationship.
- Design considerations when a relationship might change to one-to-many.
- Basic performance considerations related to table width and indexes.
Understanding one-to-one relationships helps you design cleaner schemas and decide when to split data across multiple tables in your backend applications.
Views: 6
KAHIBARO