Data AnalyticsSQL for Analyticsbeginner
Updated:

SQL Self Joins for Data Analysts

4 min read

Some questions need a table joined to itself: employees and their managers, or comparing one row to another. Learn self joins with clear analyst examples.

TL;DR – Quick Answer

A self join is a regular join where a table is joined to itself using two different aliases, letting you relate rows within the same table. Analysts use it when a table references itself — such as employees whose manager_id points to another employee — or to compare one row against another in the same table. You give the table two aliases so SQL can treat the single table as two logical copies.

On This Page

Most joins connect two different tables. But sometimes the relationship you need to report on lives inside a single table. An employees table where each row has a manager_id pointing at another employee. A table of orders where you want to compare each order to the same customer's previous one. In these cases you join the table to itself — a self join. It confuses beginners because the two "tables" are physically the same, but once you see it as two logical copies with two names, it becomes routine.

This tutorial follows joins explained; make sure INNER and LEFT joins are solid first. It sits in the intermediate stretch of the SQL for analytics path.

A table that references itself

The classic self-join dataset is an employee table where managers are also employees:

-- employees
emp_id | emp_name | manager_id | department
-------+----------+------------+-----------
1      | Aarti    | NULL       | Sales
2      | Bhaskar  | 1          | Sales
3      | Chitra   | 1          | Sales
4      | Devan    | 2          | Sales
5      | Esha     | 2          | Sales

manager_id refers back to an emp_id in the same table. Aarti has no manager (she is at the top). Bhaskar and Chitra report to Aarti. Devan and Esha report to Bhaskar. To produce a report of "employee and their manager's name", you must look up each manager_id in the same table.

Writing the self join

Give the table two aliases — e for the employee, m for the manager — and join them:

SELECT e.emp_name  AS employee,
       m.emp_name  AS manager
FROM employees e
LEFT JOIN employees m
    ON e.manager_id = m.emp_id;
employee | manager
---------+--------
Aarti    | NULL
Bhaskar  | Aarti
Chitra   | Aarti
Devan    | Bhaskar
Esha     | Bhaskar

Read the ON clause carefully: each employee row's manager_id is matched to a manager row's emp_id. The two aliases let SQL treat the one table as two, so e.emp_name and m.emp_name come from different "copies". The LEFT JOIN keeps Aarti even though her manager_id is NULL — an INNER JOIN would silently drop the top of the hierarchy, which is a common bug in org-chart reports.

Counting reports per manager

Combine a self join with grouping to answer "how many people does each manager have?":

SELECT m.emp_name           AS manager,
       COUNT(e.emp_id)      AS direct_reports
FROM employees e
JOIN employees m ON e.manager_id = m.emp_id
GROUP BY m.emp_name
ORDER BY direct_reports DESC;
manager | direct_reports
--------+---------------
Aarti   | 2
Bhaskar | 2

Here an INNER JOIN is correct because we only want people who actually manage someone. This kind of "supervisor span" report is a genuine HR-analytics deliverable.

Comparing rows within one table

Self joins also compare rows to each other. Suppose you want pairs of employees in the same department earning within a comparison — for example, listing every pair of colleagues in the same department:

SELECT a.emp_name AS employee_1,
       b.emp_name AS employee_2,
       a.department
FROM employees a
JOIN employees b
    ON a.department = b.department
   AND a.emp_id < b.emp_id;

The condition a.emp_id < b.emp_id is the trick that prevents an employee from pairing with themselves and stops each pair appearing twice (Aarti-Bhaskar and Bhaskar-Aarti). This "less-than on the key" pattern appears whenever you generate unique pairings from a single table.

Practical usage

In analytics, self joins show up most in three situations: resolving hierarchies (employee-manager, category-parent category), building pairwise comparisons (customers in the same city, products in the same price band), and simple duplicate detection. For hierarchies that go several levels deep — manager of manager of manager — a single self join is not enough, and you graduate to a recursive CTE. But for one level of relationship, a self join is the cleanest tool.

Duplicate detection is worth a concrete look, because it is a routine data-quality task. Suppose you suspect the same customer was entered twice under different IDs but with an identical email. A self join surfaces the offending pairs:

SELECT a.emp_id AS id_1, b.emp_id AS id_2, a.emp_name
FROM employees a
JOIN employees b
    ON a.emp_name = b.emp_name
   AND a.emp_id < b.emp_id;

Again the a.emp_id < b.emp_id inequality keeps each duplicate pair once. For pure "how many duplicates exist" counting, GROUP BY emp_name HAVING COUNT(*) > 1 is simpler, but the self join has the advantage of showing both conflicting IDs side by side so you can decide which record to keep.

Common mistakes

  • Forgetting distinct aliases. Referencing the table name twice without aliases makes every column ambiguous. Always give the two copies different names.
  • Using INNER JOIN and losing the top of a hierarchy. Top-level rows have a NULL parent key; an INNER self join drops them. Use LEFT JOIN when the report must include them.
  • Producing duplicate or self pairs. Comparing rows without an inequality like a.id < b.id yields self-matches and mirror-image duplicates. Add the inequality to keep pairs unique.
  • Assuming one join covers deep hierarchies. A self join reaches exactly one level up or down. Multi-level trees need recursion, not repeated joins.

In interviews

The employee-manager self join is one of the most-asked analyst SQL questions: "list each employee with their manager's name" or "find employees who earn more than their manager". The latter compares two aliases on salary and is a reliable filter for whether a candidate truly understands that the two aliases are independent copies. Expect a follow-up on why you chose LEFT versus INNER, and on how you would handle a hierarchy deeper than one level, which opens the door to CTEs.

Where this fits in your learning path

Self joins extend joins explained to relationships inside a single table. When comparisons get more complex — "employees above the department average" — you often reach for subqueries instead, and for multi-level hierarchies you move to CTEs. All are staples of the data analyst roadmap.

Frequently Asked Questions

What is a self join in SQL?
A self join joins a table to itself by listing it twice with two different aliases. It is not a special join type; it uses the same INNER or LEFT JOIN syntax. It is needed when rows in a table relate to other rows in the same table, like an employee whose manager is also an employee.
Why do I need two aliases in a self join?
Because SQL must treat the single physical table as two logical copies to match rows against each other. Without distinct aliases, references like employee.name and manager.name would be ambiguous. The aliases, such as e and m, tell the database which copy each column comes from.
Should a self join be INNER or LEFT?
It depends on whether unmatched rows should be kept. An INNER self join on manager_id drops top-level employees who have no manager. A LEFT self join keeps them, showing NULL for the manager. Choose LEFT when the report must include rows that lack a partner.
Can I use a self join to find duplicates?
Yes. Joining a table to itself on the columns that should be unique, while requiring different primary keys, surfaces rows that share those values. Analysts also use GROUP BY with HAVING COUNT(*) > 1 for the same purpose, which is often simpler for pure duplicate detection.
Is a self join slow on large tables?
It can be, because it effectively scans the table against itself, so performance depends on indexes on the join columns. On large datasets ensure the join key is indexed. For hierarchies many levels deep, recursive CTEs are often clearer and more efficient than repeated self joins.

Want to Build Your Career in Data Analytics with AI?

Join CodeBegun and train with working industry engineers — Explore the Data Analytics program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 16 July 2026 LinkedIn
Chat with us