Missing Data in Joins (PostgreSQL + Supabase)
As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021.
With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences.
In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS.
I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.
In the last lesson, I mentioned two things in a very mysterious way:
Sometimes the ordering of
FROMandJOINmatters.There are different kinds of joins available in SQL.
Now we’re going to understand this in a very practical way.
We will write a simple join query.
It will work perfectly.
Then we will make a very small change in our table.
And suddenly… the query will not behave the way we expect.
🎯 Our Goal
We want to:
Show each photo’s URL
Show the username of the person who posted it
And most importantly:
We must show every single photo, no matter what
No matter how many photos exist.
No matter what state they are in.
Every photo must appear in the result.
🧠 How We Think About the Join
We start with the photos table.
Then for each user_id inside photos, we look into the users table.
If we find a matching users.id, we attach that user’s data.
From all that combined data, we only care about:
urlusername
So we ignore everything else.
Simple.
✅ Writing the Query
Here’s the query:
SELECT url, username
FROM photos
JOIN users
ON photos.user_id = users.id;
We run it.
It works perfectly.
We see all 20 photos.
Each photo has a username.
Everything looks correct.
But here is something very important:
👉 Every photo currently has a valid user_id that matches a real user.
That is why this worked.
🔧 Now Let’s Break It
Now we will insert a new photo.
This photo will have:
url = 'banner.jpg'user_id = NULL
INSERT INTO photos (url, user_id)
VALUES ('banner.jpg', NULL);
What does NULL mean?
It means:
No user owns this photo
No user posted this photo
Is this allowed?
Yes.
Even if we have ON DELETE CASCADE, that only applies when deleting a user.
We are not deleting anything.
We are simply inserting a photo that does not reference any user.
This does not break any database rules.
▶️ Run the Same Query Again
Now we run this again:
SELECT url, username
FROM photos
JOIN users
ON photos.user_id = users.id;
The query runs successfully.
But something strange happens.
The new photo (banner.jpg) does NOT appear in the result.
We only see the original 20 photos.
❗ Why Did This Happen?
Remember our goal:
Show every photo no matter what.
But now we are not showing all photos.
We are only showing photos that are connected to a user.
So what’s happening?
The problem is that JOIN by default is an INNER JOIN.
An INNER JOIN only returns rows where the condition matches on both tables.
That means:
If
photos.user_id = users.id→ show itIf there is no match → do not show it
If
user_idis NULL → it will not match anything
Very important:
NULL does not equal anything.
Not even another NULL.
So this condition:
photos.user_id = users.id
will fail if user_id is NULL.
And because it fails, that row is removed from the result.
🚨 The Real Problem
Our query is no longer fulfilling our goal.
We said:
Show every photo.
But we are actually showing:
Only photos that have a matching user.
This is a big difference.
In real applications, this matters a lot.
Maybe we are:
Counting total photos
Calculating total storage
Showing company-owned photos
If we miss some rows, our logic becomes incorrect.
🤔 So What Now?
We need to ask two important questions:
Why is that photo not being listed?
→ Because INNER JOIN removes non-matching rows.How can we join photos and users but still show every photo?
That’s what we’ll solve next — by using a different type of join.
This is one of the most important lessons in SQL:
Sometimes your query runs successfully…
But it silently removes data you expected to see.
Always make sure your query fulfills your original goal exactly.