MINI.MIS - Truy vấn SQL phức tạp

TỔNG QUAN BÀI VIẾT

Truy vấn nối bằng UNION

Mệnh đề UNION được sử dụng để ghép kết quả của hai hoặc nhiều câu lệnh SELECT thành một tập kết quả duy nhất theo chiều dọc (nối thêm các dòng dữ liệu).

Các câu lệnh SELECT tham gia UNION phải trả về cùng số lượng cột, các cột ở cùng vị trí nên có kiểu dữ liệu tương thích và có cùng ý nghĩa.

Cú pháp

SELECT cột1, cột2, ... FROM bảng1
UNION
SELECT cột1, cột2, ...FROM bảng2

Các cách sử dụng

Cách sử dụng Mô tả Ví dụ
Ghép hai bảng có cùng cấu trúc Kết hợp dữ liệu từ hai bảng có cùng số lượng cột. SELECT * FROM [Data2025$] UNION SELECT * FROM [Data2026$]
Ghép các cột được chọn Chỉ ghép các cột cần thiết thay vì toàn bộ dữ liệu. SELECT [Mã KH], [Tên KH] FROM [Customer1$] UNION SELECT [Mã KH], [Tên KH] FROM [Customer2$]
Sắp xếp kết quả Chỉ sử dụng một mệnh đề ORDER BY ở cuối câu lệnh UNION. ... UNION ... ORDER BY [Mã KH]

Lưu ý

Nội dung Ví dụ
Tất cả các câu lệnh SELECT phải có cùng số lượng cột. SELECT [Mã], [Tên] ... UNION SELECT [Mã], [Tên] ...
Các cột ở cùng vị trí nên có kiểu dữ liệu tương thích. Cột thứ nhất đều là mã, cột thứ hai đều là tên.
Tên cột của kết quả được lấy theo câu lệnh SELECT đầu tiên. SELECT [Mã KH], [Tên KH] ... UNION SELECT [Code], [Customer] ... → Kết quả có tiêu đề là Mã KH, Tên KH.
Chỉ được sử dụng một mệnh đề ORDER BY và phải đặt ở cuối toàn bộ câu lệnh UNION. ... UNION ... ORDER BY [Tên KH]
Không sử dụng ORDER BY trong từng câu lệnh SELECT riêng lẻ. Đúng: SELECT ... UNION SELECT ... ORDER BY [Mã]
Có thể sử dụng WHERE, GROUP BYHAVING trong từng câu lệnh SELECT. SELECT ... WHERE ... GROUP BY ... HAVING ... UNION SELECT ...

So sánh UNION và UNION ALL

Câu lệnh Kết quả trả về
UNION Kết hợp kết quả và tự động loại bỏ các dòng trùng lặp.
UNION ALL Kết hợp kết quả và giữ lại tất cả các dòng, kể cả các dòng trùng lặp. Thực hiện nhanh hơn UNION do không phải kiểm tra trùng lặp.

Ví dụ 1: Ghép dữ liệu từ hai worksheet

SELECT [Mã KH], [Tên KH], [Doanh thu] FROM [Customer2025$]
UNION
SELECT [Mã KH], [Tên KH], [Doanh thu] FROM [Customer2026$]

Ví dụ 2: Giữ lại tất cả các bản ghi

SELECT [Mã SP], [Tên SP] FROM [ProductsA$]
UNION ALL
SELECT [Mã SP], [Tên SP] FROM [ProductsB$]

Ví dụ 3: Kết hợp với ORDER BY

SELECT [Mã KH], [Tên KH] FROM [CustomerHN$]
UNION
SELECT [Mã KH], [Tên KH] FROM [CustomerHCM$]
ORDER BY [Tên KH]

Truy vấn ghép bằng JOIN

Mệnh đề JOIN được sử dụng để kết hợp dữ liệu từ hai hoặc nhiều bảng (Worksheet hoặc Named Range) dựa trên một hoặc nhiều trường có liên quan.

Trong SQL VBA Excel (Jet/ACE SQL), các bảng được liên kết thông qua mệnh đề ON, trong đó điều kiện liên kết thường là các trường có cùng ý nghĩa, chẳng hạn như Mã nhân viên, Mã khách hàng, Mã sản phẩm,...

Cú pháp

SELECT danh_sách_cột
FROM bảng1
     INNER JOIN bảng2
     ON điều_kiện_liên_kết

Các kiểu JOIN

