Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Số hàng có thứ tự không xác định

Chức năng cửa sổ ROW_NUMBER có nhiều ứng dụng thực tế, ngoài nhu cầu xếp hạng rõ ràng. Hầu hết thời gian, khi bạn tính toán số hàng, bạn cần phải tính toán chúng dựa trên một số thứ tự và bạn cung cấp đặc tả thứ tự mong muốn trong mệnh đề thứ tự cửa sổ của hàm. Tuy nhiên, có những trường hợp bạn cần tính số hàng không theo thứ tự cụ thể; nói cách khác, dựa trên trật tự không xác định. Điều này có thể nằm trên toàn bộ kết quả truy vấn hoặc trong các phân vùng. Ví dụ bao gồm việc gán các giá trị duy nhất cho các hàng kết quả, loại bỏ dữ liệu trùng lặp và trả về bất kỳ hàng nào cho mỗi nhóm.

Lưu ý rằng cần chỉ định số hàng dựa trên thứ tự không xác định khác với việc cần chỉ định chúng dựa trên thứ tự ngẫu nhiên. Với cái trước, bạn chỉ không cần quan tâm đến thứ tự chúng được chỉ định và việc thực thi lặp đi lặp lại truy vấn có tiếp tục gán các số hàng giống nhau cho các hàng giống nhau hay không. Với cái thứ hai, bạn mong đợi các lần thực thi lặp đi lặp lại để tiếp tục thay đổi hàng nào được gán với số hàng. Bài viết này khám phá các kỹ thuật khác nhau để tính toán số hàng với thứ tự không xác định. Hy vọng là tìm ra một kỹ thuật vừa đáng tin cậy vừa tối ưu.

Đặc biệt cảm ơn Paul White về mẹo liên quan đến việc gấp liên tục, về kỹ thuật liên tục thời gian chạy và luôn là một nguồn thông tin tuyệt vời!

Khi đơn đặt hàng quan trọng

Tôi sẽ bắt đầu với các trường hợp mà thứ tự số hàng quan trọng.

Tôi sẽ sử dụng một bảng có tên là T1 trong các ví dụ của mình. Sử dụng mã sau để tạo bảng này và điền vào bảng này với dữ liệu mẫu:

SET NOCOUNT ON;
 
USE tempdb;
 
DROP TABLE IF EXISTS dbo.T1;
GO
 
CREATE TABLE dbo.T1
(
  id INT NOT NULL CONSTRAINT PK_T1 PRIMARY KEY,
  grp VARCHAR(10) NOT NULL,
  datacol INT NOT NULL
);
 
INSERT INTO dbo.T1(id, grp, datacol) VALUES
  (11, 'A', 50),
  ( 3, 'B', 20),
  ( 5, 'A', 40),
  ( 7, 'B', 10),
  ( 2, 'A', 50);

Hãy xem xét truy vấn sau (chúng tôi sẽ gọi nó là Truy vấn 1):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Ở đây bạn muốn các số hàng được chỉ định trong mỗi nhóm được xác định bởi grp cột, được sắp xếp bởi datacol cột. Khi tôi chạy truy vấn này trên hệ thống của mình, tôi nhận được kết quả sau:

id  grp  datacol  n
--- ---- -------- ---
5   A    40       1
2   A    50       2
11  A    50       3
7   B    10       1
3   B    20       2

Số hàng được gán ở đây theo thứ tự xác định một phần và một phần không xác định. Ý tôi muốn nói ở đây là bạn có một sự đảm bảo rằng trong cùng một phân vùng, một hàng có giá trị datacol lớn hơn sẽ nhận được giá trị số hàng lớn hơn. Tuy nhiên, vì datacol không phải là duy nhất trong phân vùng grp, thứ tự gán số hàng giữa các hàng có cùng giá trị grp và datacol là không xác định. Đó là trường hợp với các hàng có giá trị id 2 và 11. Cả hai đều có giá trị grp A và giá trị datacol 50. Khi tôi thực hiện truy vấn này trên hệ thống của mình lần đầu tiên, hàng có id 2 có hàng số 2 và hàng có id 11 có hàng số 3. Đừng bận tâm về khả năng điều này xảy ra trong thực tế trong SQL Server; Nếu tôi chạy lại truy vấn, về mặt lý thuyết, hàng có id 2 có thể được gán với hàng số 3 và hàng có id 11 có thể được gán với hàng số 2.

