Class 12 Computer Science Chapter 12 ยท 25 min read

๐Ÿ”— Grouping Records, Joins in SQL

Unit 3 ยท Ch 12 Jul 06, 2026
Chapter Quiz Share

After learning to query and filter a single table, this chapter adds two tools: GROUP BY, which summarises rows in groups using aggregate functions (with HAVING to filter the groups), and joins, which combine data from two or more tables, such as the Equi-Join and the Natural Join.

๐Ÿ’ก How to use these notes

This chapter has two main sections โ€” GROUP BY (summarising groups of rows) and Joins (combining data from multiple tables). Read the Board Exam Tips with each section, and pay particular attention to Section 12.3.2 (HAVING clause) and Section 12.4.3 (Equi-Join) during revision.


12.1 ๐Ÿ Introduction

In Chapter 11 we learnt to query a single table. But real-world databases almost always need answers like:

  • "How many employees are in each department?"
  • "What is the average salary per city?"
  • "Show me each employee's name along with their department name from a separate table."

These questions need two new powerful tools:

graph LR
    G["๐Ÿ“Š GROUP BY\nSummarise rows\ninto groups"]
    J["๐Ÿ”— Joins\nCombine data from\nmultiple tables"]
    POWER["๐Ÿ’ช Real-World\nSQL Power!"]

    G --> POWER
    J --> POWER

    style G fill:#2196F3,color:#fff
    style J fill:#9C27B0,color:#fff
    style POWER fill:#4CAF50,color:#fff

Think of it this way: Chapter 11 was like reading individual student report cards. Chapter 13 lets you summarise the whole class (GROUP BY) and combine the report card with the attendance register (Joins)!


12.2 โš™๏ธ Types of SQL Functions

Before diving into GROUP BY, it helps to understand the two broad categories of SQL functions:

๐Ÿ”ข

Single-Row Functions

Operate on ONE row at a time and return one result per row

UPPER()ROUND()YEAR()LENGTH() โ€” we covered these in Ch 11
๐Ÿ“Š

Multi-Row (Aggregate) Functions

Operate on a GROUP of rows and return ONE summary result

COUNT()SUM()AVG()MAX()MIN() โ€” the stars of this chapter!
graph TD
    F["SQL FUNCTIONS"]
    F1["๐Ÿ”ข Single-Row Functions\nOne input row โ†’ One output row\nOutput has same number of rows as input"]
    F2["๐Ÿ“Š Aggregate Functions\nMany input rows โ†’ One output value\nCollapses many rows into a summary"]

    F --> F1
    F --> F2

    F1 --> EX1["UPPER(Name)\nUPPER('priya') โ†’ 'PRIYA'\nfor EVERY row"]
    F2 --> EX2["AVG(Salary)\nAll salaries โ†’ one average\nfor the WHOLE group"]

    style F fill:#FF9800,color:#fff
    style F1 fill:#2196F3,color:#fff
    style F2 fill:#9C27B0,color:#fff

Aggregate Functions โ€” Quick Reference:

Function Purpose Ignores NULL?
COUNT(*) Count all rows No (counts everything)
COUNT(col) Count non-NULL values Yes
SUM(col) Total of all values Yes
AVG(col) Average of all values Yes
MAX(col) Largest value Yes
MIN(col) Smallest value Yes
๐Ÿ“‹ Board Exam Tip

"What is the difference between single-row and aggregate functions?" โ€” 2-mark question. Answer: Single-row functions process one row at a time and return one result per row (same number of output rows as input rows). Aggregate functions process a group of rows and return a single summary value for the entire group (e.g., SUM, AVG, COUNT).


12.3 ๐Ÿ“Š Grouping Result โ€” GROUP BY

Without GROUP BY, aggregate functions summarise the entire table into one row. With GROUP BY, they summarise each group separately โ€” giving you one result row per group!

The Problem Without GROUP BY:

-- without_group_by.sql
SELECT COUNT(*) FROM Employee;
-- Output: 6 (total employees โ€” just ONE number, not very useful!)

The Solution With GROUP BY:

-- with_group_by.sql
SELECT Dept, COUNT(*) AS TotalEmployees
FROM Employee
GROUP BY Dept;

Output:

