Các mẫu ngẫu nhiên đơn giản từ cơ sở dữ liệu Sql


93

Làm cách nào để lấy một mẫu ngẫu nhiên đơn giản hiệu quả trong SQL? Cơ sở dữ liệu được đề cập đang chạy MySQL; bảng của tôi có ít nhất 200.000 hàng và tôi muốn một mẫu ngẫu nhiên đơn giản khoảng 10.000.

Câu trả lời "hiển nhiên" là:

SELECT * FROM table ORDER BY RAND() LIMIT 10000

Đối với các bảng lớn, điều đó quá chậm: nó gọi RAND()mọi hàng (đã đặt nó ở vị trí O (n)) và sắp xếp chúng, làm cho nó tốt nhất là O (n lg n). Có cách nào để làm điều này nhanh hơn O (n) không?

Lưu ý : Như Andrew Mao đã chỉ ra trong phần nhận xét, Nếu bạn đang sử dụng phương pháp này trên SQL Server, bạn nên sử dụng hàm T-SQL NEWID(), vì RAND () có thể trả về cùng một giá trị cho tất cả các hàng .

CHỈNH SỬA: 5 NĂM SAU

Tôi lại gặp phải vấn đề này với một bảng lớn hơn và kết thúc bằng cách sử dụng phiên bản giải pháp của @ ignore, với hai tinh chỉnh:

  • Lấy mẫu các hàng lên gấp 2-5 lần kích thước mẫu mong muốn của tôi, với giá rẻ ORDER BY RAND()
  • Lưu kết quả RAND()vào một cột được lập chỉ mục trên mỗi lần chèn / cập nhật. (Nếu tập dữ liệu của bạn không quá cập nhật, bạn có thể cần phải tìm một cách khác để giữ cho cột này luôn mới.)

Để lấy mẫu bảng gồm 1000 mục, tôi đếm các hàng và lấy mẫu kết quả trung bình xuống, trung bình là 10.000 hàng với cột freeze_rand:

SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high

  SELECT *
    FROM table
   WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000

(Việc triển khai thực tế của tôi liên quan đến nhiều công việc hơn để đảm bảo tôi không lấy mẫu và quấn rand_high theo cách thủ công, nhưng ý tưởng cơ bản là "cắt ngẫu nhiên N của bạn xuống một vài nghìn".)

Mặc dù điều này gây ra một số hy sinh, nhưng nó cho phép tôi lấy mẫu cơ sở dữ liệu bằng cách sử dụng quét chỉ mục, cho đến khi nó đủ nhỏ để làm ORDER BY RAND()lại.


3
Điều đó thậm chí không hoạt động trong máy chủ SQL vì RAND()trả về cùng một giá trị cho mỗi lần gọi tiếp theo.
Andrew Mao

1
Điểm tốt - Tôi sẽ thêm một lưu ý rằng người dùng SQL Server nên sử dụng ORDER BY NEWID () để thay thế.
ojrac

Nó vẫn rất kém hiệu quả vì nó phải sắp xếp tất cả dữ liệu. Kỹ thuật lấy mẫu ngẫu nhiên cho một số tỷ lệ phần trăm là tốt hơn, nhưng tôi thậm chí sau khi đọc một loạt các bài đăng trên đây, tôi vẫn chưa tìm thấy giải pháp chấp nhận được là đủ ngẫu nhiên.
Andrew Mao

Nếu bạn đọc câu hỏi, tôi đang hỏi cụ thể vì ORDER BY RAND () là O (n lg n).
ojrac

Câu trả lời của muposat dưới đây là rất tốt nếu bạn không quá bị ám ảnh bởi tính ngẫu nhiên thống kê của RAND ().
Josh Greifer

Câu trả lời:


25

Có một cuộc thảo luận rất thú vị về loại vấn đề này ở đây: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

Tôi nghĩ rằng hoàn toàn không có giả định nào về bảng rằng giải pháp O (n lg n) của bạn là tốt nhất. Mặc dù thực sự với một trình tối ưu hóa tốt hoặc một kỹ thuật hơi khác, truy vấn bạn liệt kê có thể tốt hơn một chút, O (m * n) trong đó m là số hàng ngẫu nhiên mong muốn, vì nó sẽ không bắt buộc phải sắp xếp toàn bộ mảng lớn , nó chỉ có thể tìm kiếm m lần nhỏ nhất. Nhưng đối với loại số bạn đã đăng, dù sao thì m cũng lớn hơn lg n.

