Truy vấn hiệu quả để nhận giá trị lớn nhất cho mỗi nhóm từ bảng lớn


13

Đưa ra bảng:

    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)

Bảng này có 20 triệu bản ghi , tương đối không phải là một con số lớn. Nhưng nó làm cho quét liên tiếp chậm.

Làm thế nào tôi có thể nhận được bản ghi cuối cùng ( max(created_at)) của mỗi equipment_id?

Tôi đã thử cả hai truy vấn sau đây, với một số biến thể mà tôi đã đọc qua nhiều câu trả lời của chủ đề này:

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;

Tôi cũng đã thử tạo các chỉ mục btree cho equipment_id,created_atnhưng Postgres thấy rằng sử dụng seqscan nhanh hơn. Buộc enable_seqscan = offlà không sử dụng được vì đọc chỉ số cũng chậm như quét seq, có thể tồi tệ hơn.

Truy vấn phải chạy định kỳ luôn luôn trả về lần cuối.

Sử dụng Postgres 9.3.

Giải thích / phân tích (với 1,7 triệu hồ sơ):

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"

lần trước tôi đã kiểm tra không có NULLgiá trị nào trong equipment_idtỷ lệ phần trăm dự kiến ​​dưới 0,1%
Feyd

Câu trả lời:


10

Sau đó, một chỉ số cây đa nhiều đơn giản sẽ hoạt động:

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

Tại sao DESC NULLS LAST?

Chức năng

Nếu bạn không thể nói ý nghĩa với trình lập kế hoạch truy vấn, một hàm lặp qua bảng thiết bị sẽ thực hiện thủ thuật. Tra cứu một thiết bị_id tại một thời điểm sử dụng chỉ mục. Đối với một số lượng nhỏ (57 đánh giá từ EXPLAIN ANALYZEđầu ra của bạn ), đó là nhanh chóng.
Có an toàn không khi cho rằng bạn có một cái equipmentbàn?

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;

Cũng làm cho một cuộc gọi tốt đẹp:

SELECT * FROM f_latest_equip();

Truy vấn con tương quan

Hãy nghĩ về nó, sử dụng equipmentbảng này , bạn có thể làm việc bẩn với các truy vấn con có tương quan thấp để có hiệu quả tuyệt vời:

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;

Hiệu suất rất tốt.

LATERAL tham gia 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;

Giải thích chi tiết:

Hiệu suất tương tự như truy vấn con tương quan. So sánh hiệu suất của max(),, DISTINCT ONchức năng, truy vấn con tương quan và LATERALtrong này:

Câu đố SQL .


1
@ErwinBrandstetter đây là điều tôi đã thử sau câu trả lời từ Colin, nhưng tôi không thể ngừng nghĩ rằng đây là một cách giải quyết sử dụng loại truy vấn bên cơ sở dữ liệu n + 1 (không chắc là nó có rơi vào phản âm không vì có không có chi phí kết nối) ... Bây giờ tôi đang tự hỏi tại sao nhóm lại tồn tại, nếu nó không thể xử lý một vài triệu bản ghi đúng cách ... Nó chỉ không có ý nghĩa, phải không? là một cái gì đó chúng ta đang thiếu. Cuối cùng, câu hỏi đã thay đổi một chút và chúng tôi đang giả sử sự hiện diện của một bảng thiết bị ... Tôi muốn biết liệu thực sự có cách nào khác không
Feyd

3

Cố gắng 1

Nếu

  1. Tôi có một equipmentbảng riêng , và
  2. Tôi có một chỉ số trên geoposition_records(equipment_id, created_at desc)

Sau đó, các công việc sau đây cho tôi:

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

Tôi không thể buộc PG thực hiện một truy vấn nhanh để xác định cả danh sách equipment_ids và các liên quan max(created_at). Nhưng tôi sẽ thử lại vào ngày mai!

Cố gắng 2

Tôi đã tìm thấy liên kết này: http://zogovic.com/post/44856908222/optimizing-postgresql-query-for-distinc-values Kết hợp kỹ thuật này với truy vấn của tôi từ lần thử 1, tôi nhận được:

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;

và điều này hoạt động NHANH CHÓNG! Nhưng bạn cần

  1. mẫu truy vấn cực kỳ mâu thuẫn này và
  2. một chỉ số trên geoposition_records(equipment_id, created_at desc).
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.