Cách tốt nhất để có được một thứ tự ngẫu nhiên là gì?


27

Tôi có một truy vấn mà tôi muốn các bản ghi kết quả được sắp xếp ngẫu nhiên. Nó sử dụng một chỉ mục được nhóm, vì vậy nếu tôi không bao gồm một chỉ mục, order bynó có thể sẽ trả về các bản ghi theo thứ tự của chỉ mục đó. Làm thế nào tôi có thể đảm bảo một thứ tự hàng ngẫu nhiên?

Tôi hiểu rằng nó có thể sẽ không "thực sự" ngẫu nhiên, giả ngẫu nhiên là đủ tốt cho nhu cầu của tôi.

Câu trả lời:


19

ĐẶT HÀNG THEO NEWID () sẽ sắp xếp các bản ghi ngẫu nhiên. Một ví dụ ở đây

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()

7
ĐẶT HÀNG THEO NEWID () có hiệu quả ngẫu nhiên, nhưng không ngẫu nhiên về mặt thống kê. Có một sự khác biệt nhỏ và hầu hết thời gian sự khác biệt không thành vấn đề.
mrdenny

4
Từ quan điểm hiệu suất, điều này khá chậm - bạn có thể nhận được một sự cải thiện đáng kể bằng cách ĐẶT HÀNG B CHNG KIỂM TRA (NEWID ())
Miles D

1
@mrdenny - Bạn dựa vào cái gì "không ngẫu nhiên về mặt thống kê"? Câu trả lời ở đây nói rằng nó kết thúc bằng cách sử dụng CryptGenRandomcuối cùng. dba.stackexchange.com/a/208069/3690
Martin Smith

15

Đề xuất đầu tiên của Pradeep Adiga ORDER BY NEWID(), là tốt và một cái gì đó tôi đã sử dụng trong quá khứ vì lý do này.

Hãy cẩn thận với việc sử dụng RAND()- trong nhiều ngữ cảnh, nó chỉ được thực hiện một lần cho mỗi câu lệnh vì vậy ORDER BY RAND()sẽ không có hiệu lực (vì bạn đang nhận được kết quả tương tự từ RAND () cho mỗi hàng).

Ví dụ:

SELECT display_name, RAND() FROM tr_person

trả về mỗi tên từ bảng người của chúng tôi và một số "ngẫu nhiên", giống nhau cho mỗi hàng. Số lượng thay đổi mỗi lần bạn chạy truy vấn, nhưng giống nhau cho mỗi hàng mỗi lần.

Để chỉ ra rằng trường hợp tương tự được RAND()sử dụng trong ORDER BYmệnh đề, tôi thử:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

Các kết quả vẫn được sắp xếp theo tên cho biết trường sắp xếp trước đó (trường được dự kiến ​​là ngẫu nhiên) không có tác dụng nên có lẽ luôn có cùng giá trị.

NEWID()Tuy nhiên, việc đặt hàng bằng cách hoạt động, bởi vì nếu NEWID () không phải lúc nào cũng được đánh giá lại thì mục đích của UUID sẽ bị phá vỡ khi chèn nhiều hàng mới trong một sttnt với mã định danh duy nhất làm khóa, vì vậy:

SELECT display_name FROM tr_person ORDER BY NEWID()

không đặt tên "ngẫu nhiên".

DBMS khác

Điều trên đúng với MSSQL (ít nhất là năm 2005 và 2008, và nếu tôi nhớ đúng năm 2000). Một hàm trả về UUID mới phải được đánh giá mỗi lần trong tất cả các DBMS NEWID () nằm dưới MSSQL nhưng đáng để xác minh điều này trong tài liệu và / hoặc bằng các thử nghiệm của riêng bạn. Hành vi của các hàm kết quả tùy ý khác, như RAND (), có nhiều khả năng thay đổi giữa các DBMS, vì vậy hãy kiểm tra lại tài liệu.