Ba giả định chúng tôi có thể thử:

  1. có một khóa chính, được lập chỉ mục, duy nhất trong bảng

  2. số hàng ngẫu nhiên bạn muốn chọn (m) nhỏ hơn nhiều so với số hàng trong bảng (n)

  3. khóa chính duy nhất là một số nguyên nằm trong khoảng từ 1 đến n không có khoảng trống

Chỉ với giả định 1 và 2, tôi nghĩ điều này có thể được thực hiện trong O (n), mặc dù bạn sẽ cần phải viết một chỉ mục toàn bộ vào bảng để khớp với giả định 3, vì vậy nó không nhất thiết phải là O (n) nhanh. Nếu chúng ta có thể BỔ SUNG giả sử một cái gì đó khác tốt đẹp về bảng, chúng ta có thể thực hiện nhiệm vụ trong O (m log m). Giả định 3 sẽ là một thuộc tính bổ sung dễ làm việc. Với một trình tạo số ngẫu nhiên đẹp mắt đảm bảo không có trùng lặp khi tạo m số liên tiếp, một giải pháp O (m) sẽ khả thi.

Với ba giả thiết, ý tưởng cơ bản là tạo ra m số ngẫu nhiên duy nhất từ ​​1 đến n, rồi chọn các hàng có các khóa đó từ bảng. Tôi không có mysql hoặc bất kỳ thứ gì trước mặt tôi ngay bây giờ, vì vậy trong mã giả hơi, điều này sẽ giống như sau:


create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)

-- generate m random keys between 1 and n
for i = 1 to m
  insert RandomKeysAttempt select rand()*n + 1

-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt

-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) < m
  NextAttempt = rand()*n + 1
  if not exists (select * from RandomKeys where RandomKey = NextAttempt)
    insert RandomKeys select NextAttempt

-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey

Nếu bạn thực sự lo lắng về tính hiệu quả, bạn có thể cân nhắc thực hiện việc tạo khóa ngẫu nhiên bằng một số loại ngôn ngữ thủ tục và chèn kết quả vào cơ sở dữ liệu, vì hầu hết mọi thứ khác ngoài SQL có thể sẽ tốt hơn ở loại lặp và tạo số ngẫu nhiên được yêu cầu .


Tôi khuyên bạn nên thêm một chỉ mục duy nhất trên lựa chọn khóa ngẫu nhiên và có thể bỏ qua các bản sao trên phần chèn, sau đó bạn có thể loại bỏ những thứ khác biệt và việc nối sẽ nhanh hơn.
Sam Saffron

Tôi nghĩ rằng thuật toán số ngẫu nhiên có thể sử dụng một số chỉnh sửa - hoặc là một ràng buộc DUY NHẤT như đã đề cập hoặc chỉ tạo 2 * m số và CHỌN SỐ ĐƠN HÀNG, ĐẶT HÀNG THEO id (đến trước-phục vụ trước, vì vậy điều này giảm xuống ràng buộc DUY NHẤT ) GIỚI HẠN m. Tôi thích nó.
ojrac 31/10/08

Khi thêm một chỉ mục duy nhất vào lựa chọn khóa ngẫu nhiên và sau đó bỏ qua các bản sao trên chèn, tôi nghĩ điều này có thể đưa bạn trở lại hành vi O (m ^ 2) thay vì O (m lg m) cho một sắp xếp. Không chắc máy chủ đang duy trì chỉ mục hiệu quả như thế nào khi chèn từng hàng ngẫu nhiên một.
user12861

Đối với các đề xuất để tạo 2 * m số hoặc thứ gì đó, tôi muốn một thuật toán được đảm bảo hoạt động bất kể điều gì. Luôn có cơ hội (mỏng) là các số ngẫu nhiên 2 * m của bạn sẽ có nhiều hơn m trùng lặp, vì vậy bạn sẽ không có đủ cho truy vấn của mình.
user12861