Dept TotalEmployees
Sales 2
IT 2
HR 2
graph TD
    ALL["๐Ÿ“‹ All 6 Employee Rows"]
    GRP["GROUP BY Dept"]
    G1["๐Ÿข Sales Group\nPriya, Anjali\nCOUNT = 2"]
    G2["๐Ÿ’ป IT Group\nArjun, Rohan\nCOUNT = 2"]
    G3["๐Ÿ‘ฅ HR Group\nSita, Vikram\nCOUNT = 2"]

    ALL --> GRP
    GRP --> G1
    GRP --> G2
    GRP --> G3

    style GRP fill:#FF9800,color:#fff
    style G1 fill:#4CAF50,color:#fff
    style G2 fill:#2196F3,color:#fff
    style G3 fill:#9C27B0,color:#fff

Our Sample Employee Table (used throughout this chapter):

EmpId Name Dept Salary DOJ City
101 Priya Sharma Sales 45000 2022-03-15 Delhi
102 Arjun Kumar IT 62000 2021-07-01 Mumbai
103 Sita Roy HR 38000 2023-01-20 Delhi
104 Rohan Das IT 71000 2020-11-05 Bangalore
105 Anjali Gupta Sales NULL 2023-06-10 Mumbai
106 Vikram Singh HR 41000 2022-09-25 Delhi

More GROUP BY Examples:

-- group_by_examples.sql

-- Average salary per department:
SELECT Dept, AVG(Salary) AS AvgSalary
FROM Employee
GROUP BY Dept;
Dept AvgSalary
Sales 45000.00
IT 66500.00
HR 39500.00

Notice: AVG(Salary) for Sales is 45000, not NULL-affected โ€” because AVG ignores NULL. Only Priya's 45000 counts for Sales (Anjali is NULL).

-- Count employees per city:
SELECT City, COUNT(*) AS EmpCount
FROM Employee
GROUP BY City;
City EmpCount
Delhi 3
Mumbai 2
Bangalore 1

General GROUP BY Syntax:

SELECT column, aggregate_function(column)
FROM table_name
[WHERE condition]
GROUP BY column
[HAVING condition]
[ORDER BY column];
โš  The Golden Rule of GROUP BY!

Any column in the SELECT list that is NOT inside an aggregate function MUST appear in the GROUP BY clause. Writing SELECT Dept, Name, COUNT(*) FROM Employee GROUP BY Dept; is an error โ€” Name is not aggregated and not in GROUP BY. Either add Name to GROUP BY or remove it from SELECT!

๐Ÿ“‹ Board Exam Tip

"Write a query to display the number of employees in each department." โ€” 2-mark question. Answer: SELECT Dept, COUNT(*) AS TotalEmployees FROM Employee GROUP BY Dept;


12.3.1 Nested Groups โ€” Grouping on Multiple Columns ๐Ÿช†

You can GROUP BY more than one column to create finer sub-groups โ€” like grouping first by Department, then by City within each department.

Real-World Need: "How many employees are in each city, within each department?"

-- nested_groups.sql
SELECT Dept, City, COUNT(*) AS EmpCount
FROM Employee
GROUP BY Dept, City;

Output:

Dept City EmpCount
Sales Delhi 1
Sales Mumbai 1
IT Mumbai 1
IT Bangalore 1
HR Delhi 2
graph TD
    ALL["๐Ÿ“‹ All 6 Employees"]
    D1["๐Ÿ’ผ Sales Dept"]
    D2["๐Ÿ’ป IT Dept"]
    D3["๐Ÿ‘ฅ HR Dept"]
    S1["Delhi โ†’ 1 emp\n(Priya)"]
    S2["Mumbai โ†’ 1 emp\n(Anjali)"]
    I1["Mumbai โ†’ 1 emp\n(Arjun)"]
    I2["Bangalore โ†’ 1 emp\n(Rohan)"]
    H1["Delhi โ†’ 2 emps\n(Sita, Vikram)"]

    ALL --> D1
    ALL --> D2
    ALL --> D3
    D1 --> S1
    D1 --> S2
    D2 --> I1
    D2 --> I2
    D3 --> H1

    style D1 fill:#4CAF50,color:#fff
    style D2 fill:#2196F3,color:#fff
    style D3 fill:#9C27B0,color:#fff

