Làm cách nào tôi có thể yêu cầu một hàng ngẫu nhiên (hoặc càng gần với ngẫu nhiên thực sự càng tốt) trong SQL thuần túy?
Làm cách nào tôi có thể yêu cầu một hàng ngẫu nhiên (hoặc càng gần với ngẫu nhiên thực sự càng tốt) trong SQL thuần túy?
Câu trả lời:
Xem bài đăng này: SQL để chọn một hàng ngẫu nhiên từ bảng cơ sở dữ liệu . Nó đi qua các phương thức để thực hiện điều này trong MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 và Oracle (sau đây được sao chép từ liên kết đó):
Chọn một hàng ngẫu nhiên với MySQL:
SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Chọn một hàng ngẫu nhiên với PostgreSQL:
SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Chọn một hàng ngẫu nhiên với Microsoft SQL Server:
SELECT TOP 1 column FROM table
ORDER BY NEWID()
Chọn một hàng ngẫu nhiên với IBM DB2
SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Chọn một bản ghi ngẫu nhiên với Oracle:
SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
order by rand()
hoặc tương đương trong tất cả các dbs: |. cũng được đề cập ở đây .
ORDER BY RAND()
là sai ...
O(n)
có n
số lượng hồ sơ trong bảng. Hãy tưởng tượng bạn có 1 triệu hồ sơ, bạn có thực sự muốn tạo 1 triệu số ngẫu nhiên hoặc id duy nhất không? Tôi muốn sử dụng COUNT()
và liên quan đến điều đó trong một LIMIT
biểu thức mới với một số ngẫu nhiên duy nhất.
Các giải pháp như Kẻ thù:
SELECT * FROM table ORDER BY RAND() LIMIT 1
hoạt động, nhưng họ cần quét tuần tự tất cả các bảng (vì giá trị ngẫu nhiên được liên kết với mỗi hàng cần được tính toán - sao cho có thể xác định giá trị nhỏ nhất), có thể khá chậm đối với các bảng có kích thước trung bình. Đề xuất của tôi sẽ là sử dụng một số loại cột số được lập chỉ mục (nhiều bảng có các khóa này làm khóa chính) và sau đó viết một cái gì đó như:
SELECT * FROM table WHERE num_value >= RAND() *
( SELECT MAX (num_value ) FROM table )
ORDER BY num_value LIMIT 1
Điều này hoạt động trong thời gian logarit, bất kể kích thước bảng, nếu num_value
được lập chỉ mục. Một cảnh báo: điều này giả định num_value
được phân bổ đều trong phạm vi 0..MAX(num_value)
. Nếu tập dữ liệu của bạn sai lệch mạnh mẽ với giả định này, bạn sẽ nhận được kết quả sai lệch (một số hàng sẽ xuất hiện thường xuyên hơn các hàng khác).
Tôi không biết hiệu quả của nó như thế nào, nhưng tôi đã sử dụng nó trước đây:
SELECT TOP 1 * FROM MyTable ORDER BY newid()
Vì GUID khá ngẫu nhiên, nên việc đặt hàng có nghĩa là bạn nhận được một hàng ngẫu nhiên.
ORDER BY RAND() LIMIT 1
TOP 1
và newid()
.
ORDER BY NEWID()
nhận 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
mất 0.0065 milliseconds
!
Tôi chắc chắn sẽ đi với phương pháp sau.
rand()
trả về số dấu phẩy động n
trong đó 0 < n < 1
. Giả sử num_value
là một số nguyên, giá trị trả về của rand() * max(num_value)
cũng sẽ bị ép buộc thành một số nguyên, do đó cắt bất cứ thứ gì sau dấu thập phân. Do đó, rand() * max(num_value)
sẽ luôn luôn ít hơn max(num_value)
, đó là lý do tại sao hàng cuối cùng sẽ không bao giờ được chọn.
Bạn đã không nói máy chủ nào bạn đang sử dụng. Trong các phiên bản cũ hơn của SQL Server, bạn có thể sử dụng điều này:
select top 1 * from mytable order by newid()
Trong SQL Server 2005 trở lên, bạn có thể sử dụng TABLESAMPLE
để lấy một mẫu ngẫu nhiên có thể lặp lại:
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
Đối với máy chủ SQL
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 đây 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.
Nếu có thể, hãy sử dụng các câu lệnh được lưu trữ để tránh sự kém hiệu quả của cả hai chỉ mục trên RND () và tạo trường số bản ghi.
CHUẨN BỊ RandomRecord TỪ "CHỌN * TỪ GIỚI HẠN bảng ?, 1"; SET @ n = FLOOR (RAND () * (CHỌN COUNT (*) TỪ bảng)); EXECUTE RandomRecord SỬ DỤNG @n;
Cách tốt nhất là đặt một giá trị ngẫu nhiên vào một cột mới chỉ cho mục đích đó và sử dụng một cái gì đó như thế này (mã giả + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
Đây là giải pháp được sử dụng bởi mã MediaWiki. Tất nhiên, có một số sai lệch so với các giá trị nhỏ hơn, nhưng họ thấy rằng nó đủ để bọc giá trị ngẫu nhiên quanh 0 khi không có hàng nào được tìm nạp.
giải pháp newid () có thể yêu cầu quét toàn bộ bảng để mỗi hàng có thể được chỉ định một hướng dẫn mới, sẽ ít hiệu quả hơn nhiều.
Giải pháp rand () hoàn toàn không thể hoạt động (ví dụ với MSSQL) vì hàm này sẽ được đánh giá chỉ một lần và mỗi hàng sẽ được gán cùng một số "ngẫu nhiên".
Đối với SQL Server 2005 và 2008, nếu chúng tôi muốn một mẫu ngẫu nhiên các hàng riêng lẻ (từ Sách trực tuyến ):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
Được sử dụng RAND (), vì nó không được khuyến khích , bạn có thể chỉ cần lấy ID tối đa (= Max):
SELECT MAX(ID) FROM TABLE;
nhận ngẫu nhiên giữa 1..Max (= My_Generated_Random)
My_Generated_Random = rand_in_your_programming_lang_function(1..Max);
và sau đó chạy SQL này:
SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1
Lưu ý rằng nó sẽ kiểm tra bất kỳ hàng nào có Id là THIẾT BỊ hoặc CAO hơn giá trị đã chọn. Bạn cũng có thể tìm kiếm hàng trong bảng và nhận ID bằng hoặc thấp hơn My_Generated_Random, sau đó sửa đổi truy vấn như sau:
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC LIMIT 1
Như đã chỉ ra trong nhận xét của @ BillKarwin về câu trả lời của @ cnu ...
Khi kết hợp với GIỚI HẠN, tôi đã thấy rằng nó hoạt động tốt hơn nhiều (ít nhất là với PostgreQuery 9.1) để THAM GIA với một thứ tự ngẫu nhiên thay vì đặt trực tiếp các hàng thực tế: vd
SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
FROM tbl_post
WHERE create_time >= 1349928000
) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100
Chỉ cần đảm bảo rằng 'r' tạo ra giá trị 'rand' cho mọi giá trị khóa có thể có trong truy vấn phức tạp được nối với nó nhưng vẫn giới hạn số lượng hàng 'r' nếu có thể.
CAST as Integer đặc biệt hữu ích cho PostgreQuery 9.2, có tối ưu hóa sắp xếp cụ thể cho các kiểu nổi chính xác và số nguyên.
Hầu hết các giải pháp ở đây nhằm tránh việc sắp xếp, nhưng chúng vẫn cần thực hiện quét tuần tự trên một bảng.
Cũng có một cách để tránh quét tuần tự bằng cách chuyển sang quét chỉ mục. Nếu bạn biết giá trị chỉ mục của hàng ngẫu nhiên, bạn có thể nhận được kết quả gần như ngay lập tức. Vấn đề là - làm thế nào để đoán một giá trị chỉ số.
Giải pháp sau đây hoạt động trên PostgreSQL 8.4:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
limit 1;
Tôi ở trên giải pháp bạn đoán 10 giá trị chỉ số ngẫu nhiên khác nhau từ phạm vi 0 .. [giá trị cuối cùng của id].
Số 10 là tùy ý - bạn có thể sử dụng 100 hoặc 1000 vì nó (đáng kinh ngạc) không ảnh hưởng lớn đến thời gian phản hồi.
Cũng có một vấn đề - nếu bạn có id thưa thớt bạn có thể bỏ lỡ . Giải pháp là có một kế hoạch dự phòng :) Trong trường hợp này là một thứ tự thuần túy theo truy vấn ngẫu nhiên (). Khi id kết hợp trông như thế này:
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
union all (select * from cms_refs order by random() limit 1)
limit 1;
Không phải mệnh đề TẤT CẢ liên minh . Trong trường hợp này nếu phần đầu tiên trả về bất kỳ dữ liệu nào thì phần thứ hai KHÔNG BAO GIỜ được thực thi!
Cuối cùng, nhưng đã đến đây thông qua Google, vì lợi ích của hậu thế, tôi sẽ thêm một giải pháp thay thế.
Một cách tiếp cận khác là sử dụng TOP hai lần, với các đơn đặt hàng xen kẽ. Tôi không biết đó có phải là "SQL thuần" hay không, bởi vì nó sử dụng một biến trong TOP, nhưng nó hoạt động trong SQL Server 2008. Đây là một ví dụ tôi sử dụng đối với một bảng từ trong từ điển, nếu tôi muốn một từ ngẫu nhiên.
SELECT TOP 1
word
FROM (
SELECT TOP(@idx)
word
FROM
dbo.DictionaryAbridged WITH(NOLOCK)
ORDER BY
word DESC
) AS D
ORDER BY
word ASC
Tất nhiên, @idx là một số nguyên được tạo ngẫu nhiên, nằm trong khoảng từ 1 đến COUNT (*) trên bảng đích. Nếu cột của bạn được lập chỉ mục, bạn cũng sẽ được hưởng lợi từ nó. Một ưu điểm khác là bạn có thể sử dụng nó trong một hàm, vì NEWID () không được phép.
Cuối cùng, truy vấn trên chạy trong khoảng 1/10 thời gian thực hiện của NEWID () - loại truy vấn trên cùng một bảng. YYMV.
Bạn cũng có thể thử sử dụng new id()
chức năng.
Chỉ cần viết một truy vấn của bạn và sử dụng thứ tự theo new id()
chức năng. Nó khá ngẫu nhiên.
Để MySQL có được bản ghi ngẫu nhiên
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1
Chi tiết hơn http://jan.kneschke.de/projects/mysql/order-by-rand/
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ó cho 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.
Trong MSSQL (được thử nghiệm vào ngày 11.0.5569) bằng cách sử dụng
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
nhanh hơn đáng kể so với
SELECT TOP 100 * FROM employee ORDER BY NEWID()
Trong SQL Server, bạn có thể kết hợp TABLESAMPLE với NEWID () để có được tính ngẫu nhiên khá tốt và vẫn có tốc độ. Điều này đặc biệt hữu ích nếu bạn thực sự chỉ muốn 1 hoặc một số lượng nhỏ các hàng.
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
Với SQL Server 2012+, bạn có thể sử dụng truy vấn OFFSET FETCH để thực hiện việc này cho một hàng ngẫu nhiên
select * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY
trong đó id là một cột định danh và n là hàng bạn muốn - được tính là một số ngẫu nhiên trong khoảng từ 0 đến đếm () - 1 của bảng (bù 0 là hàng đầu tiên sau tất cả)
Điều này hoạt động với các lỗ trong dữ liệu bảng, miễn là bạn có một chỉ mục để làm việc với mệnh đề ORDER BY. Nó cũng rất tốt cho sự ngẫu nhiên - khi bạn làm việc mà tự mình vượt qua nhưng những khúc mắc trong các phương pháp khác không có mặt. Ngoài ra, hiệu suất khá tốt, trên một tập dữ liệu nhỏ hơn, nó vẫn giữ tốt, mặc dù tôi đã không thử các bài kiểm tra hiệu suất nghiêm trọng đối với vài triệu hàng.
SELECT * FROM table ORDER BY RAND() LIMIT 1
Tôi phải đồng ý với CD-MaN: Sử dụng "ORDER BY RAND ()" sẽ hoạt động tốt cho các bảng nhỏ hoặc khi bạn thực hiện CHỌN chỉ một vài lần.
Tôi cũng sử dụng kỹ thuật "num_value> = RAND () * ..." và nếu tôi thực sự muốn có kết quả ngẫu nhiên, tôi có một cột "ngẫu nhiên" đặc biệt trong bảng mà tôi cập nhật mỗi ngày một lần. Lần chạy CẬP NHẬT duy nhất đó sẽ mất một chút thời gian (đặc biệt là vì bạn sẽ phải có một chỉ mục trên cột đó), nhưng nó nhanh hơn nhiều so với việc tạo các số ngẫu nhiên cho mỗi hàng mỗi khi lựa chọn được chạy.
Hãy cẩn thận vì TableSample không thực sự trả về một mẫu hàng ngẫu nhiên. Nó hướng truy vấn của bạn để xem xét một mẫu ngẫu nhiên của các trang 8KB tạo nên hàng của bạn. Sau đó, truy vấn của bạn được thực hiện đối với dữ liệu có trong các trang này. Do cách dữ liệu có thể được nhóm trên các trang này (thứ tự chèn, v.v.), điều này có thể dẫn đến dữ liệu không thực sự là một mẫu ngẫu nhiên.
Xem: http://www.mssqltips.com/tip.asp?tip=1308
Trang MSDN cho TableSample này bao gồm một ví dụ về cách tạo một mẫu dữ liệu ngẫu nhiên thực tế.
Có vẻ như nhiều ý tưởng được liệt kê vẫn sử dụng đặt hàng
Tuy nhiên, nếu bạn sử dụng bảng tạm thời, bạn có thể chỉ định một chỉ mục ngẫu nhiên (giống như nhiều giải pháp đã đề xuất), sau đó lấy bảng đầu tiên lớn hơn số tùy ý trong khoảng từ 0 đến 1.
Ví dụ (đối với DB2):
WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY
Một cách đơn giản và hiệu quả từ http://akinas.com/pages/en/blog/mysql_random_row/
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
Đối với SQL Server 2005 trở lên, mở rộng câu trả lời của @ GreyPanther cho các trường hợp khi num_value
không có giá trị liên tục. Điều này cũng hoạt động đối với các trường hợp khi chúng ta không phân phối các bộ dữ liệu và khi đó num_value
không phải là một số mà là một định danh duy nhất.
WITH CTE_Table (SelRow, num_value)
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
)
SELECT * FROM table Where num_value = (
SELECT TOP 1 num_value FROM CTE_Table WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
Chức năng ngẫu nhiên từ sql có thể giúp đỡ. Ngoài ra nếu bạn muốn giới hạn chỉ một hàng, chỉ cần thêm nó vào cuối.
SELECT column FROM table
ORDER BY RAND()
LIMIT 1