Nếu bạn cần chỉ định số hàng dựa trên một thứ tự hoàn toàn xác định, đảm bảo kết quả có thể lặp lại qua các lần thực thi truy vấn miễn là dữ liệu cơ bản không thay đổi, bạn cần kết hợp các phần tử trong mệnh đề phân vùng cửa sổ và sắp xếp là duy nhất. Điều này có thể đạt được trong trường hợp của chúng tôi bằng cách thêm id cột vào mệnh đề thứ tự cửa sổ như một dấu ngắt. Mệnh đề OVER sau đó sẽ là:

OVER (PARTITION BY grp ORDER BY datacol, id)

Ở bất kỳ mức độ nào, khi tính toán số hàng dựa trên một số đặc tả thứ tự có ý nghĩa như trong Truy vấn 1, SQL Server cần xử lý các hàng được sắp xếp theo sự kết hợp của các phần tử phân vùng cửa sổ và sắp xếp. Điều này có thể đạt được bằng cách kéo dữ liệu được sắp xếp trước từ một chỉ mục hoặc bằng cách sắp xếp dữ liệu. Hiện tại, không có chỉ mục nào trên T1 để hỗ trợ tính toán ROW_NUMBER trong Truy vấn 1, vì vậy SQL Server phải chọn sắp xếp dữ liệu. Điều này có thể được nhìn thấy trong kế hoạch cho Truy vấn 1 được hiển thị trong Hình 1.

Hình 1:Kế hoạch cho Truy vấn 1 không có chỉ mục hỗ trợ

Lưu ý rằng kế hoạch quét dữ liệu từ chỉ mục được phân cụm với thuộc tính Có thứ tự:Sai. Điều này có nghĩa là quá trình quét không cần trả lại các hàng được sắp xếp theo khóa chỉ mục. Đó là trường hợp vì chỉ mục nhóm được sử dụng ở đây chỉ vì nó tình cờ bao hàm truy vấn chứ không phải vì thứ tự khóa của nó. Sau đó, kế hoạch áp dụng một cách sắp xếp, dẫn đến chi phí tăng thêm, tỷ lệ N Log N và thời gian phản hồi bị trì hoãn. Toán tử Phân đoạn tạo cờ cho biết hàng có phải là hàng đầu tiên trong phân vùng hay không. Cuối cùng, toán tử Dự án trình tự gán số hàng bắt đầu bằng 1 trong mỗi phân vùng.

Nếu bạn muốn tránh phải sắp xếp, bạn có thể chuẩn bị một chỉ mục bao gồm một danh sách chính dựa trên các phần tử phân vùng và sắp xếp, và một danh sách bao gồm dựa trên các phần tử bao trùm. Tôi muốn coi chỉ mục này là chỉ số POC (để phân vùng , đặt hàng bao phủ ). Dưới đây là định nghĩa về POC hỗ trợ truy vấn của chúng tôi:

CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Chạy lại Truy vấn 1:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Kế hoạch cho việc thực hiện này được thể hiện trong Hình 2.

Hình 2:Kế hoạch cho Truy vấn 1 với chỉ mục POC

Quan sát rằng lần này kế hoạch quét chỉ mục POC với thuộc tính Có thứ tự:Đúng. Điều này có nghĩa là quá trình quét đảm bảo rằng các hàng sẽ được trả về theo thứ tự khóa chỉ mục. Vì dữ liệu được lấy theo thứ tự trước từ chỉ mục như nhu cầu của hàm cửa sổ, nên không cần phải sắp xếp rõ ràng. Quy mô của kế hoạch này là tuyến tính và thời gian phản hồi tốt.

Khi đơn hàng không thành vấn đề

