IT Learning HubIT Learning Hub
PRACTICE 04 · BTHT4

Extended Functions, CASE & Subquery

5 periodsDatabaseSQL Server

Objective: After Week 4, students won't just know how to write queries to retrieve data, but will be able to transform data, classify data, and create computed columns from subqueries.

01 · WEEK 4 OVERVIEW

From Querying Data to Processing Data

WEEK 4 ├── Extended functions: CAST() · GETDATE() · DATEADD() · LEFT() · RIGHT() ├── CASE...END: by condition · by value · classifying data ├── Alias + Subquery: table/column alias · subquery in FROM · subquery in SELECT · EXISTS/NOT EXISTS └── Comprehensive exercises: Academic Affairs Management (50 questions)

This is the shift from querying data to processing data and turning it into information. By the end of this week, students will be able to convert data types, extract substrings, build conditional logic with CASE, write subqueries in FROM/SELECT combined with aliases, and check for the existence of related data with EXISTS/NOT EXISTS.

02 · THE CAST() FUNCTION

CAST()

CAST() is used to convert a value from one data type to another (e.g. VARCHAR ➜ INT, DATETIME ➜ DATE, DECIMAL ➜ INT, VARCHAR ➜ DATE).

Syntax

CAST(expression AS target_data_type)

expression: the value or expression to convert. target_data_type: the data type to convert to.

Basic Example

Convert a string into a datetime:

SELECT CAST('2025-04-10' AS DATETIME) AS Ngay;

Result: 2025-04-10 00:00:00.000

Convert a float into an integer:

SELECT CAST(123.456 AS INT) AS SoNguyen;

Result: 123

Common Data Types

TypeMeaningExample
INTIntegerCAST(123 AS INT)
VARCHAR(n)StringCAST('Hello' AS VARCHAR(50))
DATEDateCAST('2024-11-14' AS DATE)
DATETIMEDate + timeCAST('2024-11-14 10:20:30' AS DATETIME)
DECIMAL(p,s)Decimal numberCAST(123.456 AS DECIMAL(10,2))
NUMERIC(p,s)Decimal numberCAST(123.456 AS NUMERIC(10,2))
FLOATFloating-point numberCAST(123.456 AS FLOAT)
CHAR(n)Fixed-length stringCAST('Hello' AS CHAR(10))

CAST() in a Real Scenario

Table NhanVien(MaNV, HoTen, Luong) where Luong is stored as DECIMAL(12,2), and we want to display it as a whole number:

SELECT
    MaNV,
    HoTen,
    CAST(Luong AS INT) AS Luong
FROM NhanVien;

Watch Out for Rounding

SELECT CAST(123.99 AS INT);

Result: 123

CAST(... AS INT) is not the same as ROUND - it truncates the decimal part instead of rounding. This is a very easy mistake to make.

03 · THE GETDATE() FUNCTION

GETDATE()

GETDATE() returns the current date and time of the SQL Server system at the moment the query runs. This function is specific to SQL Server (T-SQL).
SELECT GETDATE() AS NgayGioHienTai;

Combining GETDATE() and CAST()

If you only need today's date, without the time:

SELECT CAST(GETDATE() AS DATE) AS NgayHienTai;
GETDATE() ➜ Date + time ➜ CAST(... AS DATE) ➜ Date only

Example: Calculating Age

Suppose NgaySinh = '2000-08-15'. Get today's date with SELECT GETDATE() AS HomNay;

Note: Don't use a simple subtraction between the current year and birth year to calculate exact age, since it also depends on whether the person has already had their birthday this year - this is the difference between naive data processing and processing data correctly from a business standpoint.

DATEADD() - Precise Age Calculation Down to the Day

DATEADD(datepart, number, date) adds (or subtracts, if number is negative) a span of time to a date value, returning a new date - datepart can be YEAR, MONTH, DAY, and so on.
SELECT DATEADD(YEAR, -18, GETDATE()) AS Ngay18NamTruoc;

To check "the student must be at least 18 years old", instead of subtracting year from year (which drifts around the birthday), compare NgaySinh against "the date exactly 18 years before today":

SELECT MaSV, HoTen, NgaySinh
FROM SinhVien
WHERE NgaySinh <= DATEADD(YEAR, -18, GETDATE());
At least 18 today ⟺ NgaySinh <= (today - 18 years) ⟺ NgaySinh <= DATEADD(YEAR, -18, GETDATE())