1
Làm thế nào để bạn có được số hàng trong bảng?
Tuyệt vời-o

54

Tôi nghĩ giải pháp nhanh nhất là

select * from table where rand() <= .3

Đây là lý do tại sao tôi nghĩ điều này nên làm công việc.

  • Nó sẽ tạo một số ngẫu nhiên cho mỗi hàng. Số nằm trong khoảng từ 0 đến 1
  • Nó đánh giá xem có hiển thị hàng đó hay không nếu số được tạo nằm trong khoảng từ 0 đến .3 (30%).

Điều này giả định rằng rand () đang tạo ra các số trong một phân phối đồng nhất. Đó là cách nhanh nhất để làm điều này.

Tôi thấy rằng ai đó đã đề xuất giải pháp đó và họ bị bắn hạ mà không có bằng chứng .. đây là những gì tôi sẽ nói với điều đó -

  • Đây là O (n) nhưng không cần sắp xếp nên nó nhanh hơn O (n lg n)
  • mysql rất có khả năng tạo ra các số ngẫu nhiên cho mỗi hàng. Thử đi -

    chọn rand () từ giới hạn 10 của INFORMATION_SCHEMA.TABLES;

Vì cơ sở dữ liệu được đề cập là mySQL, đây là giải pháp phù hợp.


1
Đầu tiên, bạn có vấn đề là điều này không thực sự trả lời câu hỏi, vì nó nhận được một số kết quả bán ngẫu nhiên được trả về, gần với một số mong muốn nhưng không nhất thiết phải chính xác là số đó, thay vì một số kết quả mong muốn chính xác.
user12861

1
Tiếp theo, về hiệu quả, của bạn là O (n), trong đó n là số hàng trong bảng. Điều đó gần như không tốt bằng O (m log m), với m là số kết quả bạn muốn, và m << n. Bạn vẫn có thể đúng rằng nó sẽ nhanh hơn trong thực tế, bởi vì như bạn nói việc tạo ra các hàm rand () và so sánh chúng với một hằng số CO SẼ rất nhanh. Bạn phải kiểm tra nó để tìm ra. Với các bàn nhỏ hơn, bạn có thể thắng. Với các bảng khổng lồ và số lượng kết quả mong muốn ít hơn nhiều, tôi nghi ngờ điều đó.
user12861

1
Mặc dù @ user12861 nói đúng về việc không nhận được con số chính xác phù hợp, nhưng đó là một cách tốt để cắt tập dữ liệu xuống đúng kích thước thô.
ojrac

1
Cơ sở dữ liệu phục vụ truy vấn sau như SELECT * FROM table ORDER BY RAND() LIMIT 10000 thế nào - ? Đầu tiên nó phải tạo một số ngẫu nhiên cho mỗi hàng (giống như giải pháp tôi đã mô tả), sau đó đặt hàng nó .. các loại đắt tiền! Đây là lý do tại sao giải pháp này SẼ chậm hơn giải pháp mà tôi đã mô tả, vì không cần sắp xếp. Bạn có thể thêm giới hạn cho giải pháp mà tôi đã mô tả và nó sẽ không cung cấp cho bạn nhiều hơn số hàng đó. Như ai đó đã chỉ ra một cách chính xác, nó sẽ không cung cấp cho bạn kích thước mẫu CHÍNH XÁC, nhưng với các mẫu ngẫu nhiên, CHÍNH XÁC thường không phải là một yêu cầu nghiêm ngặt.
không biết gì

Có cách nào để chỉ định số hàng tối thiểu không?
CMCDragonkai

5

Rõ ràng trong một số phiên bản SQL có một TABLESAMPLElệnh, nhưng nó không có trong tất cả các triển khai SQL (đặc biệt là Redshift).

http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx


Rất tuyệt! Có vẻ như nó cũng không được PostgreSQL hoặc MySQL / MariaDB triển khai, nhưng đó là một câu trả lời tuyệt vời nếu bạn đang sử dụng triển khai SQL hỗ trợ nó.
ojrac