Mọi thứ trở nên phức tạp một chút khi bạn cần gán số hàng với một thứ tự hoàn toàn không xác định. Điều tự nhiên cần làm trong trường hợp như vậy là sử dụng hàm ROW_NUMBER mà không chỉ định mệnh đề thứ tự cửa sổ. Trước tiên, hãy kiểm tra xem tiêu chuẩn SQL có cho phép điều này hay không. Dưới đây là phần có liên quan của tiêu chuẩn xác định các quy tắc cú pháp cho các hàm cửa sổ:

Quy tắc cú pháp

5) Gọi WNS là . Đặt WDX là bộ mô tả cấu trúc cửa sổ mô tả cửa sổ được xác định bởi WNS.

6) Nếu , , hoặc ROW_NUMBER được chỉ định, thì:

a) Nếu , , RANK hoặc DENSE_RANK được chỉ định, thì mệnh đề thứ tự cửa sổ WOC của WDX sẽ có mặt.

f) ROW_NUMBER () OVER WNS tương đương với :COUNT (*) OVER (WNS1 ROWS UNBOUNDED PRECEDING)

Lưu ý rằng mục 6 liệt kê các hàm , , hoặc ROW_NUMBER, sau đó mục 6a cho biết điều đó đối với các hàm , , RANK hoặc DENSE_RANK mệnh đề thứ tự cửa sổ sẽ có mặt. Không có ngôn ngữ rõ ràng nào nói rõ ROW_NUMBER có yêu cầu mệnh đề thứ tự cửa sổ hay không, nhưng việc đề cập đến hàm trong mục 6 và việc bỏ sót nó trong mục 6a có thể ngụ ý rằng mệnh đề này là tùy chọn cho hàm này. Rõ ràng là tại sao các hàm như RANK và DENSE_RANK lại yêu cầu mệnh đề thứ tự cửa sổ, vì các hàm này chuyên xử lý các mối quan hệ và các mối quan hệ chỉ tồn tại khi có đặc tả thứ tự. Tuy nhiên, bạn chắc chắn có thể thấy hàm ROW_NUMBER có thể được lợi như thế nào từ điều khoản thứ tự cửa sổ tùy chọn.

Vì vậy, hãy thử và cố gắng tính số hàng không có thứ tự cửa sổ trong SQL Server:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER() AS n 
FROM dbo.T1;

Nỗ lực này dẫn đến lỗi sau:

Msg 4112, Level 15, State 1, Line 53
Hàm 'ROW_NUMBER' phải có mệnh đề OVER với ORDER BY.

Thật vậy, nếu bạn kiểm tra tài liệu của SQL Server về hàm ROW_NUMBER, bạn sẽ tìm thấy văn bản sau:

“order_by_clause

Mệnh đề ORDER BY xác định trình tự trong đó các hàng được gán ROW_NUMBER duy nhất của chúng trong một phân vùng được chỉ định. Nó là bắt buộc. ”

Vì vậy, rõ ràng mệnh đề thứ tự cửa sổ là bắt buộc đối với hàm ROW_NUMBER trong SQL Server. Nhân tiện, đó cũng là trường hợp của Oracle.

Tôi phải nói rằng tôi không chắc mình hiểu lý do đằng sau yêu cầu này. Hãy nhớ rằng bạn đang cho phép xác định số hàng dựa trên một thứ tự không xác định một phần, như trong Truy vấn 1. Vậy tại sao không cho phép chủ nghĩa không xác định hoàn toàn? Có lẽ có một số lý do mà tôi không nghĩ đến. Nếu bạn có thể nghĩ ra lý do như vậy, hãy chia sẻ.

