SQL Tutorial – Lesson 23

SQL INNER JOIN – Complete Guide with Examples (2026-27)

The SQL INNER JOIN is the most commonly used JOIN in SQL. It returns only the rows where there is a matching value in both tables based on the ON condition. Rows that exist in one table but have no match in the other table are completely excluded from the result. In this beginner-friendly step-by-step guide you will learn the INNER JOIN syntax, see eight real examples — basic join, selected columns, WHERE filter, ORDER BY, three-table join, GROUP BY with aggregates, INNER JOIN vs LEFT JOIN, and a full reporting query — plus a comparison table and common mistakes to avoid.



✅ What is SQL INNER JOIN?

✅ SQL INNER JOIN returns only the rows where there is a matching value in BOTH tables based on the specified ON condition.

✅ Rows that have no match in either table are excluded from the result — no NULLs for unmatched rows.

INNER JOIN and JOIN (without a qualifier) are identical — most databases default to INNER JOIN when you write just JOIN.

✅ It is the most commonly used JOIN type in real-world SQL queries.

💡 Real-world analogy: Imagine two lists — a list of employees and a list of departments. INNER JOIN finds only the employees who have a department in the second list. Employees with no matching department and departments with no matching employee are both ignored.
🔵 INNER JOIN (This Page)
Returns ONLY rows where there is a match in BOTH tables. Most commonly used JOIN type.
⬅️ LEFT JOIN
Returns ALL rows from the left table. NULL where no match on the right.
➡️ RIGHT JOIN
Returns ALL rows from the right table. NULL where no match on the left.
🔄 FULL OUTER JOIN
Returns ALL rows from BOTH tables. NULL where no match on either side.

✅ SQL INNER JOIN Syntax

SELECT table1.column1, table2.column2, ...
FROM table1
INNER JOIN table2
ON table1.common_column = table2.common_column;

INNER JOIN:- Keyword that triggers the join. Can be written as just JOIN — both are identical.

ON:- Specifies the condition that connects the two tables — usually a foreign key = primary key.

table1.column:- Always prefix column names with the table name or alias to avoid ambiguity.

With Table Aliases (recommended):-

SELECT E.column1, D.column2, ...
FROM table1 AS E
INNER JOIN table2 AS D
ON E.common_column = D.common_column;
ℹ️ JOIN = INNER JOIN: FROM Employees INNER JOIN Department ON ... produces exactly the same result as FROM Employees JOIN Department ON .... INNER is the default JOIN type — writing just JOIN is a common shorthand.

✅ Sample Tables Used in All Examples

Table 1: Employees
EmpIDNameDeptIDSalary
1ANNI1010000
2POOJA209000
3RAJ305000
4SUJAN408000
5MEENANULL12000
Table 2: Department
DeptIDDeptNameLocation
10ITMumbai
20HRDelhi
30SalesPune
50FinanceBangalore
ℹ️ Key mismatches to observe: SUJAN has DeptID 40 — which does not exist in the Department table. MEENA has NULL DeptID — no department. Department 50 (Finance) has no matching employee. INNER JOIN will exclude all three — only ANNI, POOJA, and RAJ have matching DeptIDs in both tables.

✅ Example 1 – Basic INNER JOIN (Two Tables)

Join Employees with Department to show each employee's department name:

SELECT Query:-

SELECT E.EmpID, E.Name, D.DeptName
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID;

Result Show:-

Result – Employees with Matching Department
EmpIDNameDeptName
1ANNIIT
2POOJAHR
3RAJSales
💡 Why only 3 rows? SUJAN (DeptID 40) has no matching department — excluded. MEENA (NULL DeptID) — excluded. Finance (DeptID 50) has no matching employee — excluded. INNER JOIN returns only perfect matches from both sides.

✅ Example 2 – INNER JOIN with Selected Columns and Aliases

Select specific columns and use descriptive aliases for cleaner output:

SELECT Query:-

SELECT E.EmpID          AS EmployeeID,
       E.Name           AS EmployeeName,
       D.DeptName       AS Department,
       D.Location       AS Office,
       E.Salary         AS MonthlySalary
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID;

Result Show:-

Result – INNER JOIN with Column Aliases
EmployeeIDEmployeeNameDepartmentOfficeMonthlySalary
1ANNIITMumbai10000
2POOJAHRDelhi9000
3RAJSalesPune5000
ℹ️ Best practice: Always specify which columns you need instead of using SELECT *. This makes queries faster, more readable, and protects against breaking when table columns are added or reordered.

✅ Example 3 – INNER JOIN with WHERE Clause

Join the tables, then filter the joined result to show only IT department employees:

SELECT Query:-

SELECT E.Name AS EmployeeName,
       D.DeptName AS Department,
       E.Salary AS MonthlySalary
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
WHERE D.DeptName = 'IT';

Result Show:-

Result – Only IT Department Employees
EmployeeNameDepartmentMonthlySalary
ANNIIT10000

Another example – Filter by salary after joining:-

SELECT E.Name, D.DeptName, E.Salary
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
WHERE E.Salary > 8000;

Result Show:-

