From Venn Diagram Panic to pgAdmin Pro: How I Finally Understood SQL Joins
- Shital Pilare
- 3 days ago
- 4 min read

Introduction :
Understanding SQL joins is one of those milestones every database learner hits sooner or later. For me, it wasn’t smooth. I could write SELECT queries confidently, filter with WHERE like a pro, and even felt pretty good about grouping and sorting. But the moment I encountered joins, everything fell apart.
Suddenly, I was staring at Venn diagrams, overlapping circles, and abstract explanations that made perfect sense in theory — but absolutely none in practice.
Everything changed when I opened pgAdmin, created tiny tables, and started experimenting. That hands‑on approach turned SQL joins from intimidating to intuitive.
If you are struggling with joins, you are not alone. Here is the practical, jargon-free way I finally wrapped my head around SQL joins using PostgreSQL and pgAdmin.
The Setup: Ditching the Abstract :
The biggest mistake I made initially was trying to learn joins with massive, complicated databases. I couldn't tell if my query was wrong or if the data was just messy.
To fix this, I opened up pgAdmin and created the simplest scenario possible based on real life: Customers and Orders.
I ran these scripts in the pgAdmin Query Tool to set the stage:
SQL QUERY :
-- Create the tables
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
amount INT,
customer_id INT
);
-- Insert some dummy data
INSERT INTO customers (name) VALUES ('Alice'), ('Bob'), ('Charlie'), ('David');
INSERT INTO orders (amount, customer_id) VALUES (100, 1), (250, 1), (50, 3);
Once I ran that, I right-clicked my tables in pgAdmin and viewed the data to make sure I knew exactly what I was working with.
Customers: Alice (ID 1), Bob (ID 2), Charlie (ID 3), and David (ID 4).
Orders: Two orders for Alice, one for Charlie. Crucially, Bob and David have ordered nothing.

Okay, the stage is set. How do we connect them?
The Breakthrough: The "Left Join" Mentality :
The textbook definition usually starts with the INNER JOIN. I think that’s a mistake. The concept that actually made it click for me was the LEFT JOIN.
The LEFT JOIN (The "Bring Everyone" Party) :
Imagine you are hosting a party for your customers. You want a list of every single customer you have, and IF they ordered something, you want to see what it was.
If you just look at the orders table, you miss Bob and David. If you just look at the customers table, you don't see the sales data.
I realized that the table mentioned first (on the left side of the join keyword) is the "main" table.
SQL QUERY :
SELECT customers.name, orders.amount
FROM customers -- This is the "Left" table. We want ALL of these guys.
LEFT JOIN orders ON customers.customer_id = orders.customer_id;
When I ran this in pgAdmin, the result was exactly what I needed to see to understand what was happening.

Look at the results for Bob and David. Because they exist on the Left table (Customers) but have no matching ID in the right table (Orders), PostgreSQL just puts a NULL (a blank placeholder) there.
My realization: A LEFT JOIN says, "Keep everything from the first table no matter what. Try to attach data from the second table if it fits; otherwise, just leave it blank."
The "Ambiguous Column" Panic (And How to Fix It) :
Before I move on, I have to mention the error that almost made me quit. When I first wrote that query, I tried to just type SELECT customer_id... and pgAdmin screamed at me:
ERROR: column reference "customer_id" is ambiguous
I stared at the screen for ten minutes. "What do you mean ambiguous? It's right there!"
The problem, I realized, was that both tables have a column named customer_id. PostgreSQL didn't know if I wanted the ID from the Customer table or the Order table.
The Fix: You have to take ownership. Always specify the table name, like customers.customer_id. It feels verbose, but it saves you from the ambiguity error.
The INNER JOIN (The Exclusive Club) :
Once I understood the Left Join, the Inner Join became obvious.
The Inner Join is the "exclusive club." It only wants rows where there is a match in both tables. If a customer hasn't ordered, they are kicked out of the list. If an order exists without a customer attached (impossible in my setup, but theoretically), it's kicked out too.
SQL QUERY :
SELECT customers.name, orders.amount
FROM customers
INNER JOIN orders ON customers.customer_id = orders.customer_id;
When I ran this, Bob and David (the NULL guys from before) disappeared.

This is the center overlap of that dreaded Venn diagram.
Leveling Up: Making Code Readable with Aliases
After writing customers.customer_id = orders.customer_id about fifty times, my fingers got tired. I started looking for a shortcut and found Aliases.
This is strictly a quality-of-life upgrade, but it makes your code look much more professional. You can rename your tables temporarily using AS.
SQL QUERY :
SELECT c.name, o.amount
FROM customers AS c -- "c" is now the nickname for customers
LEFT JOIN orders AS o ON c.customer_id = o.customer_id; -- "o" is orders
It does the exact same thing, but it’s cleaner, faster to type, and easier to read in the pgAdmin editor.
What about Right and Full Outer Joins?
Honestly? In my day-to-day work with PostgreSQL, I use Left Joins about 80% of the time and Inner Joins the other 19%.
RIGHT JOIN: It's just a Left Join reversed. I find it easier to just flip the order of my tables in the FROM clause and stick to Left Joins just to keep my mental model simple.
FULL OUTER JOIN: This gives you everything. All customers (even if they didn't order) AND all orders (even if they don't have a customer). It leads to lots of NULLs on both sides. It's useful for finding orphaned data, but rare for standard reporting.
Conclusion: Don't Overthink It
Learning joins in pgAdmin wasn't about memorizing syntax; it was about seeing the NULL values appear in real-time.
Don't rely just on reading tutorials. Open pgAdmin, create two tiny tables with 4 rows each, and force them to connect. Once you see how PostgreSQL handles the data that doesn't match up, the whole concept stops being scary.