Another Example โ€” Total salary per department per city:

-- nested_group_salary.sql
SELECT Dept, City, SUM(Salary) AS TotalSalary
FROM Employee
GROUP BY Dept, City;
๐Ÿง  Order in GROUP BY matters for display! ๐Ÿง 

GROUP BY Dept, City creates groups of (Dept, City) combinations. The order in GROUP BY determines how rows are sub-divided โ€” first by Dept, then within each Dept by City. This is exactly like an Excel pivot table!

๐Ÿ“‹ Board Exam Tip

"Write a query to display total salary for each department in each city." โ€” 3-mark question! Answer: SELECT Dept, City, SUM(Salary) AS TotalSalary FROM Employee GROUP BY Dept, City;


12.3.2 Placing Conditions on Groups โ€” HAVING Clause ๐ŸŽฏ

WHERE filters individual rows before grouping. HAVING filters groups after grouping. You use HAVING when you want to show only groups that meet a certain condition.

Real-World Need: "Show only departments where the average salary is more than โ‚น50,000."

-- having_clause.sql
SELECT Dept, AVG(Salary) AS AvgSalary
FROM Employee
GROUP BY Dept
HAVING AVG(Salary) > 50000;

Output (only IT qualifies):

Dept AvgSalary
IT 66500.00
graph LR
    DATA["All Rows"]
    WHERE_STEP["WHERE\n(filters individual rows\nbefore grouping)"]
    GROUP_STEP["GROUP BY\n(creates groups)"]
    AGG_STEP["Aggregate\nFunctions\n(SUM, AVG etc.)"]
    HAVING_STEP["HAVING\n(filters groups\nafter grouping)"]
    RESULT["Final Result"]

    DATA --> WHERE_STEP
    WHERE_STEP --> GROUP_STEP
    GROUP_STEP --> AGG_STEP
    AGG_STEP --> HAVING_STEP
    HAVING_STEP --> RESULT

    style WHERE_STEP fill:#FF9800,color:#fff
    style HAVING_STEP fill:#F44336,color:#fff
    style GROUP_STEP fill:#2196F3,color:#fff

WHERE vs HAVING โ€” The Critical Difference:

Feature WHERE HAVING
What it filters Individual rows Groups (after GROUP BY)
When it runs BEFORE grouping AFTER grouping
Can use aggregates? โŒ No (WHERE AVG(Salary) > 5000 is illegal) โœ… Yes (HAVING AVG(Salary) > 5000 is correct)
Used with Any SELECT statement Only with GROUP BY

Using WHERE and HAVING Together:

-- where_and_having.sql
-- Show departments where average salary > 40000,
-- but only considering employees who joined before 2023

SELECT Dept, AVG(Salary) AS AvgSalary
FROM Employee
WHERE YEAR(DOJ) < 2023
GROUP BY Dept
HAVING AVG(Salary) > 40000;

Step-by-step:

  1. WHERE first removes Sita Roy (2023) and Anjali Gupta (2023) โ€” 4 rows remain
  2. GROUP BY groups the remaining 4 rows by Dept
  3. HAVING keeps only groups with AVG Salary > 40000
๐Ÿ“‹ Board Exam Tip

"Differentiate between WHERE and HAVING clause." โ€” 2-mark question. Answer: WHERE filters individual rows before grouping and cannot use aggregate functions. HAVING filters groups after GROUP BY is applied and CAN use aggregate functions. You cannot replace one with the other โ€” they serve different purposes.

More HAVING Examples:

-- more_having_examples.sql

-- Departments with more than 1 employee:
SELECT Dept, COUNT(*) AS Total
FROM Employee
GROUP BY Dept
HAVING COUNT(*) > 1;

-- Cities where maximum salary exceeds 60000:
SELECT City, MAX(Salary) AS MaxSal
FROM Employee
GROUP BY City
HAVING MAX(Salary) > 60000;

12.3.3 Non-Group Expressions with GROUP BY ๐Ÿ“

When using GROUP BY, the SELECT list must follow strict rules โ€” any column that is not in an aggregate function must be in the GROUP BY clause. This avoids ambiguity about which value to show.

