Chọn n hàng ngẫu nhiên từ bảng SQL Server


309

Tôi đã có một bảng SQL Server với khoảng 50.000 hàng trong đó. Tôi muốn chọn khoảng 5.000 hàng trong số đó một cách ngẫu nhiên. Tôi đã nghĩ ra một cách phức tạp, tạo một bảng tạm thời với cột "số ngẫu nhiên", sao chép bảng của tôi vào đó, lặp qua bảng tạm thời và cập nhật từng hàng với RAND(), sau đó chọn từ bảng đó trong đó cột số ngẫu nhiên < 0,1. Tôi đang tìm kiếm một cách đơn giản hơn để làm điều đó, trong một tuyên bố duy nhất nếu có thể.

Bài viết này đề nghị sử dụng NEWID()chức năng. Điều đó có vẻ đầy hứa hẹn, nhưng tôi không thể thấy làm thế nào tôi có thể chọn một tỷ lệ hàng nhất định.

Bất cứ ai từng làm điều này trước đây? Có ý kiến ​​gì không?


3
MSDN có một bài viết hay đề cập đến rất nhiều vấn đề sau: Chọn hàng ngẫu nhiên từ một Bảng lớn
KyleMit

Câu trả lời:


387
select top 10 percent * from [yourtable] order by newid()

Đáp lại bình luận "thùng rác tinh khiết" liên quan đến các bảng lớn: bạn có thể làm như thế này để cải thiện hiệu suất.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

Chi phí của việc này sẽ là quét chính các giá trị cộng với chi phí tham gia, mà trên một bảng lớn với một lựa chọn tỷ lệ nhỏ là hợp lý.


1
Tôi thích cách tiếp cận này tốt hơn nhiều sau đó sử dụng bài viết mà ông tham khảo.
JoshBerke

14
Luôn luôn tốt để nhớ rằng newid () không phải là trình tạo số giả ngẫu nhiên thực sự tốt, ít nhất là không tốt như rand (). Nhưng nếu bạn chỉ cần một số mẫu ngẫu nhiên mơ hồ và không quan tâm đến các phẩm chất toán học và như vậy, nó sẽ đủ tốt. Nếu không, bạn cần: stackoverflow.com/questions/249602/ từ
user12861

1
Ừm, xin lỗi nếu điều này là hiển nhiên .. nhưng những gì [yourPk]đề cập đến? EDIT: Nvm, đã tìm ra ... Khóa chính. Durrr
Snailer

4
newid - guide bị từ chối là duy nhất nhưng không ngẫu nhiên .. cách tiếp cận không chính xác
Brans Ds

2
với số lượng lớn các hàng, ví dụ trên 1 triệu newid()Sắp xếp Ước tính chi phí I / O sẽ rất cao và sẽ ảnh hưởng đến hiệu suất.
aadi1295

81

Tùy thuộc vào nhu cầu của bạn, TABLESAMPLEsẽ giúp bạn có được hiệu suất gần như ngẫu nhiên và tốt hơn. cái này có sẵn trên máy chủ MS SQL 2005 trở lên.

TABLESAMPLE sẽ trả về dữ liệu từ các trang ngẫu nhiên thay vì các hàng ngẫu nhiên và do đó, thậm chí không lấy lại dữ liệu mà nó sẽ không trả về.

Trên một cái bàn rất lớn tôi đã thử

select top 1 percent * from [tablename] order by newid()

mất hơn 20 phút.

select * from [tablename] tablesample(1 percent)

mất 2 phút.

Hiệu suất cũng sẽ cải thiện trên các mẫu nhỏ hơn trong TABLESAMPLEkhi nó sẽ không với newid().

Xin lưu ý rằng điều này không ngẫu nhiên như newid()phương pháp nhưng sẽ cung cấp cho bạn một mẫu tốt.

Xem trang MSDN .


7
Như Rob Boek đã chỉ ra dưới đây, lấy mẫu kết quả theo nhóm, và do đó không phải là cách tốt để có được một số lượng nhỏ kết quả ngẫu nhiên
Oskar Austegard

Bạn nhớ câu hỏi làm thế nào điều này hoạt động: chọn top 1 phần trăm * từ thứ tự [tablename] theo newid () vì newid () không phải là một cột trong [tablename]. Là máy chủ sql nối thêm cột nội bộ newid () trên mỗi hàng và sau đó thực hiện sắp xếp?
FrenkyB

