Tìm hiểu trình tự thực hiện mệnh đề của câu lệnh Select trong SQL Server

Trong bài viết này Stanford sẽ giúp các bạn mới học SQL hiểu rõ hơn về trình tự thực hiện mệnh đề của câu lệnh Select trong hệ quản trị cơ sở dữ liệu SQL Server như thế nào.

SQL là kiểu ngôn ngữ truy vấn cấu trúc, nghĩa là SQL Server sẽ không thực thi những dòng lệnh theo thứ tự bạn đã viết mà thay vào đó nó sẽ viết lại theo ý nó để việc thực thi đạt hiệu quả tốt hơn. Khi bắt đầu tiếp xúc với T-SQL bạn cần làm quen một số thuật ngữ để dễ trao đổi, thảo luận với mọi người đặc biệt hơn là hiểu được ý tác tác giả đang muốn đề cập đến đối tượng nào. Bên cạnh đó, bạn cũng cần hiểu rõ cấu trúc của câu lệnh SELECT và trình tự xử lý ở mức luận lý để viết code T-SQL linh hoạt hơn và không bị dư thừa, gây khó hiểu thậm chí ảnh hưởng tốc độ truy vấn.

Một câu lệnh SELECT trong SQL Server thường sẽ có cấu trúc như sau:
SELECT DISTINCT <TOP> <danh sách các cột>
FROM <tên bảng 1>
<kiểu join> JOIN <tên bảng 2>
ON <các điều kiện join>
WHERE <các điều kiện where>
GROUP BY <danh sách các cột group by>
HAVING <các điều kiện having>
ORDER BY <danh sách các cột order by>

Ví dụ sau sử dụng cơ sở dữ liệu stackoverflow, bạn có thể tải cơ sở dữ liệu stackoverflow tại đây. Hãy cùng xét câu truy vấn dưới đây:

SELECT location, COUNT(*) AS cnum
FROM Users u
    INNER JOIN Posts p ON u.Id = p.OwnerUserId
WHERE PostTypeId = 2
GROUP BY location
HAVING location LIKE '%vietnam%'
ORDER BY cnum
Trong cơ sở dữ liệu stackoverflow này, mỗi câu hỏi hoặc câu trả lời xem như là một bài post và sẽ có PostTypeId tương ứng là 1 cho câu hỏi và 2 cho câu trả lời. Nếu bạn thắc mắc “có bao nhiêu bài post trả lời của những người dùng ở Vietnam có location giống nhau?” thì hãy dùng câu truy vấn trên. Kết quả trả về như hình bên dưới:


Câu lệnh (statement): là một đoạn code T-SQL bất kì mà tuân theo định dạng và cú pháp để SQL Server xem là hợp lệ, chúng có thể là SELECT, UPDATE, INSERT, DELETE,..
Câu truy vấn (query) : Đó là chỉ câu lệnh SELECT
Mệnh đề (clause) : là những thành phần trong một câu lệnh, ví dụ như mệnh đề FROM, mệnh đề WHERE, mệnh đề JOIN, mệnh đề ORDER BY, mệnh đề SELECT
Vị từ (predicate) : có thể hiểu là một biểu thức logic dùng để lọc data hay dễ hình dung hơn là điều kiện tìm kiếm (search condition), predicate có thể có ở mệnh đề ON hoặc HAVING và thường thấy nhất là ở mệnh đề WHERE. Trong câu truy vấn trên có các predicate như "PostTypeId = 2" và "location LIKE '%vietnam%'".
Column alias (bí danh): là một tên mới chúng ta gán cho một cột nào đó vì cột đó chưa có tên do được tạo ra từ việc cộng số hoặc chuỗi, hoặc sử dụng các hàm tập hợp. “COUNT(*) AS cnt” thì cnt là tên của cột đếm sao này trong bảng kết quả trả về.
Hàm tập hợp (aggregate functions) : các hàm thực hiện tính toán tên một tập nhiều giá trị và trả về một giá trị duy nhất. Ví dụ một sốhàm như MIN(), MAX(), AVG(), COUNT() .

Trong ví dụ trên, giả sử các bạn chỉ muốn lấy ra những location có số lượng câu trả lời lớn hơn 10 thì làm sao? Các bạn có để ý là alias cnum ở mệnh đề SELECT được sử dụng ở mệnh đề ORDER BY hay không, liệu chúng ta có thể dùng nó đặt ở mệnh đề WHERE hoặc mệnh đề HAVING để loại bỏ những location nào có số lượng câu trả lời dưới 10 được không? Các bạn hãy thử viết câu truy vấn thực hiện yêu cầu trên xem sao. Câu truy vấn của mình như sau:

SELECT location, COUNT(*) AS cnum
FROM Users u
    INNER JOIN Posts p ON u.Id = p.OwnerUserId
WHERE PostTypeId = 2
GROUP BY location
HAVING location LIKE '%vietnam%' AND COUNT(*) > 10
ORDER BY cnum;
Tại sao mình ko dùng cột cnum (giống như nó đang được dùng ở mệnh đề ORDER BY) để loại bỏ những dòng không thỏa mãn trên mệnh đề HAVING mà phải đếm lại số lượng post? Đơn giản rằng lúc SQL Server chạy tới mệnh đề HAVING thì chưa tồn tại column alias cnum đó đâu nha các bạn, để hiểu rõ hơn tình huống này chúng ta hãy cùng tìm hiểu trình tự thực thi các mệnh đề trong một câu truy vấn như sau.
(7) SELECT (8) DISTINCT (10) <TOP> <danh sách các cột>
(1) FROM <tên bảng 1>
(3) <kiểu join> JOIN <tên bảng 2>
(2) ON <các điều kiện join>
(4) WHERE <các điều kiện where>
(5) GROUP BY <danh sách các cột group by>
(6) HAVING <các điều kiện having>
(9) ORDER BY <danh sách các cột order by>

Để dễ tiếp cận các bạn hãy hình dung mỗi bước như vậy sẽ nhận data (dạng bảng) từ bước liền trước rồi thực hiện chức năng của bước đó trả kết quả (cũng dạng bảng) cho bước sau nó.

  1. FROM: lấy từng dòng của bảng 1 join với từng dòng của bảng 2 (cross join) và kết quả sẽ ra 1 bảng data DL1.
  2. ON: tiếp theo predicate trên mệnh đề ON sẽ được sử dụng để loại bỏ những dòng join không thỏa mãn. Như trong ví dụ của chúng ta là "u.Id = p.OwnerUserId" và những dòng thỏa điều kiện trên sẽ được insert vào bảng DL2.
  3. INNER JOIN: (ngoài ra còn có CROSS JOIN và OUTER JOIN) tùy vào kiểu join mà data ở bảng 1 hoặc bảng 2 mặc dù không thỏa mệnh đề ON vẫn có thể được insert vào bảng kết quả DL3. Nếu có nhiều hơn hai bảng ở mệnh đề FROM thì SQL Server sẽ lặp lại các bước từ 1 đến 3 cho bảng kết quả của phép join những bảng trước với bảng kế tiếp. Cứ như vậy cho đến hết các bảng trong mệnh đề FROM.
  4. WHERE: điều kiện tìm kiếm ở mệnh đề WHERE sẽ được sử dụng để lấy những dòng kết quả insert vào bảng DL4.
  5. GROUP BY: SQL Server sẽ gom nhóm các dòng data trong bảng DL4 theo danh sách các cột chỉ định trên mệnh đề GROUP BY, tạo ra bảng DL5.
  6. HAVING: điều kiện tìm kiếm ở mệnh đề HAVING được sử dụng để lấy những dòng thỏa mãn và insert vào bảng DL6.
  7. SELECT: ở bước này chỉ lấy những cột được chỉ định ở mệnh đề này và insert vào bảng DL7. Các alias được gán sẽ là tên cột của bảng DL7.
  8. DISTINCT: loại bỏ những dòng trùng lắp trong bảng DL7. kết quả sẽ là bảng DL8.
  9. ORDER BY: Những dòng trong bảng DL8 sẽ được sắp xếp theo thứ tự các cột được chỉ định trên mệnh đề ORDER BY này. Và đây là bước đầu tiên cũng là bước duy nhất có thể sử dụng column alias được tạo ra trong mệnh đề SELECT ở step 7. Bước này có xíu khác biệt so với các bước trước đó là kết quả của bước này không phải một bảng bình thường mà vì nó có thứ tự nên sẽ là một cursor, vậy nên tập kết quả này đặt tên là DL9.
  10. TOP: mệnh đề này sẽ xác định số lượng dòng sẽ được select từ DL9, kết quả sẽ là bảng DL10 và trả về cho người gọi.