Ở bất kỳ mức độ nào, bạn có thể lập luận rằng nếu bạn không quan tâm đến đơn đặt hàng, vì điều khoản đặt hàng cửa sổ là bắt buộc, bạn có thể chỉ định bất kỳ đơn hàng nào. Vấn đề với cách tiếp cận này là nếu bạn sắp xếp theo một số cột từ (các) bảng được truy vấn, điều này có thể liên quan đến một hình phạt hiệu suất không cần thiết. Khi không có chỉ mục hỗ trợ, bạn sẽ phải trả tiền cho việc phân loại rõ ràng. Khi có một chỉ mục hỗ trợ, bạn đang giới hạn công cụ lưu trữ trong chiến lược quét thứ tự chỉ mục (theo sau danh sách liên kết chỉ mục). Bạn không cho phép nó linh hoạt hơn như thường có khi đơn đặt hàng không quan trọng trong việc lựa chọn giữa quét đơn đặt hàng chỉ mục và quét đơn đặt hàng phân bổ (dựa trên các trang IAM).

Một ý tưởng đáng thử là chỉ định một hằng số, như 1, trong mệnh đề thứ tự cửa sổ. Nếu được hỗ trợ, bạn hy vọng rằng trình tối ưu hóa đủ thông minh để nhận ra rằng tất cả các hàng đều có cùng giá trị, do đó không có mức độ liên quan sắp xếp thực sự và do đó không cần buộc phải sắp xếp hoặc quét thứ tự lập chỉ mục. Đây là một truy vấn đang cố gắng tiếp cận này:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1) AS n 
FROM dbo.T1;

Rất tiếc, SQL Server không hỗ trợ giải pháp này. Nó tạo ra lỗi sau:

Msg 5308, Mức 16, Trạng thái 1, Dòng 56
Các hàm cửa sổ, tổng hợp và các hàm NEXT VALUE FOR không hỗ trợ các chỉ số nguyên dưới dạng biểu thức mệnh đề ORDER BY.

Rõ ràng, SQL Server giả định rằng nếu bạn đang sử dụng một hằng số nguyên trong mệnh đề thứ tự cửa sổ, nó đại diện cho vị trí thứ tự của một phần tử trong danh sách CHỌN, giống như khi bạn chỉ định một số nguyên trong mệnh đề ORDER BY của bản trình bày. Nếu đúng như vậy, một tùy chọn khác cũng đáng thử là chỉ định một hằng số không có giá trị gia tăng, như sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No Order') AS n 
FROM dbo.T1;

Hóa ra là giải pháp này cũng không được hỗ trợ. SQL Server tạo ra lỗi sau:

Msg 5309, Mức 16, Trạng thái 1, Dòng 65
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ hằng số dưới dạng biểu thức mệnh đề ORDER BY.

Rõ ràng, mệnh đề thứ tự cửa sổ không hỗ trợ bất kỳ loại hằng số nào.

Cho đến nay, chúng tôi đã tìm hiểu những điều sau về mức độ liên quan sắp xếp cửa sổ của hàm ROW_NUMBER trong SQL Server:

  1. ORDER BY là bắt buộc.
  2. Không thể sắp xếp theo một hằng số nguyên vì SQL Server cho rằng bạn đang cố gắng chỉ định một vị trí thứ tự trong SELECT.
  3. Không thể sắp xếp theo bất kỳ loại hằng số nào.

Kết luận là bạn phải sắp xếp theo các biểu thức không phải là hằng số. Rõ ràng, bạn có thể sắp xếp theo danh sách cột từ (các) bảng được truy vấn. Nhưng chúng tôi đang thực hiện nhiệm vụ tìm ra một giải pháp hiệu quả trong đó trình tối ưu hóa có thể nhận ra rằng không có sự liên quan theo thứ tự.

Gấp liên tục

Kết luận cho đến nay là bạn không thể sử dụng hằng số trong mệnh đề thứ tự cửa sổ của ROW_NUMBER, nhưng còn những biểu thức dựa trên hằng số, chẳng hạn như trong truy vấn sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+0) AS n 
FROM dbo.T1;

Tuy nhiên, nỗ lực này trở thành nạn nhân của một quá trình được gọi là liên tục gấp, thường có tác động tích cực đến hiệu suất đối với các truy vấn. Ý tưởng đằng sau kỹ thuật này là cải thiện hiệu suất truy vấn bằng cách gấp một số biểu thức dựa trên hằng số thành hằng số kết quả của chúng ở giai đoạn đầu của quá trình xử lý truy vấn. Bạn có thể tìm thấy chi tiết về những loại biểu thức nào có thể được gấp lại liên tục tại đây. Biểu thức 1 + 0 của chúng tôi được gấp lại thành 1, dẫn đến lỗi rất giống bạn mắc phải khi chỉ định trực tiếp hằng số 1:

Msg 5308, Mức 16, Trạng thái 1, Dòng 79
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ các chỉ số nguyên dưới dạng biểu thức mệnh đề ORDER BY.

Bạn sẽ gặp phải tình huống tương tự khi cố gắng nối hai ký tự chuỗi ký tự, như sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No' + ' Order') AS n 
FROM dbo.T1;

Bạn gặp phải lỗi tương tự mà bạn đã mắc phải khi chỉ định trực tiếp từ 'Không có đơn hàng':

Msg 5309, Mức 16, Trạng thái 1, Dòng 55
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ hằng số dưới dạng biểu thức mệnh đề ORDER BY.

Thế giới kỳ lạ - các lỗi ngăn chặn lỗi

Cuộc sống đầy những điều bất ngờ…

Một điều ngăn cản việc gấp liên tục là khi biểu thức thường dẫn đến lỗi. Ví dụ:biểu thức 2147483646 + 1 có thể được gấp lại không đổi vì nó dẫn đến giá trị kiểu INT hợp lệ. Do đó, nỗ lực chạy truy vấn sau không thành công:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n 
FROM dbo.T1;
Msg 5308, Mức 16, Trạng thái 1, Dòng 109
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ các chỉ số nguyên dưới dạng biểu thức mệnh đề ORDER BY.

Tuy nhiên, biểu thức 2147483647 + 1 không thể được gấp lại liên tục vì nỗ lực như vậy sẽ dẫn đến lỗi tràn INT. Hàm ý về việc đặt hàng khá thú vị. Hãy thử truy vấn sau (chúng tôi sẽ gọi đây là Truy vấn 2):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n 
FROM dbo.T1;

Thật kỳ lạ, truy vấn này chạy thành công! Điều gì xảy ra là một mặt, SQL Server không áp dụng việc gấp liên tục và do đó thứ tự dựa trên một biểu thức không phải là một hằng số duy nhất. Mặt khác, trình tối ưu hóa tính toán rằng giá trị thứ tự là giống nhau cho tất cả các hàng, vì vậy nó hoàn toàn bỏ qua biểu thức thứ tự. Điều này được xác nhận khi kiểm tra kế hoạch cho truy vấn này như thể hiện trong Hình 3.

Hình 3:Kế hoạch cho Truy vấn 2

Quan sát rằng kế hoạch quét một số chỉ mục bao gồm thuộc tính Có thứ tự:Sai. Đây chính xác là mục tiêu hiệu suất của chúng tôi.

Theo cách tương tự, truy vấn sau liên quan đến nỗ lực gấp liên tục thành công và do đó không thành công:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/1) AS n 
FROM dbo.T1;
Msg 5308, Mức 16, Trạng thái 1, Dòng 123
Các hàm cửa sổ, tổng hợp và các hàm NEXT VALUE FOR không hỗ trợ các chỉ số nguyên dưới dạng biểu thức mệnh đề ORDER BY.

Truy vấn sau liên quan đến một nỗ lực gấp liên tục không thành công và do đó thành công, tạo ra kế hoạch được hiển thị trước đó trong Hình 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/0) AS n 
FROM dbo.T1;

Truy vấn sau liên quan đến nỗ lực gấp liên tục thành công (VARCHAR theo nghĩa đen '1' được chuyển đổi ngầm thành INT 1 và sau đó 1 + 1 được gấp lại thành 2) và do đó không thành công:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'1') AS n 
FROM dbo.T1;
Msg 5308, Mức 16, Trạng thái 1, Dòng 134
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ các chỉ số nguyên dưới dạng biểu thức mệnh đề ORDER BY.

Truy vấn sau liên quan đến nỗ lực gấp liên tục không thành công (không thể chuyển đổi 'A' thành INT), và do đó thành công, tạo ra kế hoạch được hiển thị trước đó trong Hình 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'A') AS n 
FROM dbo.T1;

