web-dev-qa-db-ja.com

時間と距離に基づいてテーブルを結合する:PostGISとPostgreSQL?

時間と位置のデータを含む数千行のテーブルがあります。同じgroup_idを持つ行のタイムスタンプは同じです。このテーブルを「小」と呼びましょう。

Small Table:
id  event_time                   group_id   item_id  position
1   '2018-06-21 18:35:01.631094+00' '123a'  1   '01010230...'
2   '2018-06-21 18:35:01.630881+00' '123a'  2   '01010044...'
3   '2018-06-18 10:35:01.630663+00' '321b'  1   '01015600...'
4   '2018-06-18 10:35:01.630305+00' '321b'  2   '01010031...'

同様のデータ列(時間、位置、データ1、データ2など)を持つ別のテーブル(テーブル "ビッグ")があります。このテーブルのタイムスタンプは連続的で、最初のテーブルと重複しており、8000万行以上あります。

Big Table:
id  event_time                       Data1   position
1   '2018-06-21 18:45:01.631094+00' 'john'  '01013000...'
2   '2018-06-21 18:41:01.630881+00' 'joe'   '01016000...'
3   '2018-06-21 18:33:01.630663+00' 'john'  '01017000...'
4   '2018-06-21 18:30:01.630305+00' 'rory'  '01018000...'

2つのテーブルに地理空間インデックスと時間インデックスがあります。

私がしたいことは、BigとSmallの間で最も近い一致を見つけ、data1、data2、および空間と時間の違いを返すことです。つまり、「ジョン」がグループ「123a」のアイテム2に最も一致することを確認したいと思います。彼はそこから100 mと2分でしたが、「ロリー」はアイテム1に最も近かった(5分と1 kmまたはなんでも)。

私はこれに似たコマンドを試しましたが、遅すぎます。インデックスを使用していないようです。

SELECT 
  big.id, 
  small.id,
  st_distance(big.position, small.position) as pos_delta,
  (big.event_time, small.event_time) as time_delta,
  big.data1,
  small.item_id
FROM big, small 
WHERE
  (big.event_time - small.event_time) < '2 hours'
ORDER BY login_sar_vessel.position <-> login_pos_report.position
LIMIT 1

最初に大きなテーブルからgroup_idに関連付けられたタイムスタンプの前後に2時間データを選択し、距離が最も小さい(big.positionからsmall.position)大きな行を見つけて、各group_idに対して繰り返す方法はありますか?それは少し厄介なようです。

ああ、DBはpostgres 9.6とpostgis 2.4です。

3
RedM

あなたはこのようなものが欲しいと思います、

SELECT
  big.id,
  small.id,
  ST_Distance(big.geom, small.geom),
  big.event_time <-> small.event_time
FROM small
CROSS JOIN LATERAL (
  SELECT *
  FROM big
  WHERE (small.event_time <-> big.event_time) < '2 hours'::interval
  ORDER BY small.geom <-> big.geom
  OFFSET 0
  LIMIT 1
) AS big;

btree_Gist を使用して、timestmapとgeomの両方にこれらの両方を追加して、さらに楽しいことができます。

CREATE EXTENSION btree_Gist;

CREATE INDEX ON big USING Gist (event_time, geom);
CREATE INDEX ON small USING Gist (event_time, geom);
VACUUM FULL ANALYZE big;
VACUUM FULL ANALYZE small;

注:PostgreSQL 10にアップグレードすると、この作業負荷にmajorの違いが生じるため、並列処理が必要になります。

タイムスタンプのBRINインデックス、およびタイムスタンプによるクラスタリングも検討できます。その後、GISに移行します。または、インデックス付きの新しいMATERIALIZED VIEWデータの最後の1週間のみを含めます。

1
Evan Carroll

これが私が最終的に使用したオプションです。いくつかの一般的なテーブル式を使用して、必要な最小量にデータを削減します。いくつかの「ビッグ」データを「小さい」タイムスタンプの前後からフェッチするという考えです。次に、時間と空間で「小」ポイントに最も近い「大」ポイントを並べ替えて検索します。いくつかの作業が必要です:

--Get Small info
WITH small_cut AS
(SELECT * FROM
small 
WHERE
group_id = 'abc123'
),

-- Get BIG info from before Small event 
big_before as
(SELECT DISTINCT ON (Data1)
big.data1
big.position,
big.event_time, 
(small_cut.event_time - big.event_time) as time_delta,
FROM
big,small_cut
WHERE
ST_Within(big.position, small_cut.bounding_geom)
AND
big.event_time > small_cut.event_timestamp - INTERVAL '1 Hours'  
AND
big.event_time < small_cut.event_timestamp
ORDER BY data1, big.event_time DESC
),

big_after as
(SELECT DISTINCT ON (Data1)
big.data1
big.position,
big.event_time, 
(small_cut.event_time - big.event_time) as time_delta,
FROM
big,small_cut
WHERE
ST_Within(big.position, small_cut.bounding_geom)
AND
big.event_time > small_cut.event_timestamp - INTERVAL '1 Hours'  
AND
big.event_time < small_cut.event_timestamp
ORDER BY data1, big.event_time DESC
)

SELECT DISTINCT ON (small.item_id)
* 
FROM Big, small_cut, big_before, big_after
WHERE
{some Id's are equal to other's}

私のDB構造が少し変更され、提供したダミーデータと100%インライン化されていないため、これが他の人にとってどれほど役立つかわかりません。

0
RedM