Legal โ€” column in GROUP BY:

-- legal_group_by.sql
SELECT Dept, COUNT(*) AS Total
FROM Employee
GROUP BY Dept;
-- โœ… Legal: Dept is in GROUP BY

Illegal โ€” column NOT in GROUP BY:

-- illegal_group_by.sql
SELECT Dept, Name, COUNT(*) AS Total
FROM Employee
GROUP BY Dept;
-- โŒ ILLEGAL: Name is not in GROUP BY and not aggregated!
-- MySQL error: 'Name' is not in GROUP BY clause

Why is it illegal? If Sales has Priya and Anjali, which Name should MySQL show in the output row for Sales? It's ambiguous โ€” MySQL has no way to know!

The fix โ€” add Name to GROUP BY (creates finer groups):

-- fix_non_group.sql
SELECT Dept, Name, COUNT(*) AS Total
FROM Employee
GROUP BY Dept, Name;
๐Ÿง  MySQL's ONLY_FULL_GROUP_BY Mode! ๐Ÿง 

Older versions of MySQL would sometimes allow non-group expressions and return an arbitrary value (any row from the group). Modern MySQL (from version 5.7+) runs in ONLY_FULL_GROUP_BY mode by default, which correctly rejects these ambiguous queries with an error. Always follow the rule: every non-aggregated SELECT column must be in GROUP BY!


12.4 ๐Ÿ”— Joins

So far, all queries used a single table. But real databases split data across multiple tables โ€” a Join combines rows from two (or more) tables based on a related column between them.

Real-World Analogy: Imagine your school keeps student names in one register and their marks in another. To see "Priya's marks", someone needs to join the two registers using the Roll Number as the common link โ€” that's exactly what SQL Joins do!

Our Two Sample Tables for Join Examples:

Table: Employee

EmpId Name DeptId Salary City
101 Priya Sharma D01 45000 Delhi
102 Arjun Kumar D02 62000 Mumbai
103 Sita Roy D03 38000 Delhi
104 Rohan Das D02 71000 Bangalore
105 Anjali Gupta D01 NULL Mumbai
106 Vikram Singh D03 41000 Delhi

Table: Department

DeptId DeptName Location
D01 Sales New Delhi
D02 IT Mumbai
D03 HR Kolkata
D04 Finance Chennai

12.4.1 Cartesian Product ๐ŸŒ

A Cartesian Product (also called a Cross Join) combines every row of the first table with every row of the second table โ€” producing all possible combinations.

-- cartesian_product.sql
SELECT * FROM Employee, Department;
-- OR equivalently:
SELECT * FROM Employee CROSS JOIN Department;

Result: 6 rows ร— 4 rows = 24 rows โ€” every employee paired with every department!

graph LR
    E["Employee\n6 rows"]
    D["Department\n4 rows"]
    CP["Cartesian Product\n6 ร— 4 = 24 rows\n(Every combination!)"]

    E --> CP
    D --> CP

    style CP fill:#F44336,color:#fff

Sample Output (showing just a few rows):

EmpId Name DeptId DeptId DeptName
101 Priya D01 D01 Sales
101 Priya D01 D02 IT
101 Priya D01 D03 HR
101 Priya D01 D04 Finance
102 Arjun D02 D01 Sales
... ... ... ... ...
โš  Cartesian Product is almost always a mistake!

In most real queries, you don't want every possible combination โ€” you want matching rows. A Cartesian Product of two large tables produces a massive, meaningless result. It's mainly shown to explain WHY you need a proper Join condition. Always add a WHERE or JOIN condition to link tables meaningfully!

๐Ÿ“‹ Board Exam Tip

"What is a Cartesian Product in SQL?" โ€” 2-mark question. Answer: A Cartesian Product results when two tables are joined without a joining condition. It produces every possible combination of rows from both tables. If Table A has m rows and Table B has n rows, the Cartesian Product has m ร— n rows.


12.4.2 Table Aliases ๐Ÿท๏ธ

When joining tables, column names from different tables may conflict (both tables might have a DeptId column). Table Aliases give tables short, temporary names to:

  1. Distinguish columns from different tables
  2. Make long queries shorter and more readable