Mẫu bảng là câu trả lời tốt nhất cho tôi khi tôi đang thực hiện một truy vấn phức tạp trên một bảng rất lớn. Không có câu hỏi rằng nó đã được nhanh chóng đáng kể. Tôi đã nhận được một biến thể trong các bản ghi số được trả về khi tôi chạy nó nhiều lần nhưng tất cả chúng đều nằm trong phạm vi sai số chấp nhận được.
jessier3

38

newid () / order by sẽ hoạt động, nhưng sẽ rất tốn kém cho các tập kết quả lớn vì nó phải tạo id cho mỗi hàng, sau đó sắp xếp chúng.

TABLESAMPLE () tốt từ quan điểm hiệu suất, nhưng bạn sẽ nhận được kết quả (tất cả các hàng trên một trang sẽ được trả về).

Để có một mẫu ngẫu nhiên thực sự tốt hơn, cách tốt nhất là lọc ra các hàng ngẫu nhiên. Tôi đã tìm thấy mẫu mã sau trong Bài viết trực tuyến Giới hạn bộ bài viết của SQL Server bằng cách sử dụng TABLESAMPLE :

Nếu bạn thực sự muốn một mẫu ngẫu nhiên của các hàng riêng lẻ, hãy sửa đổi truy vấn của bạn để lọc ra các hàng ngẫu nhiên, thay vì sử dụng TABLESAMPLE. Ví dụ: truy vấn sau sử dụng hàm NEWID để trả về khoảng một phần trăm các hàng của bảng Sales.SalesOrderDetail:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

Cột SalesOrderID được bao gồm trong biểu thức CHECKSUM để NEWID () đánh giá một lần trên mỗi hàng để đạt được lấy mẫu trên cơ sở mỗi hàng. Biểu thức CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) ước tính giá trị float ngẫu nhiên trong khoảng từ 0 đến 1.

Khi chạy với bảng có 1.000.000 hàng, đây là kết quả của tôi:

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Nếu bạn có thể thoát khỏi việc sử dụng TABLESAMPLE, nó sẽ mang lại cho bạn hiệu suất tốt nhất. Nếu không, sử dụng phương thức newid () / filter. newid () / order by nên là giải pháp cuối cùng nếu bạn có tập kết quả lớn.


Tôi cũng đã xem bài viết đó và thử nó trên mã của mình, dường như chỉ NewID()được đánh giá một lần, thay vì mỗi hàng, điều mà tôi không thích ...
Andrew Mao

23

Chọn hàng ngẫu nhiên từ một bảng lớn trên MSDN có một giải pháp đơn giản, được khớp nối rõ ràng để giải quyết các mối quan tâm về hiệu suất quy mô lớn.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Rất thú vị. Sau khi đọc bài viết, tôi thực sự không hiểu tại sao RAND()không trả về cùng một giá trị cho mỗi hàng (điều này sẽ đánh bại BINARY_CHECKSUM()logic). Có phải vì nó được gọi bên trong một hàm khác chứ không phải là một phần của mệnh đề SELECT?
John M Gant

Truy vấn này chạy trên một bảng có hàng 6MM trong chưa đầy một giây.
Mark Melville

2
Tôi đã chạy truy vấn này trên một bảng có 35 mục và liên tục có hai trong số chúng trong tập kết quả rất thường xuyên. Đây có thể là một vấn đề với rand()hoặc sự kết hợp của những điều trên - nhưng tôi đã từ chối giải pháp này vì lý do đó. Ngoài ra số lượng kết quả thay đổi từ 1 đến 5 vì vậy điều này cũng có thể không được chấp nhận trong một số trường hợp.
Oliver

Không RAND () trả về cùng một giá trị cho mỗi hàng?
Sarsaparilla