Thành thật mà nói, ngay cả khi kỹ thuật này đạt được mục tiêu hiệu suất ban đầu của chúng tôi, tôi không thể nói rằng tôi coi nó là an toàn và do đó tôi không cảm thấy thoải mái khi dựa vào nó.

Hằng số thời gian chạy dựa trên các hàm

Tiếp tục tìm kiếm giải pháp tốt để tính toán số hàng với thứ tự không xác định, có một số kỹ thuật có vẻ an toàn hơn giải pháp kỳ quặc cuối cùng:sử dụng hằng số thời gian chạy dựa trên hàm, sử dụng truy vấn con dựa trên hằng số, sử dụng cột bí danh dựa trên một hằng số và sử dụng một biến.

Như tôi đã giải thích trong các lỗi, cạm bẫy và phương pháp hay nhất của T-SQL - tính xác định, hầu hết các hàm trong T-SQL chỉ được đánh giá một lần cho mỗi tham chiếu trong truy vấn — không phải một lần cho mỗi hàng. Đây là trường hợp ngay cả với hầu hết các hàm không xác định như GETDATE và RAND. Có rất ít ngoại lệ đối với quy tắc này, như các hàm NEWID và CRYPT_GEN_RANDOM, được đánh giá một lần trên mỗi hàng. Hầu hết các hàm, như GETDATE, @@ SPID và nhiều hàm khác, được đánh giá một lần khi bắt đầu truy vấn và giá trị của chúng sau đó được coi là hằng số thời gian chạy. Tham chiếu đến các chức năng như vậy không được gấp lại liên tục. Những đặc điểm này làm cho một hằng số thời gian chạy dựa trên một chức năng trở thành một lựa chọn tốt làm phần tử sắp xếp cửa sổ và thực sự, có vẻ như T-SQL hỗ trợ nó. Đồng thời, trình tối ưu hóa nhận ra rằng trong thực tế không có sự liên quan đến thứ tự, tránh các hình phạt hiệu suất không cần thiết.

Dưới đây là một ví dụ sử dụng hàm GETDATE:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n 
FROM dbo.T1;

Truy vấn này nhận được cùng một kế hoạch được hiển thị trước đó trong Hình 3.

Dưới đây là một ví dụ khác sử dụng hàm @@ SPID (trả về ID phiên hiện tại):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @@SPID) AS n 
FROM dbo.T1;

Điều gì về chức năng PI? Hãy thử truy vấn sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY PI()) AS n 
FROM dbo.T1;

Cái này không thành công với lỗi sau:

Msg 5309, Mức 16, Trạng thái 1, Dòng 153
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ hằng số dưới dạng biểu thức mệnh đề ORDER BY.

Các hàm như GETDATE và @@ SPID được đánh giá lại một lần cho mỗi lần thực hiện kế hoạch, vì vậy chúng không thể bị gấp lại liên tục. PI đại diện cho cùng một hằng số luôn luôn, và do đó không đổi được gấp lại.

Như đã đề cập trước đó, có rất ít hàm được đánh giá một lần trên mỗi hàng, chẳng hạn như NEWID và CRYPT_GEN_RANDOM. Điều này làm cho chúng trở thành một lựa chọn tồi như là yếu tố sắp xếp cửa sổ nếu bạn cần thứ tự không xác định — đừng nhầm lẫn với thứ tự ngẫu nhiên. Tại sao phải trả một hình phạt phân loại không cần thiết?

Dưới đây là một ví dụ sử dụng hàm NEWID:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY NEWID()) AS n 
FROM dbo.T1;

Kế hoạch cho truy vấn này được hiển thị trong Hình 4, xác nhận rằng SQL Server đã thêm tính năng sắp xếp rõ ràng dựa trên kết quả của hàm.

Hình 4:Kế hoạch cho Truy vấn 3

Nếu bạn muốn các số hàng được gán theo thứ tự ngẫu nhiên, thì đó là kỹ thuật bạn muốn sử dụng. Bạn chỉ cần lưu ý rằng nó phải chịu chi phí sắp xếp.