-- table_aliases.sql
SELECT E.Name, E.Salary, D.DeptName
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId;
-- 'E' is an alias for Employee, 'D' is an alias for Department

Syntax Rules:

FROM Employee E         -- 'E' is the alias (no AS needed for table aliases)
FROM Employee AS E      -- Also valid with AS
Without Alias With Alias
Employee.Name E.Name
Department.DeptName D.DeptName
Employee.DeptId = Department.DeptId E.DeptId = D.DeptId
๐Ÿง  Table Aliases save your sanity! ๐Ÿง 

When joining 3+ tables, writing full table names every time makes queries unreadable. Short aliases like E, D, S instantly tell you which table a column comes from. It's a best practice even when not strictly necessary!


12.4.3 Equi-Join and Natural Join ๐Ÿค

Equi-Join

An Equi-Join links two tables using an equality condition (=) on a common column โ€” typically a Foreign Key in one table matching the Primary Key in another.

-- equi_join.sql
SELECT E.EmpId, E.Name, E.Salary, D.DeptName
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId;

Output (only rows where DeptId matches in both tables):

EmpId Name Salary DeptName
101 Priya Sharma 45000 Sales
102 Arjun Kumar 62000 IT
103 Sita Roy 38000 HR
104 Rohan Das 71000 IT
105 Anjali Gupta NULL Sales
106 Vikram Singh 41000 HR
graph LR
    E["Employee Table\n(has DeptId FK)"]
    D["Department Table\n(has DeptId PK)"]
    MATCH["WHERE E.DeptId = D.DeptId\n๐Ÿ”— Matching rows joined!"]
    RESULT["๐Ÿ“Š Combined Result\nName + Salary + DeptName\nin one query!"]

    E --> MATCH
    D --> MATCH
    MATCH --> RESULT

    style MATCH fill:#4CAF50,color:#fff
    style RESULT fill:#2196F3,color:#fff

What happened to Department 'Finance' (D04)? Notice it's not in the output! Because no employee has DeptId = D04, there's no matching row โ€” so Finance doesn't appear. This is a key property of Equi-Join (which is an Inner Join).

Alternative Modern Syntax (JOIN ... ON):

-- modern_join_syntax.sql
SELECT E.EmpId, E.Name, E.Salary, D.DeptName
FROM Employee E
JOIN Department D ON E.DeptId = D.DeptId;
-- Produces exactly the same result โ€” cleaner, modern syntax
๐Ÿง  Which syntax to use in CBSE exams? ๐Ÿง 

Both the old comma-separated syntax (FROM Employee E, Department D WHERE E.DeptId = D.DeptId) and the modern JOIN ON syntax are correct. The textbook mostly uses the comma-separated form. Use whichever you're most comfortable with โ€” both are accepted in answers!


Natural Join

A Natural Join automatically joins tables on all columns with the same name and data type โ€” no explicit join condition needed.

-- natural_join.sql
SELECT * FROM Employee NATURAL JOIN Department;
-- MySQL automatically joins on DeptId (the common column name)

Output: Same as Equi-Join above โ€” MySQL finds DeptId as the common column name and joins on it automatically.

Equi-Join vs Natural Join โ€” Quick Comparison:

Feature Equi-Join Natural Join
Join condition Explicitly written by programmer Automatically found by MySQL
Basis Equality on specified columns ALL columns with same name
Duplicate column in output? Yes โ€” DeptId appears twice No โ€” common column appears once
Control High (you choose which column) Low (MySQL decides)
Risk Safer โ€” explicit Can cause surprises if unexpected columns share names
๐Ÿ“‹ Board Exam Tip

"Differentiate between Equi-Join and Natural Join." โ€” 2-mark question asked frequently! Answer: Equi-Join links tables using an explicit equality condition written by the programmer (WHERE E.DeptId = D.DeptId). Natural Join automatically links tables on all columns that share the same name and data type โ€” no condition needs to be written. Natural Join also eliminates duplicate columns from the result.


12.4.4 Additional Search Conditions in Joins ๐ŸŽฏ

Real queries often need both a Join condition AND a filter condition (using WHERE) at the same time. You simply add more conditions using AND.

Real-World Need: "Show employees who work in IT department, with their department location."