This is exact down to the day since it compares 2 date values directly, unlike rounding to calendar years with YEAR(GETDATE()) - YEAR(NgaySinh) - the calendar-year rounding still works fine when the question only needs a rough estimate, but it will be off by a few months around a birthday.

Common mistake: Writing GETDATE() - NgaySinh >= 18 (subtracting the 2 date values directly and comparing to 18) looks like the right direction but is completely wrong - a date subtraction in SQL Server returns a difference measured in days, so comparing it to 18 only checks "18 days have passed since birth", not 18 years.

04 · THE LEFT() AND RIGHT() FUNCTIONS

LEFT() and RIGHT()

LEFT() takes a number of characters from the left of a string, RIGHT() takes them from the right.

LEFT(string, num_chars)
RIGHT(string, num_chars)
SELECT LEFT('abcdef', 3);   -- abc
SELECT RIGHT('abcdef', 3);  -- def

Example with a Student ID

Suppose MaSV = '22520001', table SinhVien(MaSV, HoTen). Get the first 3 characters (cohort code):

SELECT
    MaSV,
    HoTen,
    LEFT(MaSV, 3) AS Khoa
FROM SinhVien;

Get the last 3 characters (sequence number):

SELECT
    MaSV,
    RIGHT(MaSV, 3) AS SoThuTu
FROM SinhVien;

For MaSV = 22520001: Khoa = 225, SoThuTu = 001.

Quick Comparison

FunctionTakes FromExampleResult
LEFT()The leftLEFT('abcdef',3)abc
RIGHT()The rightRIGHT('abcdef',3)def
LEFT ← ← ← RIGHT ➜ ➜ ➜

05 · CASE...END

CASE...END

CASE is SQL's conditional construct, functioning similarly to IF / ELSE IF / ELSE in programming languages.

Condition-Based Syntax

CASE
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ELSE result_other
END
SELECT
    HoTen,
    Luong,
    CASE
        WHEN Luong >= 20000000 THEN N'Cao'
        WHEN Luong >= 10000000 THEN N'Trung bình'
        ELSE N'Thấp'
    END AS MucLuong
FROM NhanVien;

Example: Classifying Customers

Classify by TongChiTieu (total spend): ≥ 10,000,000 ➜ VIP, ≥ 5,000,000 ➜ Loyal, otherwise ➜ Regular.

SELECT
    MaKH,
    HoTen,
    CASE
        WHEN TongChiTieu >= 10000000 THEN N'VIP'
        WHEN TongChiTieu >= 5000000 THEN N'Thân thiết'
        ELSE N'Thường'
    END AS PhanLoai
FROM KhachHang;

Condition Order Matters - A Lot

The following is logically wrong:

CASE
    WHEN TongChiTieu >= 5000000 THEN N'Thân thiết'
    WHEN TongChiTieu >= 10000000 THEN N'VIP'
    ELSE N'Thường'
END
Since 15,000,000 >= 5,000,000 is already true at the first condition, 15,000,000 gets classified as "Loyal" instead of "VIP". Rule: more specific/higher conditions should come before broader ones (>=10M ➜ >=5M ➜ the rest).

CASE with Value Comparison

CASE expression
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ELSE result_other
END

Example: classifying gender:

SELECT
    MaNV,
    HoTen,
    CASE GioiTinh
        WHEN 1 THEN N'Nam'
        WHEN 0 THEN N'Nữ'
        ELSE N'Không xác định'
    END AS PhanLoaiGioiTinh
FROM NhanVien;

The Two Forms of CASE

FormWhen to Use?
CASE WHEN conditionComplex conditions
CASE expression WHEN valueComparing one value against several values

CASE Isn't Just for SELECT

CASE can also be used in ORDER BY, and even WHERE/GROUP BY when needed:

ORDER BY
    CASE
        WHEN TrangThai = N'Ưu tiên' THEN 1
        ELSE 2
    END;

QUICK CHECK

In a CASE WHEN with multiple tiered conditions (e.g. classifying salary), which condition should come first?

06 · ALIAS AND SUBQUERY

Alias and Basic Subquery

