🏗️
From Confusion to Clarity: My Journey with Database Design
TL;DR
- Why I initially dismissed database design as "boring theory"
- How my "just get it working" approach eventually backfired
- The moment I realized proper database design saves time and headaches
- Practical SQL Server examples that made it click for me
"Why are we learning ER diagrams again?"
That was my first reaction in university when introduced to database design. Tables, relationships, cardinalities, foreign keys—it all felt abstract and painfully theoretical. I'd rather jump straight into code. After all, that's what real developers do, right?
But fast forward a few years into my career, and guess what? Database design isn't just a box-ticking academic exercise. It's the foundation of every stable, scalable application I've helped build.
Early Days: "Just get it working"
In my first few projects, I used databases like glorified Excel sheets. Need a users table? Just throw in some columns. Relationships? Eh, maybe just store the user_id in a posts table without foreign keys. Normalization? Sounds like overengineering.
Here's what my early "user management" looked like in SQL Server:
-- My terrible early approach
CREATE TABLE users (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(100),
email NVARCHAR(100),
posts NVARCHAR(MAX), -- Storing comma-separated post IDs
roles NVARCHAR(MAX), -- Storing comma-separated roles
last_login DATETIME,
profile_data NVARCHAR(MAX) -- JSON blob of everything else
);
-- Adding data looked like this mess:
INSERT INTO users (name, email, posts, roles, profile_data)
VALUES (
'John Doe',
'john@example.com',
'1,5,12,25', -- Post IDs as a string
'admin,editor', -- Roles as a string
'{"bio": "Developer", "avatar": "pic.jpg", "settings": {...}}'
);
It worked. Until it didn't.
As the app grew, so did the bugs. Data inconsistency. Orphaned records. Awkward queries. Feature changes became slow and risky. That's when I realized: design matters.
The Awakening: Proper Database Design
After enough pain, I finally learned to design databases properly. Here's what the same user management system looks like with proper normalization:
-- Properly normalized approach
CREATE TABLE users (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(100) NOT NULL,
email NVARCHAR(100) UNIQUE NOT NULL,
created_at DATETIME DEFAULT GETDATE(),
last_login DATETIME,
is_active BIT DEFAULT 1
);
CREATE TABLE roles (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(50) UNIQUE NOT NULL,
description NVARCHAR(255)
);
CREATE TABLE user_roles (
user_id INT,
role_id INT,
assigned_at DATETIME DEFAULT GETDATE(),
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
CREATE TABLE posts (
id INT IDENTITY(1,1) PRIMARY KEY,
user_id INT NOT NULL,
title NVARCHAR(255) NOT NULL,
content NTEXT,
created_at DATETIME DEFAULT GETDATE(),
updated_at DATETIME,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
bio NVARCHAR(500),
avatar_url NVARCHAR(255),
website NVARCHAR(255),
location NVARCHAR(100),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
Now I can write clean, efficient queries like:
-- Find all admin users with their post counts
SELECT
u.name,
u.email,
COUNT(p.id) as post_count,
up.bio
FROM users u
INNER JOIN user_roles ur ON u.id = ur.user_id
INNER JOIN roles r ON ur.role_id = r.id
LEFT JOIN posts p ON u.id = p.user_id
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE r.name = 'admin' AND u.is_active = 1
GROUP BY u.id, u.name, u.email, up.bio
ORDER BY post_count DESC;
-- Add a new role to a user (no string manipulation!)
INSERT INTO user_roles (user_id, role_id)
VALUES (123, (SELECT id FROM roles WHERE name = 'editor'));
-- Remove orphaned posts (impossible with the old design)
DELETE FROM posts
WHERE user_id NOT IN (SELECT id FROM users WHERE is_active = 1);
What I'd Tell My Younger Self
If I could go back and talk to that student version of myself, I'd say this:
"Yes, this is hard now. Yes, the diagrams are boring. But this will save you one day."
A well-designed database doesn't just store data—it shapes how features are built, how bugs are tracked, and how teams collaborate.
Final Thought
Today, when I start a new project, the database schema is one of the first things I plan. Not because I love drawing diagrams—but because I've learned that clarity at the core prevents chaos at the edges.
The time you spend designing your database upfront is time you won't spend debugging data inconsistencies later.
<- Back to blog