Ngoài ra, tôi đã thấy việc đặt hàng theo các giá trị UUID bị bỏ qua trong một số ngữ cảnh vì DB cho rằng loại này không có thứ tự có ý nghĩa. Nếu bạn thấy đây là trường hợp đó, rõ ràng chuyển UUID thành một kiểu chuỗi trong mệnh đề thứ tự hoặc bọc một số hàm khác xung quanh nó như CHECKSUM()trong SQL Server (có thể có một sự khác biệt nhỏ về hiệu năng này vì việc đặt hàng sẽ được thực hiện trên giá trị 32 bit không phải là giá trị 128 bit, mặc dù lợi ích của việc đó có cao hơn chi phí chạy CHECKSUM()trên mỗi giá trị trước tiên tôi sẽ để bạn kiểm tra).

Lưu ý bên

Nếu bạn muốn một thứ tự tùy ý nhưng có thể lặp lại một chút, hãy sắp xếp theo một tập hợp con tương đối không được kiểm soát của dữ liệu trong các hàng. Chẳng hạn, một trong hai sẽ trả lại tên theo thứ tự tùy ý nhưng có thể lặp lại:

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

Các thứ tự tùy ý nhưng có thể lặp lại thường không hữu ích trong các ứng dụng, mặc dù có thể hữu ích trong việc kiểm tra nếu bạn muốn kiểm tra một số mã trên các kết quả theo nhiều đơn đặt hàng nhưng muốn có thể lặp lại mỗi lần chạy theo cùng một vài lần (để có được thời gian trung bình kết quả qua nhiều lần chạy hoặc kiểm tra rằng bản sửa lỗi bạn đã thực hiện đối với mã sẽ loại bỏ sự cố hoặc không hiệu quả được đánh dấu trước đó bởi một kết quả đầu vào cụ thể hoặc chỉ để kiểm tra rằng mã của bạn "ổn định" trong đó trả về cùng một kết quả mỗi lần nếu được gửi cùng một dữ liệu theo một thứ tự nhất định).

Thủ thuật này cũng có thể được sử dụng để có được kết quả tùy ý hơn từ các hàm, không cho phép các cuộc gọi không xác định như NEWID () trong cơ thể của chúng. Một lần nữa, đây không phải là thứ thường có ích trong thế giới thực nhưng có thể có ích nếu bạn muốn một hàm trả về một cái gì đó ngẫu nhiên và "Random-ish" là đủ tốt (nhưng hãy cẩn thận để nhớ các quy tắc xác định khi các hàm do người dùng xác định được tính toán, nghĩa là thường chỉ một lần trên mỗi hàng hoặc kết quả của bạn có thể không phải là những gì bạn mong đợi / yêu cầu).

Hiệu suất

Như EBarr chỉ ra, có thể có vấn đề về hiệu suất với bất kỳ vấn đề nào ở trên. Đối với nhiều hơn một vài hàng, bạn gần như chắc chắn sẽ thấy đầu ra được lưu vào tempdb trước khi số lượng hàng được yêu cầu được đọc lại theo đúng thứ tự, điều đó có nghĩa là ngay cả khi bạn đang tìm kiếm top 10, bạn vẫn có thể tìm thấy một chỉ mục đầy đủ quét (hoặc tệ hơn là quét bảng) xảy ra cùng với một khối lượng lớn ghi vào tempdb. Do đó, nó có thể cực kỳ quan trọng, như với hầu hết mọi thứ, để điểm chuẩn với dữ liệu thực tế trước khi sử dụng điều này trong sản xuất.


14

Đây là một câu hỏi cũ, nhưng theo tôi, một khía cạnh của cuộc thảo luận bị thiếu - HIỆU SUẤT. ORDER BY NewId()là câu trả lời chung chung. Khi ai đó thích thú, họ nói thêm rằng bạn thực sự nên NewID()tham gia CheckSum(), bạn biết đấy, về hiệu suất!

