Đưa ra thông số kỹ thuật của bạn (cộng với thông tin bổ sung trong các ý kiến),
- Bạn có một cột ID số (số nguyên) chỉ có một vài khoảng trống (hoặc ít vừa phải).
- Rõ ràng không có hoặc ít hoạt động viết.
- Cột ID của bạn phải được lập chỉ mục! Một khóa chính phục vụ độc đáo.
Truy vấn dưới đây không cần quét tuần tự của bảng lớn, chỉ cần quét chỉ mục.
Đầu tiên, lấy ước tính cho truy vấn chính:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Phần đắt nhất có thể là count(*)
(đối với các bảng lớn). Đưa ra các thông số kỹ thuật trên, bạn không cần nó. Một ước tính sẽ làm tốt, có sẵn mà hầu như không mất phí ( giải thích chi tiết ở đây ):
SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
Chừng nào ct
không phải là nhiều nhỏ hơn id_span
, truy vấn sẽ tốt hơn các phương pháp khác.
WITH params AS (
SELECT 1 AS min_id -- minimum id <= current min id
, 5100000 AS id_span -- rounded up. (max_id - min_id + buffer)
)
SELECT *
FROM (
SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
FROM params p
,generate_series(1, 1100) g -- 1000 + buffer
GROUP BY 1 -- trim duplicates
) r
JOIN big USING (id)
LIMIT 1000; -- trim surplus
Tạo số ngẫu nhiên trong id
không gian. Bạn có "một vài khoảng trống", vì vậy hãy thêm 10% (đủ để dễ dàng che khoảng trống) vào số lượng hàng cần lấy.
Mỗi lần id
có thể được chọn nhiều lần một cách tình cờ (mặc dù rất khó xảy ra với một không gian id lớn), vì vậy hãy nhóm các số được tạo (hoặc sử dụng DISTINCT
).
Tham gia id
s đến bàn lớn. Điều này sẽ rất nhanh với chỉ số tại chỗ.
Cuối cùng cắt bớt id
s dư thừa chưa được ăn bởi các bản sao và khoảng trống. Mỗi hàng có một cơ hội hoàn toàn bình đẳng được chọn.
Phiên bản ngắn
Bạn có thể đơn giản hóa truy vấn này. CTE trong truy vấn trên chỉ dành cho mục đích giáo dục:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Tinh chỉnh với rCTE
Đặc biệt là nếu bạn không chắc chắn về khoảng cách và ước tính.
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM generate_series(1, 1030) -- 1000 + few percent - adapt to your needs
LIMIT 1030 -- hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
UNION -- eliminate dupe
SELECT b.*
FROM (
SELECT 1 + trunc(random() * 5100000)::int AS id
FROM random_pick r -- plus 3 percent - adapt to your needs
LIMIT 999 -- less than 1000, hint for query planner
) r
JOIN big b USING (id) -- eliminate miss
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
Chúng ta có thể làm việc với một khoản thặng dư nhỏ hơn trong truy vấn cơ sở. Nếu có quá nhiều khoảng trống để chúng tôi không tìm thấy đủ hàng trong lần lặp đầu tiên, rCTE tiếp tục lặp lại với thuật ngữ đệ quy. Chúng tôi vẫn cần tương đối ít khoảng trống trong không gian ID hoặc đệ quy có thể cạn trước khi đạt đến giới hạn - hoặc chúng tôi phải bắt đầu với một bộ đệm đủ lớn, bất chấp mục đích tối ưu hóa hiệu suất.
Các bản sao được loại bỏ UNION
trong rCTE.
Bên ngoài LIMIT
làm cho CTE dừng lại ngay khi chúng ta có đủ hàng.
Truy vấn này được phác thảo cẩn thận để sử dụng chỉ mục có sẵn, tạo các hàng thực sự ngẫu nhiên và không dừng lại cho đến khi chúng tôi hoàn thành giới hạn (trừ khi đệ quy chạy khô). Có một số cạm bẫy ở đây nếu bạn sẽ viết lại nó.
Bọc thành chức năng
Để sử dụng nhiều lần với các thông số khác nhau:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big AS
$func$
DECLARE
_surplus int := _limit * _gaps;
_estimate int := ( -- get current estimate from system
SELECT c.reltuples * _gaps
FROM pg_class c
WHERE c.oid = 'big'::regclass);
BEGIN
RETURN QUERY
WITH RECURSIVE random_pick AS (
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM generate_series(1, _surplus) g
LIMIT _surplus -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
UNION -- eliminate dupes
SELECT *
FROM (
SELECT 1 + trunc(random() * _estimate)::int
FROM random_pick -- just to make it recursive
LIMIT _limit -- hint for query planner
) r (id)
JOIN big USING (id) -- eliminate misses
)
SELECT *
FROM random_pick
LIMIT _limit;
END
$func$ LANGUAGE plpgsql VOLATILE ROWS 1000;
Gọi:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Bạn thậm chí có thể làm cho cái chung này hoạt động cho bất kỳ bảng nào: Lấy tên của cột PK và bảng dưới dạng đa hình và sử dụng EXECUTE
... Nhưng điều đó nằm ngoài phạm vi của câu hỏi này. Xem:
Có thể thay thế
NẾU các yêu cầu của bạn cho phép các bộ giống hệt nhau cho các cuộc gọi lặp lại (và chúng ta đang nói về các cuộc gọi lặp lại) Tôi sẽ xem xét một quan điểm cụ thể hóa . Thực hiện truy vấn trên một lần và ghi kết quả vào bảng. Người dùng có được một lựa chọn gần như ngẫu nhiên ở tốc độ sáng. Làm mới lựa chọn ngẫu nhiên của bạn trong khoảng thời gian hoặc các sự kiện bạn chọn.
Trong trường hợp n
là một tỷ lệ phần trăm. Hướng dẫn sử dụng:
Các BERNOULLI
và SYSTEM
phương pháp lấy mẫu từng chấp nhận một đối số duy nhất đó là phần của bảng để mẫu, biểu thị bằng
tỷ lệ phần trăm giữa 0 và 100 . Đối số này có thể là bất kỳ real
biểu thức định giá nào .
Nhấn mạnh đậm của tôi. Nó rất nhanh , nhưng kết quả không chính xác ngẫu nhiên . Hướng dẫn sử dụng một lần nữa:
Các SYSTEM
phương pháp là đáng kể nhanh hơn so với BERNOULLI
phương pháp tỷ lệ phần trăm khi lấy mẫu nhỏ được quy định, nhưng nó có thể trở lại một mẫu ít ngẫu nhiên của bảng là kết quả của hiệu ứng phân nhóm.
Số lượng hàng trả về có thể thay đổi dữ dội. Ví dụ của chúng tôi, để có được khoảng 1000 hàng:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Liên quan:
Hoặc cài đặt mô-đun tsm_system_rows bổ sung để có được số lượng hàng được yêu cầu chính xác (nếu có đủ) và cho phép cú pháp thuận tiện hơn:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Xem câu trả lời của Evan để biết chi tiết.
Nhưng điều đó vẫn không chính xác ngẫu nhiên.