SELECT location, COUNT(*) AS cnum
FROM Users u
INNER JOIN Posts p ON u.Id = p.OwnerUserId
WHERE PostTypeId = 2
GROUP BY location
HAVING location LIKE '%vietnam%' AND COUNT(*) > 10
ORDER BY cnum
Xét lại câu truy vấn trên, SQL Server sẽ lấy bảng Users join với bảng Posts (các bạn lưu ý chúng ta đang xử lý câu truy vấn ở mức luận lý nên thứ tự không thay đổi ý nghĩa, ở mức vật lý SQL Server sẽ quyết định thứ tự các bảng được truy xuất, điều này rất quan trọng vì nó ảnh hưởng trực tiếp đến tốc độ truy vấn) sau đó xét điều kiện join của mệnh đề ON để chỉ lấy những bài post của user tương ứng, sau đó loại những bài post không phải là câu trả lời bằng điều kiện PostTypeId = 2 ở mệnh đề WHERE. Tiếp đến sẽ gom nhóm các users có location giống nhau (so sánh text) rồi đếm số lượng các bài post và chỉ lấy ra những nhóm có tổng bài post nhiều hơn 10 bài. Kết quả trả về sẽ có các cột location và số tổng lượng bài post tương ứng với location đó, sắp xếp kết quả theo thứ tự tăng dần của số bài post.

Sau khi hiểu được thứ tự thực thi các mệnh đề trong câu lệnh SELECT các bạn có thắc mắc rằng nếu điều kiện tìm kiếm trong một câu truy vấn được sử dụng càng sớm sẽ càng tốt phải không. Vì khi đó chúng loại bỏ data càng sớm nên số dòng trả về cho các bước sau sẽ ít hơn thành ra chi phí tính toán xử lý sẽ ít hơn, tốc độ truy vấn sẽ nhanh hơn? Hãy nhìn lại ví dụ trên, tại sao chúng ta không đặt điều kiện tìm kiếm "location like '%vietnam%'" ở mệnh đề WHERE thay vì đặt ở mệnh đề HAVING? Liệu khi đó câu truy vấn có gì khác biệt và có chạy nhanh hơn không? Các bạn hãy chạy hai câu truy vấn dưới đây và cảm nhận sự khác biệt thử xem.

--- điều kiện tìm kiếm ở mệnh đề HAVING
SELECT location, COUNT(*) AS cnum
FROM Users u
INNER JOIN Posts p ON u.Id = p.OwnerUserId
WHERE PostTypeId = 2
GROUP BY location
HAVING location LIKE '%vietnam%'
 AND COUNT(*) > 10
ORDER BY cnum;
  
--- điều kiện tìm kiếm ở mệnh đề WHERE
SELECT location, COUNT(*) AS cnum
FROM Users u
INNER JOIN Posts p ON u.Id = p.OwnerUserId
WHERE PostTypeId = 2 AND location LIKE '%vietnam%'
GROUP BY location
HAVING COUNT(*) > 10
ORDER BY cnum;
Để trả lời một cách đúng đắn và đầy đủ đòi hỏi chúng ta cần có thêm một vài kiến thức nền tản khác như chi phí thực thi một câu truy vấn gồm có những thông số nào, SQL Server thực thi câu truy vấn đó ra sao (hiểu execution plan), bao nhiêu tài nguyên đã dùng,…Các bạn có thể tham khảo thêm những bài viết về SQL khác của Stanford trên website nhé.

Như vậy ở bài này Stanford đã chia sẻ với các bạn mới học sql cú pháp thường sử dụng của một câu truy vấn trong SQL Server cùng với những thuật ngữ liên quan. Điều đáng chú ý hơn cả là thứ tự thực thi các mệnh đề trong câu truy vấn, hiểu được dữ liệu luân chuyển giữa các bước sẽ giúp mình viết code T-SQL chính xác và hiệu quả hơn.

Bên cạnh đó nếu bạn đang muốn được đào tạo bài bản từ cơ bản đến nâng cao có thể tham gia ngay khóa học sql cùng chuyên gia giàu kinh nghiệm Stanford tại đây: http://bit.ly/2SLPYFF và nhận ưu đãi hấp dẫn của Stanford trong thời gian này. Bạn có thể gọi theo hotline: 0963 723 236 - 0866 586 366 để được gọi lại tư vấn trực tiếp nhé.

=============================
☎ STANFORD – ĐÀO TẠO VÀ PHÁT TRIỂN CÔNG NGHỆ
Hotline: 0963 723 236 - 0866 586 366
Website: https://stanford.com.vn
Facebook: http://bit.ly/2FN0TYb
Youtube: http://bit.ly/2TkKT7I

Tags: trình tự xử lý câu lệnh sql, học sql cơ bản