Kiểu JOIN Mô tả Ví dụ
INNER JOIN Chỉ trả về các bản ghi có dữ liệu khớp ở cả hai bảng. FROM [Employees$] INNER JOIN [Departments$] ON [Employees$].[Mã PB] = [Departments$].[Mã PB]
LEFT JOIN hoặc LEFT OUTER JOIN Trả về toàn bộ dữ liệu của bảng bên trái và các bản ghi khớp của bảng bên phải. Nếu không có dữ liệu khớp, các trường của bảng bên phải sẽ có giá trị NULL. FROM [Employees$] LEFT JOIN [Departments$] ON [Employees$].[Mã PB] = [Departments$].[Mã PB]
RIGHT JOIN hoặc RIGHT OUTER JOIN Trả về toàn bộ dữ liệu của bảng bên phải và các bản ghi khớp của bảng bên trái. FROM [Employees$] RIGHT JOIN [Departments$] ON [Employees$].[Mã PB] = [Departments$].[Mã PB]

Lưu ý: SQL Excel không hỗ trợ FULL OUTER JOIN.

Các kiểu JOIN

Kiểu JOIN Mô tả Kết quả trả về Ví dụ
INNER JOIN Chỉ trả về các bản ghi có dữ liệu khớp ở cả hai bảng. Chỉ các bản ghi có giá trị liên kết tồn tại đồng thời ở bảng bên trái và bảng bên phải. FROM [Employees$] INNER JOIN [Departments$] ON [Employees$].[Mã PB] = [Departments$].[Mã PB]
LEFT JOIN hoặc LEFT OUTER JOIN Trả về toàn bộ dữ liệu của bảng bên trái và các bản ghi khớp của bảng bên phải. Tất cả các bản ghi của bảng bên trái. Nếu không tìm thấy bản ghi tương ứng ở bảng bên phải thì các trường của bảng bên phải có giá trị NULL. FROM [Employees$] LEFT JOIN [Departments$] ON [Employees$].[Mã PB] = [Departments$].[Mã PB]
RIGHT JOIN hoặc RIGHT OUTER JOIN Trả về toàn bộ dữ liệu của bảng bên phải và các bản ghi khớp của bảng bên trái. Tất cả các bản ghi của bảng bên phải. Nếu không tìm thấy bản ghi tương ứng ở bảng bên trái thì các trường của bảng bên trái có giá trị NULL. FROM [Employees$] RIGHT JOIN [Departments$] ON [Employees$].[Mã PB] =

Lưu ý

Nội dung Ví dụ
Khi hai bảng có cùng tên trường, nên chỉ rõ tên bảng trước tên trường. [Employees$].[Mã PB]
Tên Worksheet trong mệnh đề FROMJOIN phải đặt trong dấu ngoặc vuông ([]) và kết thúc bằng ký tự $. [Employees$]
Có thể sử dụng Worksheet hoặc Named Range trong câu lệnh JOIN. INNER JOIN [DepartmentList] ON ...
Điều kiện liên kết được khai báo trong mệnh đề ON. ON [Orders$].[Mã KH] = [Customers$].[Mã KH]
Có thể kết hợp thêm WHERE, GROUP BY, HAVINGORDER BY sau khi thực hiện JOIN. ... WHERE ... GROUP BY ... ORDER BY ...
Nếu tên trường có khoảng trắng hoặc ký tự tiếng Việt, phải đặt trong dấu ngoặc vuông ([]). [Ngày giao hàng]

Ví dụ 1: INNER JOIN

Lấy danh sách nhân viên cùng tên phòng ban.

SELECT [Employees$].[Mã NV],[Employees$].[Họ tên],[Departments$].[Tên phòng]
FROM [Employees$]
INNER JOIN [Departments$]
ON [Employees$].[Mã PB] = [Departments$].[Mã PB]

Ví dụ 2: LEFT JOIN

Lấy toàn bộ nhân viên, kể cả những nhân viên chưa được phân phòng ban.

SELECT [Employees$].[Mã NV],[Employees$].[Họ tên],[Departments$].[Tên phòng]
FROM [Employees$]
LEFT JOIN [Departments$]
ON [Employees$].[Mã PB] = [Departments$].[Mã PB]

Ví dụ 3: JOIN kết hợp GROUP BY

Thống kê số nhân viên của từng phòng ban.