Result – Employees with Salary > 8000 (Matched Only)
NameDeptNameSalary
ANNIIT10000
POOJAHR9000
💡 WHERE with JOIN: WHERE is applied AFTER the JOIN. The database first joins both tables to produce all matched rows, then filters that intermediate result using the WHERE condition. MEENA (12000) would qualify by salary but is excluded because she has no matching department.

✅ Example 4 – INNER JOIN with ORDER BY

Join the tables and sort the results by salary from highest to lowest:

SELECT Query:-

SELECT E.Name AS EmployeeName,
       D.DeptName AS Department,
       E.Salary AS MonthlySalary
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
ORDER BY E.Salary DESC;

Result Show:-

Result – Employees Sorted by Salary (Highest First)
EmployeeNameDepartmentMonthlySalary
ANNIIT10000
POOJAHR9000
RAJSales5000

✅ Example 5 – INNER JOIN Three Tables

Chain two INNER JOINs to combine three tables. Adding a third table (Location) linked to Department:

Third Table: Location

Location
LocIDCityCountry
1MumbaiIndia
2DelhiIndia
3PuneIndia

SELECT Query – Three-Table INNER JOIN:-

SELECT E.Name      AS EmployeeName,
       D.DeptName  AS Department,
       L.City      AS City,
       L.Country   AS Country,
       E.Salary    AS MonthlySalary
FROM Employees E
INNER JOIN Department D  ON E.DeptID   = D.DeptID
INNER JOIN Location   L  ON D.Location = L.City;

Result Show:-

Result – Three-Table INNER JOIN
EmployeeNameDepartmentCityCountryMonthlySalary
ANNIITMumbaiIndia10000
POOJAHRDelhiIndia9000
RAJSalesPuneIndia5000
ℹ️ Chaining JOINs: You can chain as many INNER JOINs as needed. Each new INNER JOIN adds another table. The result must have matching values in ALL linked tables — if any match fails in the chain, that row is excluded.

✅ Example 6 – INNER JOIN with GROUP BY and COUNT

Count the number of employees in each department using INNER JOIN and GROUP BY:

SELECT Query:-

SELECT D.DeptName AS Department,
       COUNT(E.EmpID) AS TotalEmployees
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
GROUP BY D.DeptName
ORDER BY TotalEmployees DESC;

Result Show:-

Result – Employee Count per Department
DepartmentTotalEmployees
IT1
HR1
Sales1
💡 Finance is not shown because no employee matches DeptID 50. INNER JOIN excludes Finance. SUJAN and MEENA are also excluded because their departments don't match. COUNT only counts the matched rows.

✅ Example 7 – INNER JOIN with GROUP BY and Multiple Aggregates

Generate a complete salary report per department using INNER JOIN combined with COUNT, MIN, MAX, and AVG:

SELECT Query:-

SELECT D.DeptName              AS Department,
       COUNT(E.EmpID)           AS TotalEmployees,
       MIN(E.Salary)            AS LowestSalary,
       MAX(E.Salary)            AS HighestSalary,
       ROUND(AVG(E.Salary), 2)  AS AvgSalary,
       SUM(E.Salary)            AS TotalSalaryBill
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
GROUP BY D.DeptName
ORDER BY AvgSalary DESC;

Result Show:-

Result – Full Department Salary Report via INNER JOIN
DepartmentTotalEmployeesLowestSalaryHighestSalaryAvgSalaryTotalSalaryBill
IT1100001000010000.0010000
HR1900090009000.009000
Sales1500050005000.005000
💡 This is the most common INNER JOIN reporting pattern — combining INNER JOIN with GROUP BY and multiple aggregate functions (COUNT, MIN, MAX, AVG, SUM) to produce a full departmental summary. This is used daily in business intelligence and data analysis.

✅ Example 8 – INNER JOIN with HAVING Clause

Use HAVING to filter grouped results after INNER JOIN — show only departments where the average salary is above 8000:

SELECT Query:-

SELECT D.DeptName              AS Department,
       COUNT(E.EmpID)           AS TotalEmployees,
       ROUND(AVG(E.Salary), 2)  AS AvgSalary
FROM Employees E
INNER JOIN Department D
ON E.DeptID = D.DeptID
GROUP BY D.DeptName
HAVING AVG(E.Salary) > 8000
ORDER BY AvgSalary DESC;

Result Show:-

Result – Departments with Avg Salary > 8000
DepartmentTotalEmployeesAvgSalary
IT110000.00
HR19000.00

Sales (AvgSalary = 5000) is excluded because 5000 is not greater than 8000.

⚠️ HAVING — not WHERE — for post-grouping filters: Use WHERE to filter rows before grouping. Use HAVING to filter groups after GROUP BY. Wrong: WHERE AVG(E.Salary) > 8000 causes a syntax error. Correct: HAVING AVG(E.Salary) > 8000.

✅ INNER JOIN vs LEFT JOIN – Side by Side Comparison

The same query using INNER JOIN vs LEFT JOIN produces very different results — understanding the difference is essential:

FeatureINNER JOINLEFT JOIN
ReturnsOnly matched rows from both tablesAll rows from left table + matched right
Unmatched left rows❌ Excluded✅ Included (NULL for right columns)
Unmatched right rows❌ Excluded❌ Excluded
Result rows (our tables)3 rows (ANNI, POOJA, RAJ)5 rows (all employees, 2 with NULL dept)
Use whenYou only need records that exist in both tablesYou need all left records even without a match
NULLs in resultNo NULLs from JOINNULL where right table has no match
Most common useStandard data retrieval from related tablesFinding orphan records, audit reports
ℹ️ Choose INNER JOIN when you only care about records that have a corresponding entry in both tables. Choose LEFT JOIN when you need all records from one table and want to see which ones have no match in the other.

✅ Key Points to Remember

✅ INNER JOIN returns only matching rows — rows with no match in either table are excluded.

✅ JOIN = INNER JOIN — writing just JOIN without a qualifier defaults to INNER JOIN in all major databases.

✅ Always use the ON condition — without ON, the query becomes a CROSS JOIN producing millions of rows.

✅ Always use table aliasesE.Name, D.DeptName is clearer than Name, DeptName in multi-table queries.

✅ Chain multiple INNER JOINs — add as many INNER JOINs as needed to combine three or more tables.

✅ WHERE filters after the JOIN — the join runs first, then WHERE filters the intermediate result.

✅ Use HAVING to filter grouped JOIN results — not WHERE — when using GROUP BY with aggregate conditions.

✅ NULL values never match — a NULL in the join column (like MEENA's DeptID) never satisfies the ON condition.

✅ Specify columns, not SELECT * — in JOIN queries, always list the columns you need with their table prefix.


✅ Common SQL INNER JOIN Mistakes to Avoid

❌ Mistake 1: Forgetting the ON condition
Wrong: SELECT * FROM Employees INNER JOIN Department; — most databases throw an error or perform a CROSS JOIN.
Correct: SELECT * FROM Employees E INNER JOIN Department D ON E.DeptID = D.DeptID;
❌ Mistake 2: Ambiguous column names without table prefix
Wrong: SELECT DeptID, Name FROM Employees INNER JOIN Department ON ...; — SQL doesn't know which table's DeptID you want.
Correct: SELECT E.DeptID, E.Name, D.DeptName FROM Employees E INNER JOIN Department D ON E.DeptID = D.DeptID;
❌ Mistake 3: Joining on the wrong column
Always join on the column that logically links the tables — the foreign key in one table must match the primary key in the other. Joining on unrelated columns produces meaningless results without an error.
❌ Mistake 4: Expecting NULL columns to match
If the join column has NULL values (like MEENA's DeptID = NULL), those rows will NEVER match in an INNER JOIN — NULL = anything is UNKNOWN, not TRUE. Use LEFT JOIN if you need to include NULL-keyed rows.
❌ Mistake 5: Using WHERE instead of HAVING to filter aggregated JOIN results
Wrong: WHERE AVG(E.Salary) > 8000 — syntax error when used with GROUP BY.
Correct: HAVING AVG(E.Salary) > 8000 — HAVING filters after grouping.

✅ Frequently Asked Questions (FAQ)

What is SQL INNER JOIN?
SQL INNER JOIN returns only the rows where there is a matching value in both tables based on the ON condition. Rows that have no match in either table are completely excluded from the result. It is the most commonly used SQL JOIN type.
What is the difference between INNER JOIN and JOIN?
There is no difference. Writing JOIN without a qualifier defaults to INNER JOIN in all major databases — MySQL, PostgreSQL, SQL Server, Oracle, and SQLite. INNER JOIN is just the explicit, more readable form. Both produce identical results with identical performance.
What is the difference between INNER JOIN and LEFT JOIN?
INNER JOIN returns only rows where there is a match in BOTH tables — unmatched rows from either side are excluded. LEFT JOIN returns ALL rows from the left table even if there is no match in the right table — unmatched right-side columns show NULL.
Can I use INNER JOIN with more than two tables?
Yes. You can chain multiple INNER JOINs: SELECT E.Name, D.DeptName, L.City FROM Employees E INNER JOIN Department D ON E.DeptID = D.DeptID INNER JOIN Location L ON D.Location = L.City; Each INNER JOIN adds another table. Rows must have matching values across all joined tables.
What happens if the ON condition matches no rows in INNER JOIN?
If the ON condition finds no matching rows between the two tables, INNER JOIN returns an empty result set — zero rows. No error is thrown. This is different from LEFT JOIN which would still return all rows from the left table with NULLs for the right side.
Can I use WHERE clause with INNER JOIN?
Yes. Add WHERE after the ON condition to further filter the joined result: SELECT E.Name, D.DeptName FROM Employees E INNER JOIN Department D ON E.DeptID = D.DeptID WHERE D.DeptName = 'IT'; WHERE is evaluated after the JOIN is complete, filtering the intermediate joined result.

✍️ About the Author: Pramod Behera

Pramod Behera is a SAP and SQL educator with 10+ years of experience in enterprise software and database systems. He founded LearnToSAP.com to help beginners learn SAP and SQL concepts through clear, practical, real-world examples. His tutorials have helped thousands of students and IT professionals across India and beyond.