Vấn đề với phương pháp này là bạn vẫn được đảm bảo quét chỉ mục đầy đủ và sau đó là một loại dữ liệu hoàn chỉnh. Nếu bạn đã làm việc với bất kỳ khối lượng dữ liệu nghiêm trọng nào, điều này có thể nhanh chóng trở nên đắt đỏ. Nhìn vào kế hoạch thực hiện điển hình này và lưu ý cách sắp xếp chiếm 96% thời gian của bạn ...

nhập mô tả hình ảnh ở đây

Để cho bạn biết về quy mô này, tôi sẽ cung cấp cho bạn hai ví dụ từ cơ sở dữ liệu tôi làm việc cùng.

  • TableA - có 50.000 hàng trên 2500 trang dữ liệu. Truy vấn ngẫu nhiên tạo ra 145 lần đọc trong 42ms.
  • Bảng B - có 1,2 triệu hàng trên 114.000 trang dữ liệu. Chạy Order By newid()trên bảng này tạo ra 53.700 lượt đọc và mất 16 giây.

Đạo đức của câu chuyện là nếu bạn có các bảng lớn (nghĩ hàng tỷ hàng) hoặc cần chạy truy vấn này thường xuyên thì newid()phương thức bị hỏng. Vậy một cậu bé phải làm gì?

Gặp gỡ TABLESAMPLE ()

Trong SQL 2005, một khả năng mới được gọi là TABLESAMPLEđã được tạo. Tôi chỉ thấy một bài viết thảo luận về việc sử dụng nó ... nên có nhiều hơn nữa. Tài liệu MSDN tại đây . Ví dụ đầu tiên:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

Ý tưởng đằng sau mẫu bảng là cung cấp cho bạn khoảng kích thước tập hợp con bạn yêu cầu. Số SQL mỗi trang dữ liệu và chọn X phần trăm của các trang đó. Số lượng hàng thực tế bạn nhận được có thể thay đổi dựa trên những gì tồn tại trong các trang đã chọn.

Vậy làm thế nào để tôi sử dụng nó? Chọn kích thước tập hợp con nhiều hơn số lượng hàng bạn cần, sau đó thêm a Top(). Ý tưởng là bạn có thể làm cho bảng ginormous của bạn xuất hiện nhỏ hơn trước khi sắp xếp đắt tiền.

Cá nhân tôi đã sử dụng nó để có hiệu lực giới hạn kích thước của bảng của tôi. Vì vậy, trên bảng hàng triệu thực hiện top(20)...TABLESAMPLE(20 PERCENT)truy vấn giảm xuống 5600 lần đọc trong 1600ms. Ngoài ra còn có một REPEATABLE()tùy chọn trong đó bạn có thể vượt qua "Hạt giống" để chọn trang. Điều này sẽ dẫn đến một lựa chọn mẫu ổn định.

Dù sao, chỉ cần nghĩ rằng điều này nên được thêm vào các cuộc thảo luận. Hy vọng nó sẽ giúp được ai đó.


Thật tuyệt khi có thể viết một truy vấn theo thứ tự ngẫu nhiên có thể mở rộng, nó không chỉ mở rộng mà còn hoạt động với các tập dữ liệu nhỏ. Có vẻ như bạn phải chuyển đổi thủ công giữa việc có và không có TABLESAMPLE()dựa trên số lượng dữ liệu bạn có. Tôi không nghĩ rằng TABLESAMPLE(x ROWS)thậm chí sẽ đảm bảo rằng ít nhất x các hàng được trả về vì tài liệu nói rằng Số lượng hàng thực tế được trả về có thể thay đổi đáng kể. Nếu bạn chỉ định một số nhỏ, chẳng hạn như 5, bạn có thể không nhận được kết quả trong mẫu. Chỉ - vì vậy ROWScú pháp thực sự vẫn chỉ là một mặt nạ PERCENTbên trong?
biley