SELECT [Departments$].[Tên phòng],COUNT(*) AS [Số nhân viên]
FROM [Departments$]
INNER JOIN [Employees$]
ON [Departments$].[Mã PB] = [Employees$].[Mã PB]
GROUP BY [Departments$].[Tên phòng]
ORDER BY COUNT(*) DESC

Truy vấn lồng (Subquery)

Câu lệnh SQL lồng nhau (Subquery) là một câu lệnh SELECT được đặt bên trong một câu lệnh SQL khác. Kết quả của câu lệnh bên trong sẽ được sử dụng làm điều kiện hoặc nguồn dữ liệu cho câu lệnh bên ngoài.

Trong SQL VBA Excel (Jet/ACE SQL), Subquery thường được sử dụng trong các mệnh đề WHERE, FROM và với các toán tử như IN, EXISTS, ANY, ALL.

Cú pháp

SELECT danh_sách_cột
FROM bảng
WHERE biểu_thức toán_tử
      (SELECT ...)

Các cách sử dụng

Cách sử dụng Mô tả Ví dụ
Subquery trong WHERE Sử dụng kết quả của truy vấn con để lọc dữ liệu. SELECT * FROM [Employees$] WHERE [Mã PB] IN (SELECT [Mã PB] FROM [Departments$] WHERE [Khu vực]='Miền Bắc')
Subquery với IN Kiểm tra giá trị có thuộc tập kết quả của truy vấn con hay không. WHERE [Mã KH] IN (SELECT [Mã KH] FROM [Orders$])
Subquery với NOT IN Loại bỏ các giá trị có trong kết quả truy vấn con. WHERE [Mã KH] NOT IN (SELECT [Mã KH] FROM [Orders$])
Subquery với EXISTS Kiểm tra truy vấn con có trả về bản ghi hay không. WHERE EXISTS (SELECT * FROM [Orders$] WHERE [Orders$].[Mã KH]=[Customers$].[Mã KH])
Subquery trong FROM Sử dụng kết quả của một truy vấn làm bảng tạm để truy vấn tiếp. SELECT * FROM (SELECT [Mã KH], SUM([Doanh thu]) AS [Tổng DT] FROM [Sales$] GROUP BY [Mã KH])
Subquery với ANY So sánh với ít nhất một giá trị trong tập kết quả của truy vấn con. WHERE [Lương] > ANY (SELECT [Lương] FROM [Employees$] WHERE [Phòng ban]='Kế toán')
Subquery với ALL So sánh với tất cả các giá trị trong tập kết quả của truy vấn con. WHERE [Lương] > ALL (SELECT [Lương] FROM [Employees$] WHERE [Phòng ban]='Kế toán')

Lưu ý

Nội dung Ví dụ
Truy vấn con phải được đặt trong dấu ngoặc đơn (). WHERE [Mã KH] IN (SELECT [Mã KH] FROM [Orders$])
Truy vấn con trong IN hoặc NOT IN chỉ nên trả về một cột. SELECT [Mã KH] FROM [Orders$]
Khi sử dụng toán tử so sánh (=, >, <, >=, <=, <>), truy vấn con phải trả về một giá trị duy nhất. WHERE [Lương] > (SELECT AVG([Lương]) FROM [Employees$])
Có thể sử dụng hàm tổng hợp trong truy vấn con. SELECT AVG([Lương]) FROM [Employees$]
Có thể kết hợp WHERE, GROUP BYHAVING trong truy vấn con. SELECT [Mã PB] FROM [Employees$] GROUP BY [Mã PB] HAVING COUNT(*) >= 10
Nếu truy vấn con trả về nhiều dòng nhưng toán tử chỉ chấp nhận một giá trị (=, >, <...), câu lệnh sẽ phát sinh lỗi. Trong trường hợp này nên sử dụng IN, ANY hoặc ALL. WHERE [Mã PB] IN (SELECT [Mã PB] FROM [Departments$])

Ví dụ 1: Subquery với IN

Lấy danh sách khách hàng đã phát sinh đơn hàng.

SELECT * FROM [Customers$] WHERE [Mã KH] IN
(
    SELECT [Mã KH] FROM [Orders$]
)

Ví dụ 2: Subquery với hàm tổng hợp

Lấy các nhân viên có lương cao hơn mức lương trung bình.

SELECT * FROM [Employees$] WHERE [Lương] >
(
    SELECT AVG([Lương])    FROM [Employees$]
)

Ví dụ 3: Subquery với EXISTS

