web-dev-qa-db-ja.com

範囲の配列から重複する要素のみを選択する

検索する必要のあるテーブルには、以下に示すnumrange値の配列が含まれています。

_CREATE TABLE data ( sensor varchar(25), ranges numrange[] );
INSERT INTO data VALUES
  ('sensor0','{"[872985609.0,873017999.0]","[873021600.0,873035999.0]","[873039600.0,873072070.0]"}'::numrange[])
, ('sensor1','{"[872929250.000000,872985609.000000]"}'::numrange[]);
_

rangesを使用すると、指定した範囲と重複するANYの要素を含む行を簡単に検索できます。

_SELECT * FROM data WHERE '[873021700,873021800]'::numrange && ANY (ranges)
_

テーブルからフィールドを返すSELECTステートメントと、指定された範囲と重複する配列のonly要素が必要です。

_SELECT sensor,[array of overlapping numranges] FROM data WHERE '[873021700,873021800]'::numrange && ANY (ranges)
_

その結果:

_sensor      ranges
sensor0     {"[873021600.0,873035999.0]"}
_

これは、サブセレクトで配列をunnest() ingせずに可能ですか?そうでない場合、配列が潜在的に非常に大きい場合、unnest()への効率的な方法は何でしょうか?

2
terse

これは必要に応じて機能します。

SELECT d.sensor, r.overlapping_ranges
FROM   data d
JOIN   LATERAL (
   SELECT array_agg(range) AS overlapping_ranges
   FROM   unnest(d.ranges) range
   WHERE  range && '[873021700,873021800]'::numrange 
   ) r ON overlapping_ranges IS NOT NULL;

LATERALについて:

大きなテーブルの場合、範囲配列の代わりに個別のrangesテーブル(行ごとに1つの範囲)を使用して設計を正規化する方がはるかに効率的です。そのためにGistインデックスを使用できます。


巨大なテーブルのソリューション

コメントで言及したような巨大なテーブル(10億行)の場合、サイズと BRIN index に最適化された別のrangesテーブルを検討します。それと一緒に行きます。

仮定:

  • 読み取り専用(またはほとんど)データ。
  • 最大6の小数桁数(scale)および最大18桁の合計(precision)。これは1000000でスケーリングされ、損失なくbigintに収まります。これは、保存するのにかなり安価です。下記参照。
  • Postgres 9.5以降。

範囲タイプの演算子クラス Postgres 9.5に同梱されているのはrange_inclusion_opsで、これはオーバーラップ演算子&&をサポートしています。

ディスクスペースをさらに最適化するには、two bigintnumbers(数値に1000000を掛けた値)を保存して機能させるだけです。 BRINインデックス。基本的にこのように:

CREATE TABLE sensors (
  sensor_id serial PRIMARY KEY
, sensor text NOT NULL);

CREATE TABLE ranges (
  sensor_id int NOT NULL REFERENCES sensors
, range_low bigint NOT NULL
, range_hi  bigint NOT NULL
);

INSERT INTO sensors (sensor) VALUES ('sensor1');

INSERT INTO ranges (sensor_id, range_low, range_hi) VALUES
   (1, 872985609.0 * 1000000, 873017999.0 * 1000000)  -- scaled
 , (1, 872929250.000000 * 1000000, 872985609.000000 * 1000000);

CREATE INDEX ranges_brin_idx ON ranges USING BRIN (int8range(range_low, range_hi, '[]'));

Queryこれまでと同じ結果を得るには:

SELECT s.sensor, r.ranges
FROM  (
   SELECT sensor_id
        , array_agg(numrange(range_low * .000001, range_hi * .000001, '[]')) AS ranges
   FROM   ranges
   WHERE  int8range(range_low, range_hi, '[]')
       && '[873021700000000,873021800000000]'::int8range  -- scaled as well
   GROUP  BY sensor_id
   ) r
JOIN   sensors s  USING (sensor_id);

bigintnumrangeのストレージサイズ

15桁の精度を持つnumrangeはディスク上で32バイトを占め、結果として1行あたり64バイトになります(さらに、int列、行ヘッダー、およびアイテムポインター)。

同じように2つのbigint列(2 x 8バイト)を使用すると、合計52バイトになります。テーブルを約12 GB小さくにします。インデックスのサイズは同じです。

自分で見て:

SELECT pg_column_size((1::bigint, '[873021700.123456,873021800.123456]'::numrange))
     , pg_column_size((1::bigint, 873021700123456::bigint, 873021700123456::bigint));

行サイズの詳細な説明:

3