大きなテーブルからグループごとに最大の価値を得るための効率的なクエリ


13

テーブルが与えられた場合:

    Column    |            Type             
 id           | integer                     
 latitude     | numeric(9,6)                
 longitude    | numeric(9,6)                
 speed        | integer                     
 equipment_id | integer                     
 created_at   | timestamp without time zone
Indexes:
    "geoposition_records_pkey" PRIMARY KEY, btree (id)

このテーブルには2000万件のレコードがありますが、比較的多くはありません。ただし、シーケンシャルスキャンが遅くなります。

max(created_at)それぞれの最後のレコード()を取得するにはどうすればよいequipment_idですか?

私はこのトピックの多くの回答を読んだいくつかのバリエーションを使用して、次の両方のクエリを試しました:

select max(created_at),equipment_id from geoposition_records group by equipment_id;

select distinct on (equipment_id) equipment_id,created_at 
  from geoposition_records order by equipment_id, created_at desc;

私もbtreeインデックスを作成しようとしましたequipment_id,created_atが、Postgresはseqscanを使用した方が速いことを発見しました。enable_seqscan = offインデックスの読み取りはseqスキャンと同じくらい遅く、おそらく悪いので、強制も役に立たない。

クエリは定期的に実行し、常に最後を返す必要があります。

Postgres 9.3を使用します。

説明/分析(170万件のレコード):

set enable_seqscan=true;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"HashAggregate  (cost=47803.77..47804.34 rows=57 width=12) (actual time=1935.536..1935.556 rows=58 loops=1)"
"  ->  Seq Scan on geoposition_records  (cost=0.00..39544.51 rows=1651851 width=12) (actual time=0.029..494.296 rows=1651851 loops=1)"
"Total runtime: 1935.632 ms"

set enable_seqscan=false;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"GroupAggregate  (cost=0.00..2995933.57 rows=57 width=12) (actual time=222.034..11305.073 rows=58 loops=1)"
"  ->  Index Scan using geoposition_records_equipment_id_created_at_idx on geoposition_records  (cost=0.00..2987673.75 rows=1651851 width=12) (actual time=0.062..10248.703 rows=1651851 loops=1)"
"Total runtime: 11305.161 ms"

前回前回確認したときに、期待されるパーセンテージにNULL値がequipment_id0.1%未満であることがなかった
-Feyd

回答:


10

結局のところ、単純な複数列のBツリーインデックスは機能するはずです。

CREATE INDEX foo_idx
ON geoposition_records (equipment_id, created_at DESC NULLS LAST);

なんでDESC NULLS LAST

関数

クエリプランナーに意味がわからない場合は、機器テーブルをループ処理する関数が役立ちます。一度に1つのequipment_idを検索するには、インデックスを使用します。少数(EXPLAIN ANALYZE出力から判断して57 )の場合、それは高速です。テーブル
があると仮定しても安全equipmentですか?

CREATE OR REPLACE FUNCTION f_latest_equip()
  RETURNS TABLE (equipment_id int, latest timestamp) AS
$func$
BEGIN
FOR equipment_id IN
   SELECT e.equipment_id FROM equipment e ORDER BY 1
LOOP
   SELECT g.created_at
   FROM   geoposition_records g
   WHERE  g.equipment_id = f_latest_equip.equipment_id
                           -- prepend function name to disambiguate
   ORDER  BY g.created_at DESC NULLS LAST
   LIMIT  1
   INTO   latest;

   RETURN NEXT;
END LOOP;
END  
$func$  LANGUAGE plpgsql STABLE;

いい電話もできます:

SELECT * FROM f_latest_equip();

相関サブクエリ

考えてequipmentみると、このテーブルを使用すると、相関性の低いサブクエリを使用して汚い作業を行うことができます。

SELECT equipment_id
     ,(SELECT created_at
       FROM   geoposition_records
       WHERE  equipment_id = eq.equipment_id
       ORDER  BY created_at DESC NULLS LAST
       LIMIT  1) AS latest
FROM   equipment eq;

性能はとても良いです。

LATERAL Postgres 9.3+に参加する

SELECT eq.equipment_id, r.latest
FROM   equipment eq
LEFT   JOIN LATERAL (
   SELECT created_at
   FROM   geoposition_records
   WHERE  equipment_id = eq.equipment_id
   ORDER  BY created_at DESC NULLS LAST
   LIMIT  1
   ) r(latest) ON true;

詳細な説明:

相関サブクエリと同様のパフォーマンス。比較、性能max()DISTINCT ON機能、相関サブクエリとLATERAL、この中:

SQL Fiddle


1
@ErwinBrandstetterこれはColinからの回答の後に試したものですが、これはデータベース側のn + 1クエリの種類を使用する回避策であると考えることを止めることはできません(アンチパターンがあるので接続のオーバーヘッドなし)...数百万件のレコードを適切に処理できない場合、なぜgroup byが存在するのか疑問に思います...意味がありません。不足しているものになります。最後に、質問がわずかに変更され、機器テーブルの存在を想定しています...実際には別の方法があるかどうかを知りたい
-Feyd

3

試行1

もし

  1. 別のequipmentテーブルがあり、
  2. にインデックスがあります geoposition_records(equipment_id, created_at desc)

それから私にとっては次のように動作します:

select id as equipment_id, (select max(created_at)
                            from geoposition_records
                            where equipment_id = equipment.id
                           ) as max_created_at
from equipment;

s のリストと関連するの両方を決定するためにPGに高速クエリを強制することはできませんでした。しかし、私は明日もう一度やります!equipment_idmax(created_at)

試行2

私はこのリンクを見つけました:http : //zogovic.com/post/44856908222/optimizing-postgresql-query-for-distinct-values この手法と試行1からのクエリを組み合わせると、次のようになります。

WITH RECURSIVE equipment(id) AS (
    SELECT MIN(equipment_id) FROM geoposition_records
  UNION
    SELECT (
      SELECT equipment_id
      FROM geoposition_records
      WHERE equipment_id > equipment.id
      ORDER BY equipment_id
      LIMIT 1
    )
    FROM equipment WHERE id IS NOT NULL
)
SELECT id AS equipment_id, (SELECT MAX(created_at)
                            FROM geoposition_records
                            WHERE equipment_id = equipment.id
                           ) AS max_created_at
FROM equipment;

そして、これは高速に動作します!しかし、あなたは必要です

  1. この超ゆがんだクエリフォーム
  2. のインデックスgeoposition_records(equipment_id, created_at desc)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.