Tôi hiểu rằng đó TABLESAMPLEkhông phải là ngẫu nhiên theo nghĩa thống kê.
Sean

4

Chỉ dùng

WHERE RAND() < 0.1 

để có được 10% hồ sơ hoặc

WHERE RAND() < 0.01 

để lấy 1% bản ghi, v.v.


1
Điều đó sẽ gọi RAND cho mọi hàng, biến nó thành O (n). Người đăng tìm kiếm thứ gì đó tốt hơn thế.
user12861

1
Không chỉ vậy, mà RAND()trả về cùng một giá trị cho các lần gọi tiếp theo (ít nhất là trên MSSQL), nghĩa là bạn sẽ nhận được toàn bộ bảng hoặc không có bảng nào với xác suất đó.
Andrew Mao

4

Nhanh hơn ORDER BY RAND ()

Tôi đã thử nghiệm phương pháp này nhanh hơn nhiều ORDER BY RAND(), do đó nó chạy trong thời gian O (n) , và nhanh một cách ấn tượng.

Từ http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx :

Phiên bản không phải MSSQL - Tôi đã không kiểm tra điều này

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()

Phiên bản MSSQL:

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

Điều này sẽ chọn ~ 1% bản ghi. Vì vậy, nếu bạn cần chọn chính xác # phần trăm hoặc bản ghi, hãy ước tính tỷ lệ phần trăm của bạn với một số biên độ an toàn, sau đó lấy ngẫu nhiên các bản ghi thừa khỏi tập hợp kết quả, sử dụng ORDER BY RAND()phương pháp đắt tiền hơn .

Thậm chí nhanh hơn

Tôi đã có thể cải thiện phương pháp này hơn nữa vì tôi có một phạm vi giá trị cột được lập chỉ mục nổi tiếng.

Ví dụ: nếu bạn có một cột được lập chỉ mục với các số nguyên được phân phối đồng đều [0..max], bạn có thể sử dụng cột đó để chọn ngẫu nhiên N khoảng thời gian nhỏ. Thực hiện động điều này trong chương trình của bạn để có một tập hợp khác nhau cho mỗi lần chạy truy vấn. Lựa chọn tập hợp con này sẽ là O (N) , có thể nhiều bậc có độ lớn nhỏ hơn tập dữ liệu đầy đủ của bạn.

Trong thử nghiệm của mình, tôi đã giảm thời gian cần thiết để có được 20 (trong số 20 triệu) bản ghi mẫu từ 3 phút sử dụng ORDER BY RAND () xuống còn 0,0 giây !


1

Tôi muốn chỉ ra rằng tất cả các giải pháp này dường như là mẫu mà không cần thay thế. Chọn K hàng trên cùng từ một sắp xếp ngẫu nhiên hoặc tham gia vào một bảng có chứa các khóa duy nhất theo thứ tự ngẫu nhiên sẽ mang lại một mẫu ngẫu nhiên được tạo mà không cần thay thế.

Nếu bạn muốn mẫu của mình độc lập, bạn sẽ cần lấy mẫu thay thế. Hãy xem Câu hỏi 25451034 để biết một ví dụ về cách thực hiện việc này bằng cách sử dụng JOIN theo cách tương tự như giải pháp của user12861. Giải pháp được viết cho T-SQL, nhưng khái niệm này hoạt động trong bất kỳ db SQL nào.


0

Bắt đầu với quan sát rằng chúng ta có thể truy xuất id của một bảng (ví dụ: đếm 5) dựa trên một tập hợp:

select *
from table_name
where _id in (4, 1, 2, 5, 3)

chúng ta có thể đi đến kết quả rằng nếu chúng ta có thể tạo chuỗi "(4, 1, 2, 5, 3)", thì chúng ta sẽ có một cách hiệu quả hơn RAND().

Ví dụ, trong Java:

ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
    indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');

Nếu id có khoảng trống, thì danh sách mảng ban đầu indiceslà kết quả của truy vấn sql trên id.


0