-- additional_search_conditions.sql
SELECT E.Name, E.Salary, D.DeptName, D.Location
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
AND D.DeptName = 'IT';

Output:

Name Salary DeptName Location
Arjun Kumar 62000 IT Mumbai
Rohan Das 71000 IT Mumbai

The Logic:

WHERE E.DeptId = D.DeptId    โ† Join condition (links the tables)
AND D.DeptName = 'IT'        โ† Search condition (filters the result)

More Examples of Combined Conditions:

-- combined_join_conditions.sql

-- Show all employees earning above 50000 with their department name:
SELECT E.Name, E.Salary, D.DeptName
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
AND E.Salary > 50000;

-- Show Delhi employees with their department location:
SELECT E.Name, E.City, D.DeptName, D.Location
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
AND E.City = 'Delhi';

Output of last query:

Name City DeptName Location
Priya Sharma Delhi Sales New Delhi
Sita Roy Delhi HR Kolkata
Vikram Singh Delhi HR Kolkata
๐Ÿ’ก Join + Filter = Real SQL Power! ๐ŸŽต

In real applications, you almost always combine a Join condition with one or more filter conditions. The Join links the tables; the filter narrows the result. Think of them as: Join = which rows fit together, Filter = which of those combined rows I actually want!

๐Ÿ“‹ Board Exam Tip

"Write a query to display the name, salary, and department name of all employees working in Delhi." โ€” 3-mark Join query. Answer:

SELECT E.Name, E.Salary, D.DeptName
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
AND E.City = 'Delhi';

๐Ÿ“ Full SELECT Statement Order โ€” The Complete Picture

Now that we have all clauses, here is the complete ORDER in which they must appear in a SELECT statement:

SELECT    column/aggregate/expression     -- WHAT to show
FROM      table(s)                        -- WHERE data comes from
WHERE     row_condition                   -- WHICH rows to include (before grouping)
GROUP BY  column(s)                       -- HOW to group the rows
HAVING    group_condition                 -- WHICH groups to include (after grouping)
ORDER BY  column [ASC|DESC]              -- HOW to sort the final result
๐Ÿ“‹ Board Exam Tip โ€” Clause Order!

A typical 1-mark question: "In what order do SQL clauses appear?" Remember this as: SF WG HO โ†’ SELECT โ†’ FROM โ†’ WHERE โ†’ GROUP BY โ†’ HAVING โ†’ ORDER BY

The Processing Order (different from writing order!):

  1. FROM โ€” identify the table(s)
  2. WHERE โ€” filter individual rows
  3. GROUP BY โ€” group the remaining rows
  4. HAVING โ€” filter the groups
  5. SELECT โ€” compute and display the output columns
  6. ORDER BY โ€” sort the final result

โš ๏ธ Common Errors and Misconceptions

Misconception Correct Fact
โŒ WHERE and HAVING do the same thing โœ… WHERE filters rows BEFORE grouping; HAVING filters groups AFTER grouping. You cannot use aggregate functions in WHERE!
โŒ You can SELECT any column with GROUP BY โœ… Every non-aggregated column in SELECT must appear in GROUP BY. Otherwise MySQL throws an error
โŒ Cartesian Product is a useful join โœ… Cartesian Product (joining without condition) produces meaningless results; always use a proper Join condition
โŒ Natural Join always gives the same result as Equi-Join โœ… Natural Join joins on ALL shared column names (which could be more than intended). Equi-Join gives you explicit control over which columns to join on
โŒ Table aliases permanently rename the table โœ… Table aliases are temporary โ€” they only exist for the duration of that query. The actual table name in the database is unchanged
โŒ Joins always show rows from both tables โœ… An Equi-Join (inner join) only shows rows where the join condition is satisfied in BOTH tables โ€” unmatched rows (like Finance with no employees) are excluded
โŒ HAVING can be used without GROUP BY โœ… HAVING is meaningful only when used with GROUP BY. Without GROUP BY, the entire table is treated as one group

๐ŸŽฏ Sample Board Exam Questions

Q1: Very Short Answer [1 mark each]

a) Which clause is used to filter groups in SQL? โ†’ HAVING

b) What is a Cartesian Product of a 5-row table and a 3-row table? โ†’ 5 ร— 3 = 15 rows

