PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Cách tốt nhất để chọn các hàng ngẫu nhiên PostgreSQL

Cung cấp thông số kỹ thuật của bạn (cùng với thông tin bổ sung trong nhận xét),

  • Bạn có một cột ID dạng số (số nguyên) chỉ có một vài khoảng trống (hoặc vừa phải).
  • Rõ ràng là không có hoặc có ít thao tác ghi.
  • Cột ID của bạn phải được lập chỉ mục! Khóa chính phân phối tốt.

Truy vấn bên dưới không cần quét tuần tự bảng lớn mà chỉ quét chỉ mục.

Đầu tiên, nhận ướ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 bảng lớn). Với các thông số kỹ thuật trên, bạn không cần nó. Một ước tính sẽ hoạt động tốt, gần như miễn phí (giải thích chi tiết tại đây):

SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;

Miễn là ct không nhiều nhỏ hơn id_span , truy vấn sẽ hoạt động tốt hơn các cách tiếp cận 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 khoảng trống. Bạn có "một vài khoảng trống", vì vậy hãy thêm 10% (đủ để dễ dàng che các khoảng trống) vào số hàng cần truy xuất.

  • Mỗi 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 không gian id lớn), vì vậy hãy nhóm các số đã tạo (hoặc sử dụng DISTINCT ).

  • Tham gia id s đến bàn lớn. Việc này sẽ diễn ra rất nhanh với chỉ mục tại chỗ.

  • Cuối cùng cắt id thừa s mà đã không bị ăn bởi lừa đảo và khoảng trống. Mọi hàng đều có 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 nếu bạn không chắc chắn lắm về khoảng trống 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
   )
TABLE  random_pick
LIMIT  1000;  -- actual limit

Chúng tôi có thể làm việc với thặng dư nhỏ hơn trong truy vấn cơ sở. Nếu có quá nhiều khoảng trống khiến chúng ta không tìm thấy đủ hàng trong lần lặp đầu tiên, thì rCTE tiếp tục lặp với số hạng đệ quy. Chúng tôi vẫn cần tương đối ít các khoảng trống trong không gian ID hoặc đệ quy có thể cạn kiệt trước khi đạt đến giới hạn - hoặc chúng ta 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ỏ bởi UNION trong rCTE.

LIMIT bên ngoài làm cho CTE dừng ngay khi chúng ta có đủ hàng.

Truy vấn này được soạn 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 hết). Có một số cạm bẫy ở đây nếu bạn định viết lại nó.

Kết hợp với hàm

Để 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
  LANGUAGE plpgsql VOLATILE ROWS 1000 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
   )
   TABLE  random_pick
   LIMIT  _limit;
END
$func$;

Gọi:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);

Bạn thậm chí có thể đặt tên 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 làm kiểu đ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ấu trúc lại một hàm PL / pgSQL để trả về kết quả đầu ra của các truy vấn SELECT khác nhau

Có thể thay thế

NẾU yêu cầu của bạn cho phép các bộ giống nhau lặp lại cuộc gọ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 chế độ xem 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 nhận được một lựa chọn gần như ngẫu nhiên ở tốc độ làm sáng. Làm mới lựa chọn ngẫu nhiên của bạn trong các khoảng thời gian hoặc sự kiện bạn chọn.

Postgres 9.5 giới thiệu TABLESAMPLE SYSTEM (n)

Ở đâu n là một tỷ lệ phần trăm. Hướng dẫn sử dụng:

BERNOULLISYSTEM mỗi phương pháp lấy mẫu chấp nhận một đối số duy nhất là phần của bảng cần lấy mẫu, được biểu thị bằng phần trăm từ 0 đến 100 . Đối số này có thể là bất kỳ real nào -giá trị biểu thức.

Tôi nhấn mạnh đậm. Nó rất nhanh , nhưng kết quả là không hoàn toàn ngẫu nhiên . Hướng dẫn sử dụng lại:

SYSTEM nhanh hơn đáng kể so với BERNOULLI khi tỷ lệ phần trăm lấy mẫu nhỏ được chỉ định, nhưng nó có thể trả về mẫu không phải ngẫu nhiên của bảng do kết quả của hiệu ứng phân nhóm.

Số lượng hàng được trả về có thể rất khác nhau. Ví dụ của chúng tôi, để nhận được khoảng 1000 hàng:

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);

Có liên quan:

  • Cách nhanh chóng để khám phá số hàng của một bảng trong PostgreSQL

Hoặc cài đặt mô-đun bổ sung tsm_system_rows để lấy chính xác số hàng được yêu cầu (nếu có đủ) và cho phép sử dụng 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 đó vẫn không phải là ngẫu nhiên.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Không có hàm nào phù hợp với các loại đối số và tên đã cho

  2. Tắt cảnh báo trong sqlalchemy

  3. Cách Setseed () hoạt động trong PostgreSQL

  4. Làm cách nào để ghép hai từ cuối cùng trong một câu trong PostgreSQL?

  5. Tạo n hàng NULL trong PostgreSQL