Database Design
One-to-One Relationships
Build ER diagrams with one-to-one relationships, understand when to use them, and implement them correctly in BookVault's database schema.
Understanding One-to-One Relationships
One-to-one relationships are the least common type of database relationship. But they solve specific problems elegantly. Think of it this way: each record in Table A connects to exactly one record in Table B, and vice versa.
Why not just put everything in one table then? Good question. Performance is usually the answer. Or security. Sometimes you need to separate data that logically belongs together.
username
created_at
FK user_id
bio
avatar_url
preferences
Each user has exactly one profile. Each profile belongs to exactly one user. That's a one-to-one relationship.
Why separate these? Profile data might include large text fields, images, or complex JSON. Keeping the main users table lean means faster authentication queries.
Common One-to-One Scenarios
Real companies use one-to-one relationships more than you'd think. Here are the most common patterns:
Security Separation
Sensitive data lives in a separate table with different access controls. Think Netflix user accounts vs payment methods.
Performance Split
Frequently accessed data stays separate from rarely used fields. Stripe does this with customer core data vs detailed billing info.
Optional Data
Core entity exists without the related data. Airbnb properties can exist before hosts add detailed descriptions and photos.
Legacy Integration
New system needs to connect with old database tables. Uber probably has this with driver data across different systems.
BookVault Example: Author Details
BookVault's authors table contains basic information. But what about detailed biographies, social media links, and publication history? That's perfect for a one-to-one relationship.
first_name
last_name
nationality
FK author_id
biography
website_url
twitter_handle
awards_json
Why split this? The main authors table stays fast for book listings. Profile data loads only when someone views an author's detailed page. Smart performance optimization.
Implementing One-to-One in SQL
Creating one-to-one relationships requires careful foreign key placement. You have two options, but one is clearly better.
✓ Recommended
Put foreign key in the dependent table (the one that might not exist).
author_profiles.author_id → authors.author_id
Possible but messy
Put foreign key in the main table. Creates nullable columns and complicates queries.
authors.profile_id → author_profiles.profile_id
Here's how to create the BookVault author profile relationship:
-- Create the author_profiles table for BookVault
-- Links to existing authors table via foreign key
CREATE TABLE author_profiles (
profile_id SERIAL PRIMARY KEY,
author_id INTEGER NOT NULL UNIQUE,
biography TEXT,
website_url VARCHAR(255),
twitter_handle VARCHAR(50),
awards_json JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Foreign key creates the one-to-one relationship
CONSTRAINT fk_author_profile
FOREIGN KEY (author_id)
REFERENCES authors(author_id)
ON DELETE CASCADE
);
CREATE TABLE Table "author_profiles" created successfully. Constraint "fk_author_profile" added. Unique constraint on "author_id" enforces one-to-one relationship.
What just happened?
The UNIQUE constraint on author_id prevents multiple profiles per author. The foreign key ensures every profile connects to a valid author. Try this: insert a profile, then try inserting another profile for the same author - it'll fail.
Adding Sample Data
Now populate both tables to see the relationship in action:
-- Insert authors first (parent table)
INSERT INTO authors (first_name, last_name, nationality) VALUES
('J.K.', 'Rowling', 'British'),
('Stephen', 'King', 'American'),
('Agatha', 'Christie', 'British');
-- Then insert profiles for some authors
INSERT INTO author_profiles (author_id, biography, website_url, twitter_handle) VALUES
(1, 'British author best known for the Harry Potter series...',
'https://www.jkrowling.com', '@jk_rowling'),
(2, 'American author of horror, supernatural fiction, and fantasy...',
'https://stephenking.com', '@StephenKing');
INSERT 0 3 INSERT 0 2 Authors table now has 3 records. Author_profiles table has 2 records. Notice: Agatha Christie has no profile yet - that's allowed in one-to-one.
What just happened?
We inserted 3 authors but only 2 profiles. That's perfectly valid - not every author needs a detailed profile immediately. Try this: attempt to insert a second profile for author_id 1 and watch it fail due to the UNIQUE constraint.
Querying One-to-One Relationships
Querying one-to-one relationships looks similar to other joins, but with important differences. You'll often use LEFT JOIN because the dependent table might not have matching records.
-- Get all authors with their profile information
-- LEFT JOIN because not all authors have profiles yet
SELECT
a.first_name,
a.last_name,
a.nationality,
p.biography,
p.website_url,
p.twitter_handle
FROM authors a
LEFT JOIN author_profiles p ON a.author_id = p.author_id
ORDER BY a.last_name;
first_name | last_name | nationality | biography | website_url | twitter_handle -----------|-----------|-------------|------------------------|--------------------|-------------- Agatha | Christie | British | NULL | NULL | NULL Stephen | King | American | American author of... | stephenking.com | @StephenKing J.K. | Rowling | British | British author best... | jkrowling.com | @jk_rowling
What just happened?
LEFT JOIN returned all authors, even Agatha Christie who has no profile. Her profile columns show NULL. If we used INNER JOIN instead, she wouldn't appear in results at all. Try this: change to INNER JOIN and see the difference.
Filtering for Complete Records
Sometimes you only want authors who have detailed profiles:
-- Find authors who have complete profile information
-- INNER JOIN excludes authors without profiles
SELECT
a.first_name || ' ' || a.last_name AS author_name,
LENGTH(p.biography) AS bio_length,
CASE
WHEN p.website_url IS NOT NULL THEN 'Has website'
ELSE 'No website'
END AS website_status
FROM authors a
INNER JOIN author_profiles p ON a.author_id = p.author_id
WHERE p.biography IS NOT NULL;
author_name | bio_length | website_status --------------|------------|--------------- J.K. Rowling | 52 | Has website Stephen King | 59 | Has website
Common Mistake
Using INNER JOIN when you meant LEFT JOIN. This accidentally filters out records from the main table. Always think: "Do I want all authors, or only authors with profiles?" Choose your JOIN type accordingly.
When NOT to Use One-to-One
Honestly, most developers overuse one-to-one relationships. Here's when to avoid them:
Data Insight
85% of one-to-one relationships in junior developer projects should actually be additional columns in the main table. Only separate when you have a specific performance, security, or architectural reason.
❌ Bad Reason
"This table has too many columns." That's not a real problem unless you're hitting database limits.
✓ Good Reason
"Profile queries are 40% slower when biography text is included in the main table."
The BookVault author profile example is borderline. In a real system, you'd measure query performance before splitting. But it's educationally perfect for understanding one-to-one concepts.
Alternative: JSON Columns
Modern databases support JSON columns. Sometimes that's cleaner than one-to-one relationships:
-- Alternative: store profile data as JSON in authors table
-- Simpler structure but less queryable
ALTER TABLE authors
ADD COLUMN profile_data JSON;
-- Update with profile information
UPDATE authors
SET profile_data = '{"biography": "British author best known...", "website": "jkrowling.com"}'
WHERE author_id = 1;
ALTER TABLE UPDATE 1 Profile data now stored as JSON in the main authors table. Trade-off: simpler structure but harder to query specific profile fields.
JSON columns work well when profile data is mostly display-only. Use separate tables when you need to query, index, or join on profile fields frequently.
Real-World Examples
Major platforms use one-to-one relationships strategically:
| Company | Main Table | Related Table | Reason |
|---|---|---|---|
| Netflix | users | viewing_preferences | Performance - login doesn't load heavy preference data |
| Stripe | customers | customer_tax_info | Security - tax data has different access rules |
| GitHub | repositories | repo_statistics | Performance - stats calculated separately from core repo data |
| Spotify | artists | artist_analytics | Optional - not all artists have detailed analytics enabled |
Notice the pattern: one-to-one relationships solve specific architectural problems. They're not just "extra tables for organization."
Quiz
1. BookVault needs a one-to-one relationship between authors and author_profiles. Where should you place the foreign key?
2. What database constraint enforces the "one" side of a one-to-one relationship?
3. BookVault wants to display all authors, including those without detailed profiles. Which JOIN type should you use?
Up Next
One-to-Many Relationships
Master the most common database relationship pattern and learn how BookVault connects customers to their multiple orders.