web-dev-qa-db-ja.com

PostgreSQLでのサンプリング

PostgreSQLでのランダムサンプリングの可能な方法を探しています。私はそれを行うためにいくつかの利点と欠点を持ついくつかの方法を見つけました。これを行う素朴な方法は次のとおりです。

select * from Table_Name
order by random()
limit 10;

別の高速な方法は次のとおりです。

select * from Table_Name
WHERE random() <= 0.01
order by random()
limit 10;

(ただし、0.01はテーブルサイズとサンプルサイズによって異なりますが、これは単なる例です。)

これらのクエリの両方で、各行に対して乱数が生成され、それらのランダムに生成された番号に基づいてソートされます。次に、ソートされた数値では、最初の10が最終結果として選択されるので、これらは置き換えなしでサンプリングする必要があると思います。

さて、私がやりたいのは、このサンプリング方法を何とかして置換サンプリングに変えることです。そんなことがあるものか?または、PostgreSQLに置き換えられた他のランダムサンプリング方法はありますか?

私はこれがどのように可能であるかもしれないという考えを持っていると言わなければなりませんが、それをPostgresに実装する方法がわかりません。これが私の考えです:

1つのランダム値を生成する代わりに、Sランダム値を生成する場合(Sはサンプルサイズ)、ランダムに生成されたすべての値を順序付けすると、置換してサンプリングされます。 (私が正しいかどうかはわかりません。)
この時点では、クエリのパフォーマンスは気にしません。

4
Rana

Postgres 9.3以降では、以下を使用できます。

select t.*
from 
  generate_series(1, 10) as x(i)
    cross join lateral
  ( select *, x.i
    from Table_Name
    -- where random() < 0.01
    order by random()
    limit 1 
  ) ;

これは基本的に1つのランダムな行を10回選択します。


古いバージョンでは、単純なcross joinを使用できます(lateralは使用できません)。

select t.*
from 
    generate_series(1, 1000) as x(i)
  cross join 
    Table_Name as t
    -- where random() < 0.01
    order by random()
    limit 10
  ) t ;

これにより、テーブルの1000倍のコピーが作成され(各行が1000回存在するため)、クエリと同じ方法で10行が選択されます。コピーの数(1000)が必要な行(10)と比較して十分に大きい場合、確率は、置換で得られる確率とほぼ同じです。

この2番目のクエリのパフォーマンスは、小さなテーブルであってももちろん恐ろしいものになります。

2
ypercubeᵀᴹ

修正

1つのランダム値を生成する代わりに、SがサンプルサイズであるSランダム値を生成する場合、ランダムに生成されたすべての値を順序付けすると、置換してサンプリングされます。 (私が正しいかどうかはわかりません。)

あなたは2つの点で間違っています。

  1. 元のクエリは「1つのランダムな値」を生成しません。 random()は、すべての行に対して呼び出される揮発性関数です。次に、すべての行が結果で並べ替えられます。これが、大きなテーブルでは非常に非効率的な理由です。
  2. したがって、「Sのランダムな値を生成する場合...」というアプローチはまったく機能しません。ランダムな行を「行番号」と一致するように「細分化」する必要があります。これは、既存のIDまたは代理番号のいずれかです。それは私が示すつもりです。

私はあなたが言ったことを知っています:

この時点では、クエリのパフォーマンスは気にしません。

だから @ ypercubeの答えは正しいです いつものように。しかし、私の心は出血しています。 ずっと速い方法があります。

最悪のケースで、テーブルについて何も知らないと仮定します。

各行に任意の順序で連番を付けると、必要に応じて複数回、ランダムな行を確実に選択できます。テーブルは1回スキャンされます。これは、巨大なテーブルと小さなサンプルの場合はまだ高価ですが、n回スキャンするよりもはるかに優れています。

WITH t AS (SELECT *, row_number() OVER () AS rn FROM tbl)
SELECT * FROM (
    SELECT trunc(random() * (SELECT max(rn) FROM t))::int + 1 AS rn
    FROM   generate_series(1, 10) g
    ) r
JOIN   t USING (rn);

各行は、何度も選択される可能性が同じです。

ギャップがほとんどない、またはまったくないID列がある場合、大きなテーブルにはより高速なオプションがあります。

3