postgresql - 查找每个组的最近日期并加入所有记录的性能问题

标签 postgresql performance join group-by subquery

我有下表:

CREATE TABLE person (
  id INTEGER NOT NULL,
  name TEXT,
  CONSTRAINT person_pkey PRIMARY KEY(id)
);

INSERT INTO person ("id", "name")
VALUES 
  (1, E'Person1'),
  (2, E'Person2'),
  (3, E'Person3'),
  (4, E'Person4'),
  (5, E'Person5'),
  (6, E'Person6');

CREATE TABLE person_book (
  id INTEGER NOT NULL,
  person_id INTEGER,
  book_id INTEGER,
  receive_date DATE,
  expire_date DATE,
  CONSTRAINT person_book_pkey PRIMARY KEY(id)
);

/* Data for the 'person_book' table  (Records 1 - 9) */

INSERT INTO person_book ("id", "person_id", "book_id", "receive_date", "expire_date")
VALUES 
  (1, 1,  1, E'2016-01-18', NULL),
  (2, 1,  2, E'2016-02-18', E'2016-10-18'),
  (3, 1,  4, E'2016-03-18', E'2016-12-18'),
  (4, 2,  3, E'2017-02-18', NULL),
  (5, 3,  5, E'2015-02-18', E'2016-02-23'),
  (6, 4, 34, E'2016-12-18', E'2018-02-18'),
  (7, 5, 56, E'2016-12-28', NULL),
  (8, 5, 34, E'2018-01-19', E'2018-10-09'),
  (9, 5, 57, E'2018-06-09', E'2018-10-09');

CREATE TABLE book (
  id INTEGER NOT NULL,
  type TEXT,
  CONSTRAINT book_pkey PRIMARY KEY(id)
) ;

/* Data for the 'book' table  (Records 1 - 8) */

INSERT INTO book ("id", "type")
VALUES 
  ( 1, E'Btype1'),
  ( 2, E'Btype2'),
  ( 3, E'Btype3'),
  ( 4, E'Btype4'),
  ( 5, E'Btype5'),
  (34, E'Btype34'),
  (56, E'Btype56'),
  (67, E'Btype67');

我的查询应该列出所有人的姓名,对于最近收到的书籍类型为 (book_id IN (2, 4, 34, 56, 67)) 的人,它应该显示书籍类型和到期日期;如果一个人没有收到这样的书类型,它应该显示空白作为书类型和到期日期。

我的查询是这样的:

SELECT p.name,
   pb.expire_date,
   b.type
   FROM 
   (SELECT p.id AS person_id, MAX(pb.receive_date) recent_date
    FROM 
        Person p
        JOIN person_book pb ON pb.person_id = p.id
    WHERE pb.book_id IN (2, 4, 34, 56, 67)
    GROUP BY p.id
   )tmp 
   JOIN person_book pb ON pb.person_id = tmp.person_id
   AND tmp.recent_date = pb.receive_date AND pb.book_id IN 
   (2, 4, 34, 56, 67)
   JOIN book b ON b.id = pb.book_id           
   RIGHT JOIN Person p ON p.id = pb.person_id 

(正确的)结果:

  name   | expire_date |  type
---------+-------------+---------
 Person1 | 2016-12-18  | Btype4
 Person2 |             |
 Person3 |             |
 Person4 | 2018-02-18  | Btype34
 Person5 | 2018-10-09  | Btype34
 Person6 |             |

查询工作正常,但由于我正在将一个小表与一个大表连接起来,所以速度很慢。有什么有效的方法可以重写这个查询吗?

我本地的PostgreSQL版本是9.3.18;但查询应该也适用于版本 8.4,因为这是我们的生产版本。

最佳答案

你的设置有问题

My local PostgreSQL version is 9.3.18; but the query should work on version 8.4 as well since that's our productions version.

在查看查询之前,这会产生两个主要问题:

  1. Postgres 8.4 太旧了。特别是对于“生产”。它已于 2014 年 7 月停产。不再进行安全升级,已经无可救药地过时了。紧急考虑升级到当前版本。

  2. 在开发和生产中使用截然不同的版本是一个装满子弹的步兵枪。未被发现的困惑和错误。我们在这里看到不止一个出于这种愚蠢行为的绝望请求。

更好的查询

这个等价物应该更简单和更快(也适用于 pg 8.4):

SELECT p.name, pb.expire_date, b.type
FROM  (
   SELECT DISTINCT ON (person_id)
          person_id, book_id, expire_date
   FROM   person_book
   WHERE  book_id IN (2, 4, 34, 56, 67)
   ORDER  BY person_id, receive_date DESC NULLS LAST
   ) pb
JOIN   book        b ON b.id = pb.book_id
RIGHT  JOIN person p ON p.id = pb.person_id;

为了优化读取性能,这个具有匹配排序顺序的部分多列索引将是完美的:

CREATE INDEX ON person_book (person_id, receive_date DESC NULLS LAST)
WHERE  book_id IN (2, 4, 34, 56, 67);

在现代 Postgres 版本(9.2 或更高版本)中,您可以将 book_id, expire_date 附加到索引列以获得仅索引扫描。见:

关于 DISTINCT ON:

关于 DESC NULLS LAST:

关于postgresql - 查找每个组的最近日期并加入所有记录的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48176932/

相关文章:

c# - 查找网络性能问题的根源

mysql - 连接来自两个不同表 MySQL 的所有行

sql - 将 ResultSet 传递给 Postgresql 函数

hibernate - grails 2.4.3 升级后不必要的数据库 changeSet 与 boolean 相关

c# - 在 ulong (C#) 中获得最后一个有效位的最快方法?

php - MySQL join 将多行匹配为单列数据

MySQL获取最新记录而不在连接中使用自动增量字段

ruby-on-rails - 强制 $ rake db :reset Despite Other Users with Postgres

java - Liquibase,如何在 Java 中以编程方式设置默认模式

javascript - 实习生 - 调试内存不足异常