An Alias is a temporary name given to a table or column in a query, making the SQL easier to read and easier to use in complex expressions.
SELECT SV.MaSV, SV.HoTen
FROM SinhVien SV;

Alias for a column:

SELECT
    HoTen AS TenSinhVien
FROM SinhVien;

Alias for a table (AS can be omitted):

SELECT
    SV.MaSV,
    SV.HoTen
FROM SinhVien AS SV;

What Is a Subquery?

A Subquery is a query nested inside another query - it can appear in SELECT, FROM, WHERE, or HAVING.
SELECT *
FROM SinhVien
WHERE Diem > (
    SELECT AVG(Diem)
    FROM SinhVien
);

Reads as: "Find students whose grade is higher than the average grade of all students." Logic: compute AVG(Diem) first, then take the rows where Diem > AVG.

07 · SUBQUERY IN FROM AND SELECT

Advanced Subquery

This is the new core topic for Week 4.

Subquery in FROM + Alias

SELECT Alias1.Cot1, Alias1.Cot2
FROM
(
    SELECT ...
    FROM ...
    WHERE ...
) AS Alias1
WHERE ...;

SQL builds a temporary result table from the subquery, and the outer query then uses that table:

Original table ➜ Subquery ➜ Temporary result table ➜ Alias ➜ Outer query
SELECT *
FROM
(
    SELECT
        MaSV,
        HoTen,
        Diem
    FROM SinhVien
) AS SV
WHERE SV.Diem >= 8;
SQL Server requires a derived table/subquery in FROM to have a name - FROM (SELECT ...) alone isn't enough, you need FROM (SELECT ...) AS SV.

Subquery in SELECT + Alias

This is an especially important part of Week 4.

SELECT
    <main column>,
    (
        SELECT <expression>
        FROM <related table>
        WHERE <linking condition>
    ) AS <alias>
FROM <main table>;

Example - Counting Sales per Product

Tables SanPham(MaSP, TenSP), CTHD(MaHD, MaSP, SoLuong). Problem: "for each product, show how many times it appears in invoice details."

SELECT
    SP.TenSP,
    (
        SELECT COUNT(*)
        FROM CTHD
        WHERE CTHD.MaSP = SP.MaSP
    ) AS SoLanBan
FROM SanPham SP;
ProductTimes Sold
Laptop5
Chuột12
Bàn phím7

Correlated Subquery

In the example above, WHERE CTHD.MaSP = SP.MaSP references SP.MaSP from the outer query - this is a correlated subquery: the subquery is recomputed separately for each row of the outer query.

Outer query ➜ SP.MaSP ➜ Subquery ➜ computed separately for each SP

Comparison with a Regular Subquery

A regular subquery runs independently, with no dependency on the outer row:

SELECT *
FROM SanPham
WHERE Gia >
(
    SELECT AVG(Gia)
    FROM SanPham
);

A correlated subquery depends on each outer row (SP.MaSP in the example above).

When Should You Use a Correlated Subquery?

It's a great fit for questions of the form "for each A, compute a value related to A" - for example: how many times has each product been sold? how many courses has each student registered for? how many projects has each employee participated in? how many invoices does each customer have? how many employees does each department have?

Example - Academic Affairs

Tables SINHVIEN(MaSV, HoTen), DANGKY(MaSV, MaHP). Problem: "show each student along with the number of courses they've registered for."

SELECT
    SV.MaSV,
    SV.HoTen,
    (
        SELECT COUNT(*)
        FROM DangKy DK
        WHERE DK.MaSV = SV.MaSV
    ) AS SoHocPhan
FROM SinhVien SV;

Combining CASE + Subquery

Problem: "classify students based on the number of courses they've registered for."

SELECT
    SV.MaSV,
    SV.HoTen,
    CASE
        WHEN
        (
            SELECT COUNT(*)
            FROM DangKy DK
            WHERE DK.MaSV = SV.MaSV
        ) >= 20
        THEN N'Đăng ký nhiều'

        WHEN
        (
            SELECT COUNT(*)
            FROM DangKy DK
            WHERE DK.MaSV = SV.MaSV
        ) >= 15
        THEN N'Bình thường'

        ELSE N'Đăng ký ít'
    END AS PhanLoai
FROM SinhVien SV;

EXISTS and NOT EXISTS