Chắc chắn, tự động ma thuật là tốt đẹp. Trong thực tế, tôi hiếm khi thấy tỷ lệ bảng 5 hàng đến hàng triệu hàng mà không cần thông báo trước. TABLESAMPLE () dường như lựa chọn cơ sở số lượng trang trong một bảng, do đó kích thước hàng đã cho ảnh hưởng đến những gì quay trở lại. Điểm của mẫu bảng, ít nhất là như tôi thấy, là cung cấp cho bạn một tập hợp con tốt mà bạn có thể chọn - giống như một bảng dẫn xuất.
EBarr

3

Nhiều bảng có cột ID số được lập chỉ mục tương đối dày đặc (vài giá trị bị thiếu).

Điều này cho phép chúng tôi xác định phạm vi của các giá trị hiện có và chọn các hàng sử dụng các giá trị ID được tạo ngẫu nhiên trong phạm vi đó. Điều này hoạt động tốt nhất khi số lượng hàng được trả về tương đối nhỏ và phạm vi giá trị ID được tập trung dày đặc (vì vậy cơ hội tạo ra một giá trị bị thiếu là đủ nhỏ).

Để minh họa, đoạn mã sau chọn 100 người dùng ngẫu nhiên riêng biệt từ bảng Stack Overflow của người dùng, có 8.123.937 hàng.

Bước đầu tiên là xác định phạm vi của các giá trị ID, một hoạt động hiệu quả do chỉ mục:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

Truy vấn phạm vi

Kế hoạch đọc một hàng từ mỗi đầu của chỉ mục.

Bây giờ chúng tôi tạo 100 ID ngẫu nhiên riêng biệt trong phạm vi (với các hàng khớp trong bảng người dùng) và trả về các hàng đó:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

truy vấn hàng ngẫu nhiên

Kế hoạch cho thấy rằng trong trường hợp này, cần có số ngẫu nhiên để tìm 100 hàng phù hợp. Nó khá nhanh:

Bảng 'Người dùng'. Quét số 1, đọc logic 1937, đọc vật lý 2, đọc trước đọc 408
Bảng 'Bàn làm việc'. Quét số 0, đọc logic 0, đọc vật lý 0, đọc trước 0
Bảng 'Workfile'. Quét số 0, đọc logic 0, đọc vật lý 0, đọc trước 0

 Thời gian thực thi máy chủ SQL:
   Thời gian CPU = 0 ms, thời gian trôi qua = 9 ms.

Hãy thử nó trên Stack Exchange Data Explorer.


0

Như tôi đã giải thích trong bài viết này , để xáo trộn tập kết quả SQL, bạn cần sử dụng lệnh gọi hàm cụ thể cho cơ sở dữ liệu.

Lưu ý rằng việc sắp xếp một tập kết quả lớn bằng hàm RANDOM có thể rất chậm, vì vậy hãy đảm bảo bạn làm điều đó trên các tập kết quả nhỏ.

Nếu bạn phải xáo trộn một tập kết quả lớn và giới hạn nó sau đó, thì tốt hơn là sử dụng SQL Server TABLESAMPLEtrong SQL Server thay vì một hàm ngẫu nhiên trong mệnh đề ORDER BY.

Vì vậy, giả sử chúng ta có bảng cơ sở dữ liệu sau:

nhập mô tả hình ảnh ở đây

Và các hàng sau trong songbảng:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

Trên SQL Server, bạn cần sử dụng NEWIDhàm, như được minh họa bằng ví dụ sau:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

Khi chạy truy vấn SQL đã nói ở trên trên SQL Server, chúng ta sẽ nhận được tập kết quả sau:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

Lưu ý rằng các bài hát đang được liệt kê theo thứ tự ngẫu nhiên, nhờ NEWIDlệnh gọi hàm được sử dụng bởi mệnh đề ORDER BY.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.