Nếu bạn cần chính xác mcác hàng, trên thực tế, bạn sẽ tạo tập hợp con ID của mình bên ngoài SQL. Hầu hết các phương thức yêu cầu tại một số thời điểm để chọn mục nhập "thứ n" và bảng SQL thực sự không phải là mảng. Việc giả định rằng các khóa liên tiếp nhau để chỉ nối các số nguyên ngẫu nhiên giữa 1 và số đếm cũng khó đáp ứng - ví dụ như MySQL không hỗ trợ nó nguyên bản, và các điều kiện khóa thì ... phức tạp .

Đây là giải pháp -time O(max(n, m lg n)), O(n)-space giả sử chỉ là các phím BTREE đơn giản:

  1. Tìm nạp tất cả các giá trị của cột chính của bảng dữ liệu theo thứ tự bất kỳ vào một mảng bằng ngôn ngữ kịch bản yêu thích của bạn trong O(n)
  2. Thực hiện xáo trộn Fisher-Yates , dừng lại sau khi mhoán đổi và giải nén mảng con [0:m-1]trongϴ(m)
  3. "Tham gia" mảng con với tập dữ liệu gốc (ví dụ SELECT ... WHERE id IN (<subarray>)) trongO(m lg n)

Bất kỳ phương thức nào tạo ra tập con ngẫu nhiên bên ngoài SQL ít nhất phải có độ phức tạp này. Quá trình kết hợp không thể nhanh hơn bất kỳ lúc nào so O(m lg n)với BTREE (vì vậy các O(m)tuyên bố là tưởng tượng đối với hầu hết các công cụ) và xáo trộn được giới hạn bên dưới nm lg nkhông ảnh hưởng đến hành vi tiệm cận.

Trong mã giả Pythonic:

ids = sql.query('SELECT id FROM t')
for i in range(m):
  r = int(random() * (len(ids) - i))
  ids[i], ids[i + r] = ids[i + r], ids[i]

results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])

0

Chọn 3000 bản ghi ngẫu nhiên trong Netezza:

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000

Ngoài việc thêm một số ghi chú dành riêng cho phương ngữ SQL, tôi không nghĩ rằng điều này trả lời câu hỏi làm thế nào để truy vấn một mẫu hàng ngẫu nhiên mà không có 'ORDER BY rand () LIMIT $ 1'.
ojrac ngày

0

Thử

SELECT TOP 10000 * FROM table ORDER BY NEWID()

Điều này có mang lại kết quả mong muốn mà không quá phức tạp không?


Lưu ý rằng NEWID()nó dành riêng cho T-SQL.
Peter O.

Lời xin lỗi của tôi. Nó là. Cảm ơn Tuy nhiên, rất hữu ích nếu biết có ai đến đây tìm kiếm như tôi đã làm trên một cách tốt hơn và đang sử dụng T-SQL
Northernlad

ORDER BY NEWID()có chức năng giống như ORDER BY RAND()- nó gọi RAND()cho mọi hàng trong tập hợp - O (n) - và sau đó sắp xếp toàn bộ điều - O (n lg n). Nói cách khác, đó là giải pháp tình huống xấu nhất mà câu hỏi này đang tìm cách cải thiện.
ojrac

0

Trong một số phương ngữ nhất định như Microsoft SQL Server, PostgreSQL và Oracle (nhưng không phải MySQL hoặc SQLite), bạn có thể làm điều gì đó như

select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);

Lý do của việc không chỉ làm (10000 rows)mà không có topTABLESAMPLElogic cung cấp cho bạn số hàng cực kỳ không chính xác (như đôi khi là 75%, đôi khi gấp 1,25%), vì vậy bạn muốn lấy mẫu quá mức và chọn số chính xác bạn muốn. Là REPEATABLE (123)để cung cấp một hạt giống ngẫu nhiên.


-4

Có lẽ bạn có thể làm

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)

1
Có vẻ như điều đó sẽ chọn một phần dữ liệu ngẫu nhiên của tôi; Tôi đang tìm kiếm thứ gì đó phức tạp hơn một chút - 10.000 hàng được phân phối ngẫu nhiên.
ojrac 30/10/08

Sau đó, tùy chọn duy nhất của bạn, nếu bạn muốn làm điều đó trong cơ sở dữ liệu, là ĐẶT HÀNG BẰNG rand ().
staticsan
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.