EXISTS checks whether a subquery returns at least 1 row, and the result is only TRUE/FALSE - it doesn't care what value the subquery returns or how many rows it has, unlike IN which must match directly against a list of specific values.
SELECT ...
FROM BangA a
WHERE EXISTS (
    SELECT * FROM BangB b WHERE b.KhoaNgoai = a.KhoaChinh
);

Example - "Find students who registered for at least 1 course" (reusing SinhVien, DangKy from above):

SELECT SV.MaSV, SV.HoTen
FROM SinhVien SV
WHERE EXISTS (
    SELECT * FROM DangKy DK WHERE DK.MaSV = SV.MaSV
);

Flip it to NOT EXISTS to find students who have not registered for any course:

SELECT SV.MaSV, SV.HoTen
FROM SinhVien SV
WHERE NOT EXISTS (
    SELECT * FROM DangKy DK WHERE DK.MaSV = SV.MaSV
);

EXISTS/NOT EXISTS is always a correlated subquery (just like the Correlated Subquery above) - the WHERE DK.MaSV = SV.MaSV clause references the outer row, so the subquery is re-evaluated separately for each student. This is exactly the mechanism behind the "2 nested NOT EXISTS" structure from Week 3 used to solve "for all" problems - a single EXISTS/NOT EXISTS is for a simpler "does this exist or not" question, while 2 nested layers are needed to express "for every".

Why prefer NOT EXISTS over NOT IN? If the inner list for NOT IN contains even a single NULL value, the entire NOT IN expression returns nothing (no rows at all) - a very hard bug to spot. NOT EXISTS never compares values directly, so it never runs into this trap and always gives the correct result.

QUICK CHECK

The MaSV column in the DangKy table could contain NULL. Which approach is safest for finding students who haven't registered for any course?

08 · PUTTING IT ALL TOGETHER

Keyword Recognition & Cheat Sheet

Keyword in the ProblemConcept
Convert data typeCAST()
Current dateGETDATE()
Add/subtract time from a date, exact ageDATEADD()
First / last charactersLEFT() / RIGHT()
Classify / "If... then..."CASE WHEN
Temporary name for a table/columnAlias
A query inside a querySubquery
Compute for each objectCorrelated Subquery
Subquery inside FROMDerived Table + Alias
Subquery inside SELECTScalar / Correlated Subquery
"Does... exist / not exist..."EXISTS / NOT EXISTS
CAST() ➜ Convert data type GETDATE() ➜ Current date + time DATEADD() ➜ Add/subtract time from a date LEFT(x, n) ➜ n characters from the left RIGHT(x, n) ➜ n characters from the right CASE ... END ➜ Condition / classification Alias ➜ Temporary name for a table / column Subquery ➜ A query inside another query FROM (SELECT ...) AS T ➜ Derived Table SELECT (SELECT ...) AS X ➜ Column computed via Subquery EXISTS (SELECT ...) ➜ Does any row match? NOT EXISTS (SELECT ...) ➜ No row matches

The First Four Weeks of IT004

Week 1: creating & manipulating data. Week 2: querying & combining data. Week 3: advanced querying ("all" / grouping / TOP). Week 4: transforming & computing over data with CAST, CASE, Subquery, and EXISTS/NOT EXISTS.

09 · PRACTICE EXERCISES

Comprehensive Practice

OFFICIAL ASSIGNMENT · BTTH4

Academic Affairs Management - Questions 1 ➜ 50

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

View exercise & answers ›
EXTRA PRACTICE · OPTIONAL

After Week 4, here are some extra problems to sharpen your thinking - not part of the BTTH4 submission. Each has a hidden hint - click to reveal.

01

Display the student ID, full name, and the last 3 characters of the student ID.

Hint
RIGHT()
02

Display today's date as a DATE only.

Hint
GETDATE() + CAST()
03

Classify students by grade: ≥8.5 ➜ Excellent, ≥7.0 ➜ Good, ≥5.0 ➜ Pass, <5.0 ➜ Fail.

Hint
CASE
04

Display each student along with the number of courses they've registered for.

Hint
Correlated Subquery
05★

Display each student, the number of courses they've registered for, and a classification: ≥20 ➜ Heavy load, 15-19 ➜ Normal, <15 ➜ Light load.

Hint
CASE + Correlated Subquery
06

Display the student ID and full name of students who haven't registered for any course.

Hint
NOT EXISTS