IT Learning HubIT Learning Hub
PRACTICE 03 · BTHT3

Division, Grouping & Outer Joins

5 periodsDatabaseSQL Server

Objective: Understand the nature of division in SQL, recognize "all" style problems, write queries using GROUP BY + HAVING COUNT and double NOT EXISTS, use aggregate functions, combine TOP/ORDER BY/WITH TIES/DISTINCT, and use LEFT/RIGHT/FULL OUTER JOIN so no data is missed.

01 · DIVISION IN SQL

What Is Division?

In SQL, division is used when you need to find objects in a table R that relate to all objects in a table S.

Keywords to Watch For

When a problem contains phrases like all, every, the entirety of, "bought all products", "participated in all projects", "registered for all courses" - think of division right away.

A Visual Example

Suppose we have 3 tables:

KhachHang
MaKHTenKH
KH01Nguyễn An
KH02Trần Bình
KH03Lê Cường
SanPham
MaSPTenSP
SP01Laptop
SP02Chuột
SP03Bàn phím
MuaHang
MaKHMaSP
KH01SP01
KH01SP02
KH01SP03
KH02SP01
KH02SP02
KH03SP01
KH03SP03

We need to find: customers who bought all products.

All products: SP01, SP02, SP03 KH01: ✓SP01 ✓SP02 ✓SP03 ➜ Passes KH02: ✓SP01 ✓SP02 ✗SP03 ➜ Fails KH03: ✓SP01 ✗SP02 ✓SP03 ➜ Fails

Result: KH01. This is exactly the way of thinking behind division.

The Core Idea

The problem "Find A that has done all of B" can be understood as:

The number of B's that A has done = The total number of B's that need to be done Example: KH01 bought 3 distinct products, total products = 3 ➜ KH01 passes

In SQL, this idea is typically implemented with GROUP BY + HAVING COUNT(DISTINCT ...).

02 · BASIC DIVISION

Basic Division

Model: R(A, B, C, D, E) and S(D, E). Division R / S finds the values A, B, C of R that relate to all values D, E in S.

The GROUP BY + HAVING Formula

SELECT B1.<Column 1>, ...
FROM <Table 1> B1
JOIN <Table 3> B3
    ON B1.<Column 1> = B3.<Column 1>
GROUP BY B1.<Column 1>
HAVING COUNT(DISTINCT B3.<Column 2>) =
(
    SELECT COUNT(DISTINCT B2.<Column 2>)
    FROM <Table 2> B2
);

Example 1 - Customers Who Bought All Products

Tables: KhachHang(MaKH, TenKH), SanPham(MaSP, TenSP), MuaHang(MaKH, MaSP).

SELECT KH.MaKH
FROM KhachHang KH
JOIN MuaHang MH
    ON KH.MaKH = MH.MaKH
GROUP BY KH.MaKH
HAVING COUNT(DISTINCT MH.MaSP) =
(
    SELECT COUNT(*)
    FROM SanPham
);

Breaking Down the SQL

Step 1 - Join customers to their purchase history: FROM KhachHang KH JOIN MuaHang MH ON KH.MaKH = MH.MaKH

Step 2 - Group by each customer with GROUP BY KH.MaKH: KH01 ➜ SP01,SP02,SP03, KH02 ➜ SP01,SP02, KH03 ➜ SP01,SP03.

Step 3 - Count distinct products purchased with COUNT(DISTINCT MH.MaSP): KH01 ➜ 3, KH02 ➜ 2, KH03 ➜ 2.

Step 4 - Count the total number of products: SELECT COUNT(*) FROM SanPham ➜ 3.

Step 5 - Compare: number of products the customer bought = total number of products that exist ➜ the customer bought all of them.

Example 2 - Students Registered for All Required Courses

Tables: SinhVien(MaSV, TenSV), MonHoc(MaMH, TenMH), DangKy(MaSV, MaMH).

SELECT SV.MaSV
FROM SinhVien SV
JOIN DangKy DK
    ON SV.MaSV = DK.MaSV
GROUP BY SV.MaSV
HAVING COUNT(DISTINCT DK.MaMH) =
(
    SELECT COUNT(*)
    FROM MonHoc
);

Student A registered for 5 courses, total required courses = 5 ➜ passes. Student B registered for 4 courses, total required courses = 5 ➜ fails.

Example 3 - Employees Who Participated in All Projects

Tables: NhanVien(MaNV, TenNV), DuAn(MaDA, TenDA), ThamGia(MaNV, MaDA).