Lấy các khách hàng có ít nhất một đơn hàng.

SELECT *
FROM [Customers$]
WHERE EXISTS
(
    SELECT *
    FROM [Orders$]
    WHERE [Orders$].[Mã KH] = [Customers$].[Mã KH]
)

Ví dụ 4: Subquery trong FROM

Tính tổng doanh thu theo khách hàng, sau đó chỉ lấy các khách hàng có tổng doanh thu lớn hơn 100.000.000.

SELECT *
FROM
(
    SELECT [Mã KH],
           SUM([Doanh thu]) AS [Tổng doanh thu]
    FROM [Sales$]
    GROUP BY [Mã KH]
) AS Q
WHERE [Tổng doanh thu] > 100000000

Toán tử sử dụng với Subquery

Toán tử Ý nghĩa Điều kiện trả về TRUE Ví dụ
EXISTS Kiểm tra truy vấn con có trả về ít nhất một bản ghi hay không. Truy vấn con trả về từ 1 bản ghi trở lên. WHERE EXISTS (SELECT * FROM [Orders$] WHERE [Orders$].[Mã KH] = [Customers$].[Mã KH])
NOT EXISTS Kiểm tra truy vấn con không trả về bản ghi nào. Truy vấn con không trả về bản ghi. WHERE NOT EXISTS (SELECT * FROM [Orders$] WHERE [Orders$].[Mã KH] = [Customers$].[Mã KH])
ANY (SOME) So sánh với từng giá trị trong tập kết quả của truy vấn con. Điều kiện đúng với ít nhất một giá trị trong tập kết quả. WHERE [Lương] > ANY (SELECT [Lương] FROM [Employees$] WHERE [Phòng ban]='Kế toán')
ALL So sánh với tất cả các giá trị trong tập kết quả của truy vấn con. Điều kiện đúng với mọi giá trị trong tập kết quả. WHERE [Lương] > ALL (SELECT [Lương] FROM [Employees$] WHERE [Phòng ban]='Kế toán')

Ví dụ minh họa

Giả sử truy vấn con trả về các giá trị:

10
20
30
Điều kiện Kết quả Giải thích
25 > ANY (10,20,30) TRUE Đúng vì 25 > 10 hoặc 25 > 20. Chỉ cần đúng với một giá trị.
25 > ALL (10,20,30) FALSE Sai vì 25 không lớn hơn 30. Phải đúng với tất cả các giá trị.
35 > ALL (10,20,30) TRUE Đúng vì 35 lớn hơn 10, 2030.
5 > ANY (10,20,30) FALSE Sai vì 5 không lớn hơn bất kỳ giá trị nào trong tập kết quả.
EXISTS (SELECT ...) TRUE Truy vấn con trả về ít nhất một bản ghi.
NOT EXISTS (SELECT ...) TRUE Truy vấn con không trả về bản ghi nào.

Lưu ý

  • ANYALL chỉ sử dụng với truy vấn con (Subquery), không sử dụng với danh sách giá trị trực tiếp.

  • ANY tương đương với việc điều kiện đúng với ít nhất một giá trị trong tập kết quả.

  • ALL yêu cầu điều kiện đúng với tất cả các giá trị trong tập kết quả.

  • EXISTSNOT EXISTS chỉ kiểm tra sự tồn tại của bản ghi, không quan tâm giá trị của các cột được chọn trong truy vấn con.

  • Trong SQL VBA Excel (Jet/ACE SQL), EXISTS, NOT EXISTS, ANYALL đều được sử dụng cùng với truy vấn con và đặt trong mệnh đề WHERE.


Chuyển đổi từ SQL Server

