TOP và ORDER BY là 2 trong nhiều mệnh đề được sử dụng rộng rãi nhất trong lập trình CSDL. Nhưng chúng cũng là những mệnh đề mà ít người chịu khó tìm hiểu cách thức hoạt động, dẫn đến nhiều hiểu lầm, nhiều cách sử dụng sai.
Trước hết ta phải xét đến cái lõi của vấn đề mặc dù nó không ảnh hưởng trực tiếp đến lập trình. ORDER BY dùng để biểu thị dữ liệu được sắp xếp theo 1 trật tự nhất định, trong khi bảng (CSDL quan hệ - nói chung), không có 1 trật tự nào cả. Nếu các dòng được sắp xếp theo 1 trật tự nào đó, thì nó không còn là bảng nữa. Vậy ORDER BY chỉ nên là bước cuối cùng trước khi biểu thị ở ứng dụng.
Trước hết ta phải xét đến cái lõi của vấn đề mặc dù nó không ảnh hưởng trực tiếp đến lập trình. ORDER BY dùng để biểu thị dữ liệu được sắp xếp theo 1 trật tự nhất định, trong khi bảng (CSDL quan hệ - nói chung), không có 1 trật tự nào cả. Nếu các dòng được sắp xếp theo 1 trật tự nào đó, thì nó không còn là bảng nữa. Vậy ORDER BY chỉ nên là bước cuối cùng trước khi biểu thị ở ứng dụng.
Tiếp theo, xét 2 câu lệnh sau đây:
1 - select top 10 * from bangA; 2 - select top 10 * from bangA order by truong1;
Đây là 2 câu lệnh rất đơn giản và cũng rất phổ biến, nhưng ít người lưu tâm đến hoạt động của chúng dẫn đến nhiều ngộ nhận, để rồi kết quả trả về sai với tính toán của mình.
Tôi đã nghe nhiều lập trình viên bảo rằng câu 1 sẽ trả về 10 dòng đầu tiên của bảng bangA, và họ còn nhấn mạnh họ đã chạy thử nhiều lần và tất cả kết quả đều đúng như thế.
Không, câu 1 trả về 10 dòng bất kỳ trong bảng bangA, chứ không phải 10 dòng đầu tiên. Chẳng qua khi ai đó chạy thử thì 10 dòng bất kỳ ấy chính là 10 dòng đầu tiên. Tức 10 dòng đầu tiên chỉ là sự ngẫu nhiên, khi dữ liệu lớn hay có nhiều người sử dụng, kết quả sẽ khác. Nếu bạn dựa vào yếu tố "đầu tiên" để sử dụng các thủ tục tiếp theo thì đó là 1 sai lầm tai hại. Chúng có thể đúng khi bạn chạy thử, nhưng sẽ chạy sai trong môi trường thực.
Điều này tương tự bạn vào 1 lớp học bảo thầy giáo rằng bạn cần 10 học sinh đứng đầu. Bạn sẽ thấy ngay đòi hỏi của mình thiếu chi tiết : đứng đầu về mặt nào (đứng đầu về chiều cao, về điểm số, hay về thời gian ngủ trong lớp) ? Nếu bạn chỉ nói đứng đầu mà không có tiêu chí nào thì rõ rằng thầy giáo có quyền chọn bất cứ 10 người nào trong lớp, mà thường là gần ông ấy nhất (cho nhanh). Và bạn sẽ thấy, đây là kết quả không ổn định, tức lần này ông ấy chọn 10 người này, nhưng rất có thể lần sau lại là 10 người khác.
Nếu muốn có 10 học sinh với tiêu chí nào đó, bạn phải nói rõ như : tôi muốn 10 học sinh của lớp có chiều cao lớn nhất. Lúc ấy, thầy giáo phải lấy số liệu về chiều cao của tất cả học sinh trong lớp, so sánh với nhau, rồi mới chọn ra 10 người đứng đầu. Bạn cũng nhận thấy quá trình này rất mất thì giờ cho thầy giáo, nhưng bảo đảm ông ấy sẽ đưa cho bạn 10 học sinh cao nhất, luôn luôn như thế. Áp dụng cho lập trình, nó cũng mất thì giờ tương tự, trường hợp này có câu lệnh như sau :
select top 10 * from bangA order by chieuCao desc;
Đây chính là khuôn mẫu của câu lệnh 2 đề cập ở trên.
Vậy bạn cũng nhận thấy câu lệnh 1 và 2 đều có ưu/nhược điểm. Bạn cần hiểu chúng để sử dụng cho thích hợp.
- Câu 1 phải nói là rất nhanh. Dù bảng có hàng triệu hay tỉ dòng thì nó trả về kết quả chỉ trong phần ngàn giây. Lý do là nó chi lấy đủ bất kỳ 10 dòng nào, rồi trả kết quả về, mà không hề lưu tâm các dòng khác. Ngay cả khi câu lệnh có WHERE cũng thế, cho dù nó chưa so sánh hết tất cả các dòng với điều kiện ở WHERE thì nó vẫn ngưng nếu đã có đủ 10 dòng.
Tuy nhiên là bạn phải nhớ, 10 dòng này chẳng dựa trên tiêu chí nào cả, chỉ là 10 dòng tồn tại trong bảng bangA mà thôi. Bạn có thể nói như thế thì vô ích. Cái này thì tùy yêu cầu của bạn. Bản thân tôi cũng thấy nó có ích khi tôi muốn biết một cách tương đối bản chất của dữ liệu trong 1 bảng.
- Câu 2 phải nó là rất chậm. Nó phải gom tất cả các dòng trong bảng bỏ sang CSDL tạm, rồi áp dúng thuật toán về trật tự, chon ra 10 dòng, rồi trả về kết quả. Như trường hợp thầy giáo minh họa ở trên, nếu có 1 triệu học sinh trong lớp thì thầy giáo sẽ mất rất nhiều thời giờ để lấy giữ liệu về chiều cao của 1 triệu học sinh, rồi lại so sánh 1 triệu học sinh với nhau.
Tuy nhiên, câu lệnh này luôn ổn định, bảo đảm 10 học sinh đó luôn là 10 học sinh có chiều cao lớn nhất. Bạn cũng hình dung được câu lệnh này nó chậm thế nào, ngay cả khi có index. Do đó, đừng dùng ORDER BY nếu không thực sự cần thiết. Theo kinh nghiệm của tôi thì trong thực tế có khoảng 30% ORDER BY được dùng ngẫu hứng, tức không có cũng không sao, chỉ vì các lập trình viên không ý thực được yếu điểm mà ORDER BY mang lại, nên viết bừa.
Có thể vắn tắt về TOP và ORDER BY như sau:
- Nếu không có ORDER BY, TOP sẽ trả về bất cứ dòng nào tồn tại trong bảng, ứng với số lượng chỉ định (10)
- Nếu có ORDER BY, hệ thống sẽ dùng trường đi kèm với ORDER BY (chieuCao) để sắp xếp thứ tự của chúng trong toàn bộ bảng (với WHERE - nếu có) rồi trả về 10 dòng có chiều cao lớn nhất. Và 10 dòng này cũng được sắp theo thứ tự của trường đi kèm với ORDER BY (chieuCao).
Sau đây là những lưu ý liên quan:
- Bạn có thể chọn 10 người có chiều cao lớn nhất, nhưng khi biểu thị thì biểu thị theo thứ tự của tên được không ? Không, trường (chieuCao) đi kèm với ORDER BY được dùng cho 2 công việc. Thứ nhất là để chọn TOP, và thứ 2 là biểu thị cho ORDER BY. Bạn không thể chọn trường này cho TOP nhưng trường khác cho ORDER BY.
- Bạn có thể sử dụng ORDER BY trong VIEW hay FUNCTION được không? Được, nhưng với điều kiện là trong câu lệnh đó phải có TOP và mục đích của ORDER BY lúc này chỉ để chọn TOP dựa vào trường đi kèm (chieuCao) chứ nó không dùng để biểu thị trật tự của các dòng.
Như đã nói ở trên, bảng là 1 tập hợp các phần tử không có trật tự. Khi bạn dùng ORDER BY để trả về 1 tập hợp có trật tự thì nó không còn là bảng nữa. VIEW và FUNCTION trả về 1 bảng, nên tập hợp nó trả về không có trật tự nào cả, và ORDER BY trong VIEW hay FUNCTION chỉ dùng cho TOP mà thôi.
Một số người cho rằng họ có mẹo để sử dụng ORDER BY trong VIEW, FUNCTION hay CTE bằng cách kết hợp TOP 100 percent và ORDER BY như sau:
;with temp as(select top 100 percent * from bangA order by chieuCao desc) select * from temp;
Câu lệnh sẽ trả về tất cả các dòng của bảng bangA với thứ tự theo chieuCao?
Không, như đã đề cập ở trên, kết quả đúng, nếu có, thì chỉ vì ngẫu nhiên, trong môi trường thực sẽ sai.
Có chỗ em chưa hiểu lắm. Là ví dụ em có trường Code, Name, ChieuCao. Nếu em ORDER BY theo ChieuCao DESC, rồi lấy TOP, nhưng thay vì SELECT TOP 10 ChieuCao, thì em muốn lấy SELECT TOP 10 Name sao lại sai ạ. Vì lúc này bản ghi đã được sắp xếp, ta không xem thông tin ChieuCao, nhưng muốn xem Name thì vẫn phải đúng chứ ạ?
Trả lờiXóaChào bạn
Trả lờiXóaVì bạn không cung cấp câu lệnh cho ví dụ của mình nên tôi không rõ ý của bạn lắm.
Tuy nhiên tôi có thể diễn giải tổng quát thế này.
Giả sử câu lệnh của bạn như sau
select top 10 Code, Name, ChieuCao from bangA order by ChieuCao desc
Câu lệnh này sẽ trả về 10 đầu tiên có ChieuCao lớn nhất, đồng thời kết quả được sắp xếp thứ tự theo ChieuCao giảm dần.
Đó là bản chất của TOP trong SQL SERVER, trường trong ORDER BY được sử dụng để chọn TOP đồng thời để sắp xếp trật tự trả về, như đã trình bày, bạn không thể sử dụng trường nay để chọn TOP nhưng sắp xếp trật tự trả về bằng 1 trường khác. Đại khái như :
select TOP 10 ChieuCao,* from bangA order by Name desc -- câu lệnh này sai
Điều này làm nhiều lập trình viên thấy khó chịu và có lẽ vì thế mà ORACLE không hề có TOP.
Tuy nhiên bạn có thể thêm 1 mẹo nhỏ trong câu lệnh để thực hiện yêu cầu của mình như sau
; with temp as
(select top 10 Code, Name, ChieuCao from bangA order by ChieuCao desc)
select Code, Name, ChieuCao from temp order by Name
Có 2 điều bạn cần lưu ý cho câu lệnh này
1- Hệ thống đã phải sử dụng thuật toán ORDER BY đến 2 lần, do đó câu lệnh có thể chậm hơn rất nhiều
2- Trật tự sắp xếp theo Name chỉ gói gọn trong 10 dòng đầu tiên mà bạn đã chọn dựa vào tiêu chí ChieuCao. Tức rất có thể tồn tạo dòng có giá trị Nam nhỏ nhất nhưng lại không được trả về trong kết quả