SELECT NV.MaNV
FROM NhanVien NV
JOIN ThamGia TG
    ON NV.MaNV = TG.MaNV
GROUP BY NV.MaNV
HAVING COUNT(DISTINCT TG.MaDA) =
(
    SELECT COUNT(*)
    FROM DuAn
);

03 · CONDITIONAL DIVISION

Conditional Division

Conditional division is basic division, but the set of objects to check is limited by a condition.

Example: "Find customers who bought all products in the Electronics category" - this is no longer "all products," but "all products WHERE DanhMuc = 'Điện tử'".

SanPham
CodeNameCategory
SP01LaptopĐiện tử
SP02Điện thoạiĐiện tử
SP03Bàn phímPhụ kiện
SP04ÁoThời trang

The set to check is only SP01, SP02, not all 4 products.

SELECT KH.MaKH
FROM KhachHang KH
JOIN MuaHang MH
    ON KH.MaKH = MH.MaKH
JOIN SanPham SP
    ON MH.MaSP = SP.MaSP
WHERE SP.DanhMuc = N'Điện tử'
GROUP BY KH.MaKH
HAVING COUNT(DISTINCT MH.MaSP) =
(
    SELECT COUNT(*)
    FROM SanPham
    WHERE DanhMuc = N'Điện tử'
);

The Thinking Formula

Set to check ↓ WHERE condition ↓ GROUP BY + count elements ↓ HAVING COUNT(...) = total elements matching the condition

Example - Required Courses Worth More Than 3 Credits

Problem: "Find students who registered for all required courses worth more than 3 credits." First determine the set of courses to check using WHERE SoTinChi > 3, then check whether each student registered for the entire set.

SELECT SV.MaSV
FROM SinhVien SV
JOIN DangKy DK
    ON SV.MaSV = DK.MaSV
JOIN MonHoc MH
    ON MH.MaMH = DK.MaMH
WHERE MH.SoTinChi > 3
GROUP BY SV.MaSV
HAVING COUNT(DISTINCT DK.MaMH) =
(
    SELECT COUNT(*)
    FROM MonHoc
    WHERE SoTinChi > 3
);
Note: In the original material, the subquery's condition is written with an alias reference that needs review. The query above is a standalone, clearer way to write it for SQL Server.

04 · DIVISION WITH NOT EXISTS

Division with NOT EXISTS

This is the second way to solve "all" problems - using negation to check whether any value fails to satisfy the condition.

The Idea: "All" ➜ "No Missing Case Exists"

"The customer bought all electronics products" can be restated as "There does not exist an electronics product that the customer has not bought."

"All of A" is equivalent to "There does not exist an A that hasn't been done."

The Double NOT EXISTS Structure

SELECT ...
FROM A
WHERE NOT EXISTS
(
    SELECT *
    FROM B
    WHERE NOT EXISTS
    (
        SELECT *
        FROM C
        WHERE ...
    )
);

Customer Example

Problem: "Find customers who bought all products in the Electronics category."

SELECT KH.MaKH
FROM KhachHang KH
WHERE NOT EXISTS
(
    SELECT *
    FROM SanPham SP
    WHERE SP.DanhMuc = N'Điện tử'
      AND NOT EXISTS
      (
          SELECT *
          FROM MuaHang MH
          WHERE MH.MaKH = KH.MaKH
            AND MH.MaSP = SP.MaSP
      )
);

Reading the SQL in Plain English

Don't read SQL line by line. Instead, read: "Get customers for whom there does not exist any electronics product that the customer hasn't bought." If no such product exists ➜ the customer has bought all of them.

Student Example

Problem: "Find students who registered for all required courses worth more than 3 credits."

SELECT SV.MaSV
FROM SinhVien SV
WHERE NOT EXISTS
(
    SELECT *
    FROM MonHoc MH
    WHERE MH.SoTinChi > 3
      AND NOT EXISTS
      (
          SELECT *
          FROM DangKy DK
          WHERE DK.MaMH = MH.MaMH
            AND DK.MaSV = SV.MaSV
      )
);

Three Ways to Recognize Division

FormSignal
Basic divisionGROUP BY + HAVING COUNT
Conditional divisionWHERE + GROUP BY + HAVING COUNT
NOT EXISTS divisionTwo nested NOT EXISTS layers
Common mistake: forgetting to write both negation layers. Writing only one layer of NOT EXISTS without the inner layer produces a completely wrong result - always double-check against the pattern "no A exists that lacks B," made of exactly two nested NOT EXISTS as shown above.

05 · AGGREGATE FUNCTIONS & GROUPING

Aggregate Functions