SQL Server Giải thích ý nghĩa Hỗ trợ trong SQL Excel Cách chuyển đổi Ví dụ chuyển đổi
CASE WHEN ... THEN ... END Phân nhánh điều kiện, trả về giá trị theo từng điều kiện. ❌ Không hỗ trợ Sử dụng IIF() hoặc lồng nhiều IIF(). SQL Server: CASE WHEN [Điểm]>=5 THEN 'Đạt' ELSE 'Trượt' END
SQL Excel: IIF([Điểm]>=5,'Đạt','Trượt')
ISNULL(expr, value) Thay thế giá trị NULL bằng giá trị khác. ❌ Không hỗ trợ Sử dụng NZ(expr,value) (nếu hỗ trợ) hoặc IIF(expr IS NULL,value,expr). SQL Server: ISNULL([Ghi chú],'')
SQL Excel: IIF([Ghi chú] IS NULL,'',[Ghi chú])
COALESCE(expr1, expr2,...) Trả về giá trị khác NULL đầu tiên trong danh sách. ❌ Không hỗ trợ Dùng NZ() hoặc lồng nhiều IIF(). SQL Server: COALESCE([Email],[Điện thoại],'N/A')
SQL Excel: IIF([Email] IS NOT NULL,[Email],IIF([Điện thoại] IS NOT NULL,[Điện thoại],'N/A'))
TRY_CAST() Chuyển đổi kiểu dữ liệu, nếu lỗi sẽ trả về NULL. ❌ Không hỗ trợ Kiểm tra dữ liệu trước khi chuyển đổi hoặc xử lý bằng VBA. Thực hiện kiểm tra dữ liệu trước khi gọi CINT(), CDATE()...
TRY_CONVERT() Chuyển đổi kiểu dữ liệu theo định dạng, nếu lỗi trả về NULL. ❌ Không hỗ trợ Kiểm tra dữ liệu trước khi chuyển đổi hoặc xử lý bằng VBA. Thực hiện trong VBA trước khi chạy SQL.
CAST() Chuyển đổi kiểu dữ liệu. ⚠️ Hỗ trợ một phần Sử dụng CINT(), CLNG(), CDBL(), CDATE(), CSTR()... SQL Server: CAST([SL] AS INT)
SQL Excel: CINT([SL])
CONVERT() Chuyển đổi kiểu dữ liệu và định dạng hiển thị. ❌ Không hỗ trợ Sử dụng các hàm chuyển đổi kiểu của VBA/Access. SQL Server: CONVERT(DATE,[Ngày])
SQL Excel: CDATE([Ngày])
FORMAT() Định dạng số, ngày tháng hoặc chuỗi theo mẫu. ⚠️ Hỗ trợ một phần Sử dụng FORMAT() theo cú pháp của chương trình (nếu hỗ trợ). FORMAT([Doanh thu],'#,##0')
STRING_AGG() Ghép nhiều giá trị thành một chuỗi. ❌ Không hỗ trợ Thực hiện bằng VBA hoặc xử lý sau truy vấn. Không có câu lệnh SQL tương đương.
CONCAT_WS() Ghép chuỗi có ký tự phân cách. ❌ Không hỗ trợ Sử dụng CONCAT() hoặc toán tử nối chuỗi. SQL Server: CONCAT_WS('-',A,B)
SQL Excel: CONCAT(A,'-',B)
LEFT() Lấy một số ký tự từ bên trái chuỗi. ⚠️ Hỗ trợ một phần Dùng SUBSTR() hoặc hàm tương đương. SQL Server: LEFT([Mã],3)
SQL Excel: SUBSTR([Mã],1,3)
RIGHT() Lấy một số ký tự từ bên phải chuỗi. ⚠️ Hỗ trợ một phần Dùng SUBSTR() kết hợp LEN() hoặc hàm tương đương. SQL Server: RIGHT([Mã],3)
SQL Excel: SUBSTR([Mã],LEN([Mã])-2,3)
CHARINDEX() Tìm vị trí của chuỗi con trong chuỗi. ❌ Không hỗ trợ Sử dụng INSTR(). SQL Server: CHARINDEX('-',[Mã])
SQL Excel: INSTR([Mã],'-')
PATINDEX() Tìm vị trí chuỗi theo mẫu (LIKE). ❌ Không hỗ trợ Sử dụng LIKE hoặc INSTR(). Tùy từng trường hợp tìm kiếm.
GETDATE() Trả về ngày giờ hiện tại của hệ thống. ❌ Không hỗ trợ Sử dụng NOW(). SQL Server: GETDATE()
SQL Excel: NOW()
CURRENT_TIMESTAMP Trả về ngày giờ hiện tại. ⚠️ Hỗ trợ một phần Sử dụng NOW(). NOW()
ROW_NUMBER() Đánh số thứ tự cho các bản ghi. ❌ Không hỗ trợ Đánh số bằng VBA hoặc sau khi truy vấn. Thêm cột STT sau khi lấy dữ liệu.
RANK() Xếp hạng các bản ghi, có thể có hạng trùng. ❌ Không hỗ trợ Xử lý bằng VBA. Thực hiện tính thứ hạng sau khi truy vấn.

 

×