Büyük tablodan grup başına en yüksek değeri elde etmek için etkili sorgu


14

Tablo verildiğinde:

    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)

Tabloda göreceli olarak çok sayıda olmayan 20 milyon kayıt vardır. Ancak sıralı taramaları yavaşlatır.

max(created_at)Her birinin son kaydını ( ) nasıl alabilirim equipment_id?

Bu konunun birçok cevabını okuduğum birkaç varyantla aşağıdaki sorguları da denedim:

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;

Ben de btree dizinleri oluşturmak için denedim equipment_id,created_atama Postgres bir seqscan kullanarak daha hızlı olduğunu bulur. enable_seqscan = offİndeksi okumak seq taraması kadar yavaş, muhtemelen daha kötü olduğu için zorlama da işe yaramaz .

Sorgu periyodik olarak her zaman sonuncuyu döndürerek çalışmalıdır.

Postgres Kullanımı 9.3.

Açıklayın / analiz edin (1,7 milyon kayıtla):

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"

iyi son kez ben oldum hiç vardı kontrol NULLdeğerleri equipment_idbeklenen yüzdesi% 0.1'in altında olduğu
Feyd

Yanıtlar:


10

Sonuçta, düz çok sütunlu bir b-ağacı endeksi çalışmalıdır:

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

Neden DESC NULLS LAST?

fonksiyon

Sorgu planlayıcı hakkında mantıklı konuşamıyorsanız, ekipman tablosunda döngü yapan bir işlev hile yapmalıdır. Tek seferde bir equipment_id ararken dizini kullanır. Küçük bir sayı için ( EXPLAIN ANALYZEçıktıdan bakıldığında 57 ), bu hızlı.
Masanız olduğunu varsaymak güvenli equipmentmi?

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;

Güzel bir çağrı da yapar:

SELECT * FROM f_latest_equip();

İlişkili alt sorgular

Bunu düşünün, bu equipmenttabloyu kullanarak , düşük korelasyonlu alt sorgular ile kirli çalışmaya büyük etki yaratabilirsiniz:

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;

Performans çok iyi.

LATERAL Postgres'e katıl 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;

Detaylı açıklama:

İlişkili alt sorgu ile benzer performans. Performansını karşılaştırmak max(), DISTINCT ONfonksiyon, korelasyon alt sorgu ve LATERALbunda:

SQL Fiddle .


1
@ErwinBrandstetter Bu Colin'den sonra denedim bir şey, ama bu tür bir veritabanı tarafı n + 1 sorguları kullanan bir geçici çözüm olduğunu düşünmeden duramıyorum (çünkü orada antipattern düşüyor emin değilim bağlantı yükü yok) ... Şimdi merak ediyorum neden grup by var, eğer birkaç milyon kaydı düzgün işleyemiyorsa ... Sadece mantıklı değil, var mı? kaçırdığımız bir şey ol. Son olarak, soru biraz değişti ve bir ekipman masasının varlığını varsayıyoruz ... Aslında başka bir yol olup olmadığını bilmek istiyorum
Feyd

3

Deneme 1

Eğer

  1. Ayrı bir equipmentmasam var ve
  2. Üzerinde bir dizinim var geoposition_records(equipment_id, created_at desc)

o zaman aşağıdakiler benim için çalışıyor:

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

Ben belirlemek için hızlı bir sorgu yapmak PG zorlamak mümkün değildi hem listesini equipment_ids ve ilgili max(created_at). Ama yarın tekrar deneyeceğim!

Deneme 2

Bu bağlantıyı buldum: http://zogovic.com/post/44856908222/optimizing-postgresql-query-for-distinct-values Bu tekniği deneme 1'den gelen sorgumla birleştirdiğimde:

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;

ve bu HIZLI çalışıyor! Ama ihtiyacın var

  1. bu ultra çarpık sorgu formu ve
  2. bir dizin geoposition_records(equipment_id, created_at desc).
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.