非常に単純なテーブルが必要だとしましょう
CREATE TABLE a(id integer PRIMARY KEY,
t timestamp default now(),
sensor_readings real[]);
CREATE TABLE b(id integer PRIMARY KEY,
t timestamp default now(),
sensor_readings real[]);
それらのいくつかのデータで
INSERT INTO a(id) SELECT generate_series( 1, 100);
INSERT INTO b(id) SELECT generate_series(10001, 10100);
実際には、テーブルaには約100_000行、テーブルbには約50_000の行があります。また、practiveでは、idシーケンスにギャップ(数%のオーダー)がある場合があります。したがって、デカルト積a x bのカーディナリティは数十億です。
1_000の並べ替えられたペア(a.id、b.id)のランダムなサンプルを取得します。次のようなクエリを使用できます。
SELECT
*
FROM
(
SELECT
*
FROM
(
SELECT
a.id AS a_id, b.id AS b_id
FROM
a CROSS JOIN b
ORDER BY
random()
) AS s0
LIMIT
1000
) AS s1
ORDER BY
a_id, b_id ;
...しかし、(CROSS JOINの増加により)aまたはbの行数が増加するとすぐに、非常に非効率になります。
これと同等のことを最適な方法で行う方法はありますか?つまり、実際にインスタンス化する必要なく、a x b
リレーションから行のランダムなサンプルを取得する実用的な方法があります。
注:a.idまたはb.idを繰り返すことができるという事実に制限はありません。ペア(a.id、b.id)はできませんが。
これを命令型言語でプログラミングしようとした場合、おそらくループを使用して、次の疑似コードのようなことを行います(そして、統計学者にチェックしてもらい、実際にサンプルを取得するようにします)すべてのペアが選択される確率は同じです):
start with a result set equal to {} (empty set)
while size of result set < 1000
Pick the id value from a random row from table a -> Rand_id_a
Pick the id value from a random row from table b -> Rand_id_b
If (Rand_id_a, Rand_id_b) not in result set
append (Rand_id_a, Rand_id_b) to result set
end if
end while
sort result set and return it
ループに頼らずに同等の結果を達成する方法はありますか?そうでない場合、plpgSQLを使用してそれを行う効率的な方法はありますか? (またはその他の言語)
最良の解決策は、セットアップのexact定義に依存します。設定例ではそれは取るに足らないことです:
_SELECT DISTINCT
1 + trunc(random() * 100)::int AS a_id
, 10001 + trunc(random() * 100)::int AS b_id
FROM generate_series(1, 1100) g -- enough excess to make up for possible dupes
LIMIT 1000; -- only take 1000
_
唯一の興味深い質問:デュープを効率的に折りたたむ方法。解決策:Postgresに決定を任せます。単にDISTINCT
を使用します。
テーブルを関与させる必要すらありません。超早い。
random()
は( ドキュメントごと )を生成することに注意してください:
0.0 <= x <1.0の範囲のランダムな値
したがって、1 + trunc(random() * 100)::int
は、1と100の間のID番号を正確にカバーします。
実際の設定についてより具体的にする必要があります。 ID列だけでなく、各テーブルに少なくともpayload列があると仮定します。
_CREATE TABLE a(a_id integer PRIMARY KEY, a text);
CREATE TABLE b(b_id integer PRIMARY KEY, b text);
INSERT INTO a(a_id, a)
SELECT g, 't' || g FROM generate_series( 1, 100) g;
INSERT INTO b(b_id, b)
SELECT g, 't' || g FROM generate_series(10001, 10100) g;
_
クエリ:
_SELECT a.a_id, a.a, b.b_id, b.b
FROM (
SELECT DISTINCT
1 + trunc(random() * 100)::int AS a_id -- cover *whole* key space
, 10001 + trunc(random() * 100)::int AS b_id -- maybe add reserve for new rows
FROM generate_series(1, 1100) g
LIMIT 1000
) ra
JOIN a USING (a_id)
JOIN b USING (b_id);
_
完全にランダムで、非常に高速で、実際のテーブルサイズにほとんど依存しません。
必要なのは、a(a_id)
およびb(b_id)
のインデックスです。あるいは、インデックスのみのスキャンを許可するための複数列インデックス。
ソリューションは、スキップされたnextval()
呼び出しからのfew gapsに対しても機能します。 島よりもギャップが多くない限り、ギャップによる損失をカバーするのに十分な組み合わせを生成することは依然として非常に安価です。 (大きなテーブルのデカルト積で作業したり、いずれにせよORDER BY random()
で大きなテーブル全体をソートしたりするよりもはるかに安い。)十分な組み合わせを生成するようにしてください。
_SELECT a.a_id, a.a, b.b_id, b.b
FROM (
SELECT DISTINCT
1 + trunc(random() * 100)::int AS a_id
, 10001 + trunc(random() * 100)::int AS b_id
FROM generate_series(1, 1100) g -- enough to cover dupes *and* gaps
) ra
JOIN a USING (a_id)
JOIN b USING (b_id)
LIMIT 1000; -- LIMIT moves to outer query to cover gaps
_
いくつかのギャップよりも多くの場合、95%の時間で十分な数の組み合わせから始め、必要に応じて行を追加するための再帰的なステップを追加します及ばない。関連する回答に、このソリューションのレシピ(単一テーブル用)があります。また、より多くの説明とバリエーション:
1000ソートされたペアのランダムサンプル(a.id、b.id)を取得します。
randomの意味に常に依存しますが、必要な行数を定義している場合は、拡張機能 _tsm_system_rows
_ が必要になる可能性があります。
モジュールは、SELECTコマンドのTABLESAMPLE句で使用できるテーブルサンプリングメソッドSYSTEM_ROWSを提供します。
このテーブルサンプリングメソッドは、読み取る行の最大数である単一の整数引数を受け入れます。テーブルに十分な行が含まれていない場合を除いて、結果のサンプルには常に正確にその数の行が含まれます。その場合、テーブル全体が選択されます。 組み込みのSYSTEMサンプリングメソッドと同様に、SYSTEM_ROWSはブロックレベルのサンプリングを実行するため、サンプルは完全にランダムではありませんが、特に少数の行のみが要求される場合は、クラスタリングの影響を受ける可能性があります。
最初に拡張機能をインストールします
_CREATE EXTENSION tsm_system_rows;
_
次に、クエリ
_SELECT *
FROM a
CROSS JOIN b
TABLESAMPLE SYSTEM_ROWS(1000);
_
ここで重要なのは、それが常に1000 ROWSを提供するであるということです。これは、random() <= 0.10
または_TABLESAMPLE BERNOULLI
_の場合は言うことができません。
本当にランダムが必要で、クラスタリングの欠点を受け入れることができない場合、私は使用します
_ORDER BY random()
LIMIT x;
_
重複を削除し(_a.id
_および_b.id
_がUNIQUE
でない場合)、結果セットをランダムに保つ唯一の健全な方法は、事前に行うことです。 。TABLESAMPLE
は仮想テーブルではまだ機能しないため、厄介になる可能性があります。そのため、一時テーブルを作成する必要があります(まだメモリに保持される場合があります)。その恥ずかしがり屋ですが、遅くて醜い他の方法を使うことができますが、少なくともそれを書く必要はありません
_SELECT *
FROM (
SELECT DISTINCT ON(a.id, b.id) a.id, b.id
FROM a
CROSS JOIN b
) AS t
ORDER BY random()
FETCH FIRST 1000 ROWS ONLY;
_