Sử dụng truy vấn con

Bạn cũng có thể sử dụng truy vấn con dựa trên một hằng số làm biểu thức sắp xếp cửa sổ (ví dụ:ORDER BY (CHỌN 'Không có thứ tự')). Cũng với giải pháp này, trình tối ưu hóa của SQL Server nhận ra rằng không có sự liên quan đến thứ tự và do đó không áp đặt một loại sắp xếp không cần thiết hoặc giới hạn các lựa chọn của công cụ lưu trữ đối với những lựa chọn phải đảm bảo thứ tự. Hãy thử chạy truy vấn sau làm ví dụ:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'No Order')) AS n 
FROM dbo.T1;

Bạn nhận được cùng một kế hoạch được hiển thị trước đó trong Hình 3.

Một trong những lợi ích tuyệt vời của kỹ thuật này là bạn có thể thêm dấu ấn cá nhân của riêng mình. Có thể bạn thực sự thích NULLs:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
FROM dbo.T1;

Có thể bạn thực sự thích một số nhất định:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n 
FROM dbo.T1;

Có thể bạn muốn gửi cho ai đó một tin nhắn:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'Lilach, will you marry me?')) AS n 
FROM dbo.T1;

Bạn hiểu rõ.

Có thể làm được, nhưng khó xử