RAND()trả về cùng một giá trị cho mỗi hàng (đó là lý do tại sao giải pháp này nhanh). Tuy nhiên, các hàng có tổng kiểm tra nhị phân rất gần nhau có nguy cơ tạo ra kết quả tổng kiểm tra tương tự, gây ra vón cục khi RAND()nhỏ. Ví dụ: (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Nếu dữ liệu của bạn gặp phải vấn đề này, hãy nhân BINARY_CHECKSUMvới 9923.
Brian

12

Liên kết này có một so sánh thú vị giữa Orderby (NEWID ()) và các phương thức khác cho các bảng có 1, 7 và 13 triệu hàng.

Thông thường, khi các câu hỏi về cách chọn các hàng ngẫu nhiên được hỏi trong các nhóm thảo luận, truy vấn NEWID được đề xuất; nó đơn giản và hoạt động rất tốt cho các bảng nhỏ.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Tuy nhiên, truy vấn NEWID có một nhược điểm lớn khi bạn sử dụng nó cho các bảng lớn. Mệnh đề ORDER BY làm cho tất cả các hàng trong bảng được sao chép vào cơ sở dữ liệu tempdb, nơi chúng được sắp xếp. Điều này gây ra hai vấn đề:

  1. Các hoạt động phân loại thường có chi phí cao liên quan đến nó. Sắp xếp có thể sử dụng rất nhiều đĩa I / O và có thể chạy trong một thời gian dài.
  2. Trong trường hợp xấu nhất, tempdb có thể hết dung lượng. Trong trường hợp tốt nhất, tempdb có thể chiếm một lượng lớn dung lượng đĩa sẽ không bao giờ được thu hồi nếu không có lệnh thu nhỏ thủ công.

Những gì bạn cần là một cách để chọn các hàng ngẫu nhiên sẽ không sử dụng tempdb và sẽ không bị chậm hơn nhiều khi bảng trở nên lớn hơn. Đây là một ý tưởng mới về cách làm điều đó:

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

Ý tưởng cơ bản đằng sau truy vấn này là chúng tôi muốn tạo một số ngẫu nhiên trong khoảng từ 0 đến 99 cho mỗi hàng trong bảng và sau đó chọn tất cả các hàng có số ngẫu nhiên nhỏ hơn giá trị của phần trăm được chỉ định. Trong ví dụ này, chúng tôi muốn khoảng 10 phần trăm các hàng được chọn ngẫu nhiên; do đó, chúng tôi chọn tất cả các hàng có số ngẫu nhiên nhỏ hơn 10.

Vui lòng đọc toàn bộ bài viết trong MSDN .


2
Xin chào Deumber, tìm thấy tốt, bạn có thể xác nhận nó vì các câu trả lời chỉ có khả năng bị xóa.
bummi

1
@bummi Tôi đã thay đổi nó để tránh bị liên kết chỉ trả lời :)
QMaster

Đây là câu trả lời tốt nhất. 'ĐẶT HÀNG THEO NEWID ()' hoạt động trong hầu hết các trường hợp (các bảng nhỏ hơn), nhưng khi các điểm chuẩn trong liên kết được điều chỉnh rõ ràng cho thấy nó bị tụt lại phía sau khi bảng phát triển
pedram bashiri

10

Nếu bạn (không giống như OP) cần một số lượng hồ sơ cụ thể (điều này làm cho cách tiếp cận CHECKSUM trở nên khó khăn) và mong muốn một mẫu ngẫu nhiên hơn TABLESAMPLE tự cung cấp và cũng muốn tốc độ tốt hơn CHECKSUM, bạn có thể thực hiện với việc sáp nhập Các phương thức TABLESAMPLE và NEWID (), như thế này:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

Trong trường hợp của tôi, đây là sự thỏa hiệp đơn giản nhất giữa tính ngẫu nhiên (thực sự không phải vậy, tôi biết) và tốc độ. Thay đổi tỷ lệ phần trăm TABLESAMPLE (hoặc hàng) phù hợp - tỷ lệ phần trăm càng cao, mẫu càng ngẫu nhiên, nhưng mong đợi tốc độ giảm tuyến tính. (Lưu ý rằng TABLESAMPLE sẽ không chấp nhận một biến)


9

Chỉ cần đặt hàng bảng theo một số ngẫu nhiên và thu được 5.000 hàng đầu tiên bằng cách sử dụng TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

CẬP NHẬT

Chỉ cần thử nó và một newid()cuộc gọi là đủ - không cần tất cả các diễn viên và tất cả các toán học.


10
Lý do 'tất cả các diễn viên và tất cả các phép toán' được sử dụng là để có hiệu suất tốt hơn.
hkf

6

Đây là sự kết hợp giữa ý tưởng hạt giống ban đầu và tổng kiểm tra, theo tôi sẽ cho kết quả ngẫu nhiên đúng mà không phải trả chi phí cho NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

Trong MySQL bạn có thể làm điều này:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
Điều này sẽ không hoạt động. Vì câu lệnh select là nguyên tử, nó chỉ lấy một số ngẫu nhiên và nhân đôi nó cho mỗi hàng. Bạn sẽ phải đặt lại nó trên mỗi hàng để buộc nó thay đổi.
Tom H

4
Mmm ... yêu nhà cung cấp khác biệt. Chọn là nguyên tử trên MySQL, nhưng tôi cho rằng theo một cách khác. Điều này sẽ hoạt động trong MySQL.
Jeff Ferland

2

Chưa thấy biến thể này trong các câu trả lời. Tôi đã có một ràng buộc bổ sung khi tôi cần, được cung cấp một hạt giống ban đầu, để chọn cùng một bộ hàng mỗi lần.