c) Which type of Join automatically joins on all columns with the same name? โ†’ Natural Join

d) What does GROUP BY Dept do in a SELECT statement? โ†’ It groups all rows with the same department into a single group so aggregate functions can summarise each group separately.

e) Can you use an aggregate function like AVG() in a WHERE clause? โ†’ No โ€” aggregate functions cannot be used in WHERE. Use HAVING instead.


Q2: Short Answer [2 marks]

Q: Differentiate between WHERE and HAVING clause with an example.

WHERE filters individual rows before grouping. It cannot use aggregate functions. SELECT Dept, AVG(Salary) FROM Employee WHERE City = 'Delhi' GROUP BY Dept; โ€” WHERE here removes non-Delhi rows before grouping.

HAVING filters groups after GROUP BY. It can use aggregate functions. SELECT Dept, AVG(Salary) FROM Employee GROUP BY Dept HAVING AVG(Salary) > 50000; โ€” HAVING here removes groups whose average salary is โ‰ค 50000.


Q3: Query Writing [3 marks]

Q: Write SQL queries based on the Employee and Department tables:

a) Display each department name and the number of employees in it.

SELECT D.DeptName, COUNT(*) AS TotalEmployees
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
GROUP BY D.DeptName;

b) Display departments where total salary paid exceeds โ‚น80,000.

SELECT Dept, SUM(Salary) AS TotalSalary
FROM Employee
GROUP BY Dept
HAVING SUM(Salary) > 80000;

c) Display the name, salary, and department name of employees earning more than โ‚น50,000.

SELECT E.Name, E.Salary, D.DeptName
FROM Employee E, Department D
WHERE E.DeptId = D.DeptId
AND E.Salary > 50000;

Q4: Output Based Question [3 marks]

Q: Based on the Employee table, what is the output of the following query?

SELECT City, COUNT(*) AS Total, MAX(Salary) AS MaxSal
FROM Employee
GROUP BY City
HAVING COUNT(*) > 1;

Output:

City Total MaxSal
Delhi 3 45000
Mumbai 2 62000

*(Bangalore has only 1 employee so it is filtered out by HAVING COUNT(*) > 1)*


Q5: Short Answer [2 marks]

Q: What is a Cartesian Product? Why should it be avoided in practice?

A Cartesian Product is produced when two tables are combined in a FROM clause without any Join condition โ€” every row of the first table is paired with every row of the second table. If Employee has 6 rows and Department has 4 rows, the Cartesian Product has 24 rows. It should be avoided because it produces meaningless data (an employee paired with every department, even irrelevant ones) and can generate enormously large result sets that slow down the database.


โœ๏ธ Practice Problems

  1. Write a query to display the average salary for each city, but only for cities where more than one employee works.

  2. Write a query to find the total salary spent on each department. Display results in descending order of total salary.

  3. Write a query to display the name and department name of all employees whose city is 'Mumbai', using an Equi-Join.

  4. A table Student(RollNo, Name, Class, Marks) and Section(Class, SectionName, Teacher) are given. Write a query to display student names, their marks, and the teacher's name for all students scoring above 80.

  5. Explain why SELECT Dept, Name, COUNT(*) FROM Employee GROUP BY Dept; produces an error. How would you fix it?

  6. Write a query to display each department name along with the highest and lowest salary in that department.

  7. Without running the query, predict how many rows SELECT * FROM Employee, Department; will return if Employee has 6 rows and Department has 4 rows.

  8. Rewrite this Equi-Join using a Natural Join: SELECT E.Name, D.DeptName FROM Employee E, Department D WHERE E.DeptId = D.DeptId;

  9. Write a query to display department names that have more than one employee earning above โ‚น40,000.

  10. What is the difference between GROUP BY Dept and GROUP BY Dept, City? Explain with a real-world example using the Employee table.

Take the Chapter 12 Quiz Custom Quiz
Solved Board Questions

Practice real board exam questions for this chapter.

View Solved Board Questions
Learning Support

Need Help With This Chapter?

Save key topics for exam revision, ask questions to teachers, or submit content corrections.

Verified Doubts & Teacher Answers

Chapter 12 Quiz