Có một số kỹ thuật có hiệu quả, nhưng hơi khó xử. Một là xác định bí danh cột cho một biểu thức dựa trên một hằng số, sau đó sử dụng bí danh cột đó làm phần tử sắp xếp cửa sổ. Bạn có thể thực hiện việc này bằng cách sử dụng biểu thức bảng hoặc bằng toán tử ÁP DỤNG CHÉO và một hàm tạo giá trị bảng. Đây là một ví dụ cho cái sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY [I'm a bit ugly]) AS n 
FROM dbo.T1 CROSS APPLY ( VALUES('No Order') ) AS A([I'm a bit ugly]);

Bạn nhận được cùng một kế hoạch được hiển thị trước đó trong Hình 3.

Một tùy chọn khác là sử dụng một biến làm phần tử sắp xếp cửa sổ:

DECLARE @ImABitUglyToo AS INT = NULL;
 
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n 
FROM dbo.T1;

Truy vấn này cũng nhận được kế hoạch được hiển thị trước đó trong Hình 3.

Điều gì sẽ xảy ra nếu tôi sử dụng UDF của riêng mình?

Bạn có thể nghĩ rằng việc sử dụng UDF của riêng mình để trả về một hằng số có thể là một lựa chọn tốt làm phần tử sắp xếp cửa sổ khi bạn muốn thứ tự không xác định, nhưng không phải vậy. Hãy xem xét định nghĩa UDF sau đây làm ví dụ:

DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;
GO
 
CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INT
AS
BEGIN
  RETURN NULL
END;
GO

Hãy thử sử dụng UDF làm mệnh đề sắp xếp cửa sổ, như vậy (chúng tôi sẽ gọi đây là Truy vấn 4):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n 
FROM dbo.T1;

Trước SQL Server 2019 (hoặc mức độ tương thích song song <150), các hàm do người dùng xác định sẽ được đánh giá trên mỗi hàng. Ngay cả khi chúng trả về một hằng số, chúng không được nội tuyến. Do đó, một mặt bạn có thể sử dụng UDF như một phần tử sắp xếp cửa sổ, nhưng mặt khác, điều này dẫn đến một hình phạt sắp xếp. Điều này được xác nhận bằng cách kiểm tra kế hoạch cho truy vấn này, như thể hiện trong Hình 5.

Hình 5:Kế hoạch cho Truy vấn 4

Bắt đầu với SQL Server 2019, ở mức độ tương thích> =150, các hàm do người dùng xác định như vậy sẽ được nội tuyến, đây hầu hết là một điều tuyệt vời, nhưng trong trường hợp của chúng tôi, dẫn đến lỗi:

Msg 5309, Mức 16, Trạng thái 1, Dòng 217
Các hàm cửa sổ, hàm tổng hợp và hàm NEXT VALUE FOR không hỗ trợ hằng số dưới dạng biểu thức mệnh đề ORDER BY.

Vì vậy, việc sử dụng UDF dựa trên một hằng số làm phần tử sắp xếp cửa sổ buộc sắp xếp hoặc lỗi tùy thuộc vào phiên bản SQL Server bạn đang sử dụng và mức độ tương thích cơ sở dữ liệu của bạn. Tóm lại, đừng làm điều này.

Số hàng được phân vùng với thứ tự không xác định

Một trường hợp sử dụng phổ biến cho số hàng được phân vùng dựa trên thứ tự không xác định là trả về bất kỳ hàng nào cho mỗi nhóm. Cho rằng theo định nghĩa, một phần tử phân vùng tồn tại trong trường hợp này, bạn sẽ nghĩ rằng một kỹ thuật an toàn trong trường hợp như vậy sẽ là sử dụng phần tử phân vùng cửa sổ cũng như phần tử sắp xếp cửa sổ. Bước đầu tiên, bạn tính toán các số hàng như sau:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
FROM dbo.T1;

Kế hoạch cho truy vấn này được thể hiện trong Hình 6.

Hình 6:Kế hoạch cho Truy vấn 5

Lý do mà chỉ mục hỗ trợ của chúng tôi được quét bằng thuộc tính Order:True là vì SQL Server cần xử lý các hàng của mỗi phân vùng như một đơn vị duy nhất. Đó là trường hợp trước khi lọc. Nếu bạn chỉ lọc một hàng cho mỗi phân vùng, bạn có cả các thuật toán dựa trên thứ tự và dựa trên băm làm tùy chọn.

Bước thứ hai là đặt truy vấn có tính toán số hàng trong biểu thức bảng và trong truy vấn bên ngoài lọc hàng có số hàng 1 trong mỗi phân vùng, như sau:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Về mặt lý thuyết, kỹ thuật này được cho là an toàn, nhưng Paul white đã tìm thấy một lỗi cho thấy rằng sử dụng phương pháp này, bạn có thể lấy các thuộc tính từ các hàng nguồn khác nhau trong hàng kết quả trả về trên mỗi phân vùng. Sử dụng một hằng số thời gian chạy dựa trên một hàm hoặc một truy vấn con dựa trên một hằng số vì phần tử sắp xếp có vẻ an toàn ngay cả với trường hợp này, vì vậy hãy đảm bảo rằng bạn sử dụng một giải pháp như sau để thay thế:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY (SELECT 'No Order')) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Không ai được vượt qua lối này nếu không có sự cho phép của tôi

Cố gắng tính số hàng dựa trên thứ tự không xác định là một nhu cầu phổ biến. Sẽ thật tuyệt nếu T-SQL chỉ đơn giản là đặt mệnh đề thứ tự cửa sổ là tùy chọn cho hàm ROW_NUMBER, nhưng không. Nếu không, sẽ rất tuyệt nếu ít nhất nó cũng cho phép sử dụng một hằng số làm phần tử sắp xếp, nhưng đó cũng không phải là một tùy chọn được hỗ trợ. Nhưng nếu bạn hỏi một cách dễ hiểu, dưới dạng một truy vấn con dựa trên một hằng số hoặc một hằng số thời gian chạy dựa trên một hàm, SQL Server sẽ cho phép điều đó. Đây là hai lựa chọn mà tôi thấy thoải mái nhất. Tôi không thực sự cảm thấy thoải mái với những biểu hiện sai lầm kỳ quặc có vẻ hiệu quả, vì vậy tôi không thể đề xuất tùy chọn này.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách tham gia trên nhiều cột

  2. Anh bạn, ai sở hữu cái bàn #temp đó?

  3. Django Migrations:A Primer

  4. Điều chỉnh Hiệu suất Toàn bộ Kế hoạch Truy vấn

  5. Tìm các cột được trả về bởi một hàm có giá trị bảng (Ví dụ T-SQL)