Đối với MS SQL:

Ví dụ tối thiểu:

select top 10 percent *
from table_name
order by rand(checksum(*))

Thời gian thực hiện chuẩn hóa: 1,00

Ví dụ về NewId ():

select top 10 percent *
from table_name
order by newid()

Thời gian thực hiện chuẩn hóa: 1,02

NewId()chậm hơn đáng kể so với rand(checksum(*)), vì vậy bạn có thể không muốn sử dụng nó đối với các bộ hồ sơ lớn.

Lựa chọn với Seed ban đầu:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % @seed) /* any other math function here */

Nếu bạn cần chọn cùng một bộ đã cho một hạt giống, điều này dường như hoạt động.


Có bất kỳ lợi thế nào khi sử dụng @seed đặc biệt chống lại RAND () không?
QMaster

hoàn toàn, Bạn đã sử dụng tham số seed và điền nó theo tham số ngày, hàm RAND () làm tương tự ngoại trừ sử dụng giá trị thời gian hoàn chỉnh, tôi muốn biết có bất kỳ lợi thế nào để sử dụng tham số được tạo tiện dụng như seed trên RAND () hay không?
QMaster

Ah!. OK, đây là một yêu cầu của dự án. Tôi cần tạo một danh sách các hàng n-ngẫu nhiên theo cách xác định. Về cơ bản lãnh đạo muốn biết những hàng "ngẫu nhiên" nào chúng tôi sẽ chọn vài ngày trước khi các hàng được chọn và xử lý. Bằng cách xây dựng giá trị hạt giống dựa trên năm / tháng, tôi có thể đảm bảo bất kỳ cuộc gọi nào đến truy vấn năm đó sẽ trả về cùng một danh sách "ngẫu nhiên". Tôi biết, điều đó thật kỳ lạ và có lẽ có nhiều cách tốt hơn nhưng nó đã hoạt động ...
klyd

HAHA :) Tôi hiểu, nhưng tôi nghĩ ý nghĩa chung của các bản ghi được chọn ngẫu nhiên không giống với các bản ghi trên các truy vấn đang chạy khác nhau.
QMaster


0

Nó xuất hiện newid () không thể được sử dụng trong mệnh đề where, vì vậy giải pháp này yêu cầu một truy vấn bên trong:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%

0

Tôi đã sử dụng nó trong truy vấn con và nó trả về cho tôi cùng một hàng trong truy vấn con

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

sau đó tôi đã giải quyết với việc bao gồm cả biến bảng cha ở đâu

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Lưu ý nơi kết án


0

Ngôn ngữ xử lý phía máy chủ đang sử dụng (ví dụ: PHP, .net, v.v.) không được chỉ định, nhưng nếu là PHP, hãy lấy số bắt buộc (hoặc tất cả các bản ghi) và thay vì ngẫu nhiên trong truy vấn sử dụng hàm xáo trộn của PHP. Tôi không biết .net có chức năng tương đương không nhưng nếu có thì hãy sử dụng chức năng đó nếu bạn đang sử dụng .net

ĐẶT HÀNG B RNG RAND () có thể có một hình phạt hiệu suất khá cao, tùy thuộc vào số lượng hồ sơ có liên quan.


Tôi không nhớ chính xác những gì tôi đã sử dụng vào lúc này, nhưng tôi có thể đang làm việc trong C #, có thể trên máy chủ hoặc có thể trong ứng dụng khách, không chắc chắn. C # không có bất cứ thứ gì có thể so sánh trực tiếp với afaik của PHP, nhưng có thể được thực hiện bằng cách áp dụng các hàm từ đối tượng Ngẫu nhiên trong một thao tác Chọn, sắp xếp kết quả và sau đó lấy mười phần trăm hàng đầu. Nhưng chúng ta phải đọc toàn bộ bảng từ đĩa trên máy chủ DB và truyền nó qua mạng, chỉ để loại bỏ 90% dữ liệu đó. Xử lý nó trực tiếp trong DB gần như chắc chắn hiệu quả hơn.
John M Gant

-2

Điều này làm việc cho tôi:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

9
@ user537824, bạn đã thử điều đó trên SQL Server chưa? RANDOM không phải là một chức năng và GIỚI HẠN không phải là một từ khóa. Cú pháp SQL Server cho những gì bạn đang làm select top 10 percent from table_name order by rand(), nhưng điều đó cũng không hoạt động vì rand () trả về cùng một giá trị trên tất cả các hàng.
John M Gant
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.