Aggregate functions let you perform calculations over a set of multiple rows.

FunctionMeaning
COUNT(*)Counts rows
COUNT(column)Counts non-NULL values
COUNT(DISTINCT column)Counts distinct, non-NULL values
SUM(x)Sum
AVG(x)Average
MAX(x)Maximum
MIN(x)Minimum

Distinguishing the 3 Kinds of COUNT

MaNVLuongChucVu
NV0110MDev
NV0212MDev
NV03NULLTester
NV0415MDev
NV0515MNULL

SELECT COUNT(*) FROM NhanVien; ➜ 5 (counts all rows)

SELECT COUNT(Luong) FROM NhanVien; ➜ 4 (NV03 has Luong = NULL)

SELECT COUNT(DISTINCT ChucVu) FROM NhanVien; ➜ 2 (Dev, Tester - NULL doesn't count)

SUM, AVG, MAX, MIN

SELECT SUM(Luong) AS TongLuong
FROM NhanVien;
SELECT AVG(Luong) AS LuongTrungBinh
FROM NhanVien;

Note: AVG ignores NULL values.

SELECT
    MAX(Luong) AS LuongCaoNhat,
    MIN(Luong) AS LuongThapNhat
FROM NhanVien;

How Does GROUP BY Work?

Example data:

MaNVPhongLuong
NV01P0110M
NV02P0112M
NV03P0215M
NV04P0218M

GROUP BY Phong splits the data into groups P01: NV01, NV02 and P02: NV03, NV04, and only then runs MAX/MIN/AVG/SUM/COUNT on each group.

SELECT
    Phong,
    MAX(Luong) AS LuongCaoNhat,
    MIN(Luong) AS LuongThapNhat,
    AVG(Luong) AS LuongTrungBinh
FROM NhanVien
GROUP BY Phong;
Common mistake: adding a column to SELECT but forgetting to add it to GROUP BY. The mandatory rule: every column in SELECT that is not inside an aggregate function (SUM, COUNT, AVG...) must also appear in GROUP BY, or SQL Server will raise a syntax error.

Counting Employees per Department

LEFT JOIN keeps a department visible even when it has no employees yet:

SELECT
    PB.MaPH,
    PB.TenPH,
    COUNT(NV.MaNV) AS SLNV
FROM PHONGBAN PB
LEFT JOIN NHANVIEN NV
    ON NV.Phong = PB.MaPH
GROUP BY
    PB.MaPH,
    PB.TenPH;

HAVING - Filtering Groups

Problem: "Find departments with more than 10 employees, showing department code, name, and employee count, sorted in descending order."

SELECT
    PB.MaPH,
    PB.TenPH,
    COUNT(NV.MaNV) AS SLNV
FROM PHONGBAN PB
LEFT JOIN NHANVIEN NV
    ON NV.Phong = PB.MaPH
GROUP BY
    PB.MaPH,
    PB.TenPH
HAVING COUNT(NV.MaNV) > 10
ORDER BY
    COUNT(NV.MaNV) DESC;

WHERE vs. HAVING - Know the Difference

WHERE filters rows before grouping (WHERE Luong > 10000000). HAVING filters groups after grouping (HAVING COUNT(*) > 10).

QUICK CHECK

To keep only departments with more than 10 employees (after GROUP BY), which clause should you use?

06 · SELECT TOP

SELECT TOP

TOP is used to limit the number of rows returned.

SELECT TOP 4
    HoTen,
    Luong
FROM NhanVien;

TOP + ORDER BY

To get the 4 highest-paid employees:

SELECT TOP 4
    HoTen,
    Luong
FROM NhanVien
ORDER BY Luong DESC;
Extremely important: Don't just write SELECT TOP 4 * FROM NhanVien; if the problem asks for "the 4 highest-paid employees" - you must include ORDER BY Luong DESC.

TOP ... WITH TIES

If the 4th-highest salary is 15 million and someone else also earns 15 million, WITH TIES will include them too:

SELECT TOP 4 WITH TIES
    HoTen,
    Luong
FROM NhanVien
ORDER BY Luong DESC;
1. Nam 20M 2. Tuấn 19M 3. Lộc 18M 4. Linh 17M 5. Trung 17M TOP 4: Nam, Tuấn, Lộc, Linh TOP 4 WITH TIES: Nam, Tuấn, Lộc, Linh, Trung

DISTINCT TOP

Get the first 4 distinct job titles:

SELECT DISTINCT TOP 4
    ChucVu
FROM NhanVien;

TOP + GROUP BY + HAVING

Problem: "Get the 4 departments with the largest total payroll, considering only departments with a total salary above 5 million."

SELECT TOP 4
    MaPB,
    SUM(Luong) AS TongLuong
FROM NhanVien
GROUP BY MaPB
HAVING SUM(Luong) > 5000000
ORDER BY SUM(Luong) DESC;

07 · OUTER JOINS

Outer Joins

INNER JOIN only returns rows with matching data. But sometimes you need to know about objects with no related data - for example, "show employees who aren't assigned to any project." With INNER JOIN, employees with no project would be excluded since there's no corresponding record. That's why LEFT JOIN is needed.

Finding Employees With No Project Assignment

SELECT
    NV.MaNV,
    NV.HoTen
FROM NHANVIEN NV
LEFT OUTER JOIN PHANCONG PC
    ON NV.MaNV = PC.MaNV
WHERE PC.MaDA IS NULL;
Key pattern to remember: LEFT JOIN + WHERE right_table.key IS NULL ➜ find records with no matching record in the right-hand table.
Common mistake: putting a WHERE condition on a different column (not a NULL check) of the right table after LEFT JOIN - the NULL rows produced by the LEFT JOIN then get filtered out by that condition, making the statement accidentally behave like an INNER JOIN. If you need to filter the right table further while keeping LEFT JOIN behavior, put that condition in the ON clause instead of WHERE.

The Three Types of OUTER JOIN

TypeKeeps
LEFT JOINAll of the left table
RIGHT JOINAll of the right table
FULL OUTER JOINAll of both sides

08 · QUICK REFERENCE TABLE

SQL Week 3 Cheat Sheet

Looking for...Think of...
AllDivision
All + a conditionConditional division
All, via negationNOT EXISTS × 2
TotalSUM()
AverageAVG()
Max / minMAX() / MIN()
Row countCOUNT(*)
Count values / distinct valuesCOUNT(column) / COUNT(DISTINCT column)
Top NTOP
Top N including tiesTOP ... WITH TIES
Top N distinct valuesDISTINCT TOP
Stats per group / filter groupsGROUP BY / HAVING
Records with no linkLEFT JOIN ... IS NULL

Reading a Problem ➜ Recognizing the Pattern

"ALL" ➜ DIVISION "ALL + A CONDITION" ➜ CONDITIONAL DIVISION "DOES NOT EXIST..." ➜ NOT EXISTS "HOW MANY / TOTAL / AVERAGE..." ➜ AGGREGATE FUNCTIONS "TOP / HIGHEST / LOWEST" ➜ TOP + ORDER BY "EACH DEPARTMENT / EACH GROUP..." ➜ GROUP BY "AT LEAST... / MORE THAN..." ➜ HAVING "NO RELATED DATA" ➜ LEFT JOIN + IS NULL

Week 3 Summary

Week 1 was about designing & manipulating data, Week 2 was about querying & combining data, and Week 3 is advanced querying: division ("all" ➜ GROUP BY+HAVING COUNT or NOT EXISTS×2), grouping (Aggregates, TOP/HAVING), and outer joins ("none" ➜ LEFT JOIN+IS NULL).

09 · PRACTICE EXERCISES

Comprehensive Practice

OFFICIAL ASSIGNMENT · BTTH3

Sales Management - Questions 26 ➜ 53

See the full assignment, attached files, and detailed answers (access code required) for BTTH3.

View exercise & answers ›
EXTRA PRACTICE · OPTIONAL

Beyond the official questions 26-53, here are extra practice problems to deepen your understanding of Week 3's way of thinking - not part of the BTTH3 submission. Each has a hidden hint - click to reveal.

01

"Bought Everything"

Find customers who bought all products in the Điện tử (Electronics) category.

Hint
"all" ➜ division ➜ GROUP BY ➜ HAVING COUNT
02

"Nothing Left Out"

Find departments that don't have any employees yet.

Hint
LEFT JOIN + IS NULL
03

"Top With Ties"

Find the 3 highest salary levels and return every employee earning one of those salaries.

Hint
TOP + WITH TIES + ORDER BY
04

"All, But With a Condition"

Find students who registered for all courses worth 3 or more credits.

Hint
Conditional Division
05

"Two Ways to Solve It"

Find employees who participated in every project. Write 2 different SQL queries: one using GROUP BY + HAVING COUNT, the other using double NOT EXISTS.

Hint
Method 1: GROUP BY + HAVING COUNT Method 2: NOT EXISTS + NOT EXISTS

This is a great exercise for understanding that a single SQL problem can have multiple valid solutions.