当前位置:  开发笔记 > 数据库 > 正文

PostgreSQL - 获取具有列的Max值的行

如何解决《PostgreSQL-获取具有列的Max值的行》经验,为你挑选了3个好方法。

我正在处理Postgres表(称为"生命"),其中包含time_stamp,usr_id,transaction_id和lives_remaining列的记录.我需要一个查询,它将为每个usr_id提供最新的lives_remaining总数

    有多个用户(不同的usr_id)

    time_stamp不是唯一标识符:有时用户事件(表中逐行)将以相同的time_stamp发生.

    trans_id仅在非常小的时间范围内是唯一的:随着时间的推移它会重复

    remaining_lives(对于给定用户)可以随时间增加或减少

例:

time_stamp|lives_remaining|usr_id|trans_id
-----------------------------------------
  07:00  |       1       |   1  |   1    
  09:00  |       4       |   2  |   2    
  10:00  |       2       |   3  |   3    
  10:00  |       1       |   2  |   4    
  11:00  |       4       |   1  |   5    
  11:00  |       3       |   1  |   6    
  13:00  |       3       |   3  |   1    

因为我需要使用每个给定的usr_id的最新数据来访问该行的其他列,所以我需要一个给出如下结果的查询:

time_stamp|lives_remaining|usr_id|trans_id
-----------------------------------------
  11:00  |       3       |   1  |   6    
  10:00  |       1       |   2  |   4    
  13:00  |       3       |   3  |   1    

如上所述,每个usr_id都可以获得或失去生命,有时这些带时间戳的事件发生得如此紧密,以至于它们具有相同的时间戳!因此,此查询将不起作用:

SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM 
      (SELECT usr_id, max(time_stamp) AS max_timestamp 
       FROM lives GROUP BY usr_id ORDER BY usr_id) a 
JOIN lives b ON a.max_timestamp = b.time_stamp

相反,我需要使用time_stamp(first)和trans_id(second)来识别正确的行.然后,我还需要将该信息从子查询传递给主查询,该查询将为相应行的其他列提供数据.这是我已经开始工作的被黑客攻击的查询:

SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM 
      (SELECT usr_id, max(time_stamp || '*' || trans_id) 
       AS max_timestamp_transid
       FROM lives GROUP BY usr_id ORDER BY usr_id) a 
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id 
ORDER BY b.usr_id

好的,这样可行,但我不喜欢它.它需要查询中的查询,自连接,在我看来,通过抓住MAX找到的具有最大时间戳和trans_id的行可以更简单.表"living"有数千万行要解析,所以我希望这个查询尽可能快速有效.我是RDBM和Postgres的新手,所以我知道我需要有效地使用正确的索引.我对如何优化有点迷茫.

我在这里找到了类似的讨论.我可以执行某种类型的Postgres,相当于Oracle分析函数吗?

有关访问聚合函数(如MAX)使用的相关列信息,创建索引以及创建更好查询的任何建议都将非常感谢!

PS您可以使用以下内容创建我的示例案例:

create TABLE lives (time_stamp timestamp, lives_remaining integer, 
                    usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);

vladr.. 82

在具有158k伪随机行的表上(usr_id均匀分布在0到10k trans_id之间,均匀分布在0到30之间),

通过下面的查询成本,我指的是Postgres基于成本的优化器的成本估算(使用Postgres的默认xxx_cost值),这是对所需I/O和CPU资源的加权函数估计; 您可以通过启动PgAdminIII并在查询上运行"查询/解释(F7)"来获取此信息,并将"查询/解释选项"设置为"分析"

Quassnoy的查询有745k成本估算(!),并在1.3秒完成(假定在一个复合索引(usr_id,trans_id,time_stamp))

Bill的查询成本估计为93k,并在2.9秒内完成(给定(usr_id,trans_id)上的复合索引)

查询#低于1具有16K成本估算,和在800ms的结束(在给定的化合物指数(usr_id,trans_id,time_stamp))

查询#低于2具有14K成本估算,和在800ms的结束(在给定的化合物功能指数(usr_id,EXTRACT(EPOCH FROM time_stamp),trans_id))

这是Postgres特有的

查询#3的下方(Postgres的8.4+)具有成本估算和完成时间相当(或更好)的查询#2(在给定(一个复合索引usr_id,time_stamp,trans_id)); 它的优点是lives只扫描一次表,如果你暂时增加(如果需要)work_mem以适应内存中的排序,它将是所有查询中最快的.

以上所有时间都包括检索完整的10k行结果集.

您的目标是最小的成本估算最短的查询执行时间,并强调估计的成本.查询执行可能显着依赖于运行时条件(例如,相关行是否已经完全缓存在内存中),而成本估算则不然.另一方面,请记住,成本估算正是估计值.

在没有负载的情况下在专用数据库上运行时获得最佳查询执行时间(例如,在开发PC上使用pgAdminIII).查询时间将根据实际机器负载/数据访问传播而在生产中变化.当一个查询稍快出现(<20%)比其它但是具有更高的成本,这将通常是明智的选择具有较高的执行时间,但成本更低.

如果您希望在运行查询时生产机器上没有内存竞争(例如,RDBMS缓存和文件系统缓存不会被并发查询和/或文件系统活动破坏)那么您获得的查询时间独立的(例如开发PC上的pgAdminIII)模式将具有代表性.如果生产系统存在争用,则查询时间将与估计的成本比率成比例地降低,因为具有较低成本的查询不依赖于高速缓存,具有较高成本的查询将反复重新访问相同的数据(触发)在没有稳定缓存的情况下额外的I/O),例如:

              cost | time (dedicated machine) |     time (under load) |
-------------------+--------------------------+-----------------------+
some query A:   5k | (all data cached)  900ms | (less i/o)     1000ms |
some query B:  50k | (all data cached)  900ms | (lots of i/o) 10000ms |

ANALYZE lives创建必要的索引后不要忘记运行一次.


查询#1

-- incrementally narrow down the result set via inner joins
--  the CBO may elect to perform one full index scan combined
--  with cascading index lookups, or as hash aggregates terminated
--  by one nested index lookup into lives - on my machine
--  the latter query plan was selected given my memory settings and
--  histogram
SELECT
  l1.*
 FROM
  lives AS l1
 INNER JOIN (
    SELECT
      usr_id,
      MAX(time_stamp) AS time_stamp_max
     FROM
      lives
     GROUP BY
      usr_id
  ) AS l2
 ON
  l1.usr_id     = l2.usr_id AND
  l1.time_stamp = l2.time_stamp_max
 INNER JOIN (
    SELECT
      usr_id,
      time_stamp,
      MAX(trans_id) AS trans_max
     FROM
      lives
     GROUP BY
      usr_id, time_stamp
  ) AS l3
 ON
  l1.usr_id     = l3.usr_id AND
  l1.time_stamp = l3.time_stamp AND
  l1.trans_id   = l3.trans_max

查询#2

-- cheat to obtain a max of the (time_stamp, trans_id) tuple in one pass
-- this results in a single table scan and one nested index lookup into lives,
--  by far the least I/O intensive operation even in case of great scarcity
--  of memory (least reliant on cache for the best performance)
SELECT
  l1.*
 FROM
  lives AS l1
 INNER JOIN (
   SELECT
     usr_id,
     MAX(ARRAY[EXTRACT(EPOCH FROM time_stamp),trans_id])
       AS compound_time_stamp
    FROM
     lives
    GROUP BY
     usr_id
  ) AS l2
ON
  l1.usr_id = l2.usr_id AND
  EXTRACT(EPOCH FROM l1.time_stamp) = l2.compound_time_stamp[1] AND
  l1.trans_id = l2.compound_time_stamp[2]

2013/01/29更新

最后,从版本8.4开始,Postgres支持窗口函数,这意味着您可以编写简单有效的内容:

查询#3

-- use Window Functions
-- performs a SINGLE scan of the table
SELECT DISTINCT ON (usr_id)
  last_value(time_stamp) OVER wnd,
  last_value(lives_remaining) OVER wnd,
  usr_id,
  last_value(trans_id) OVER wnd
 FROM lives
 WINDOW wnd AS (
   PARTITION BY usr_id ORDER BY time_stamp, trans_id
   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
 );


Marco.. 64

我会建议一个基于DISTINCT ON(见文档)的干净版本:

SELECT DISTINCT ON (usr_id)
    time_stamp,
    lives_remaining,
    usr_id,
    trans_id
FROM lives
ORDER BY usr_id, time_stamp DESC, trans_id DESC;

这是一个非常简短而合理的答案.也有很好的参考!这应该是公认的答案. (6认同)


Bill Karwin.. 7

这是另一种方法,碰巧没有使用相关的子查询或GROUP BY.我不是PostgreSQL性能调优的专家,所以我建议你尝试这个和其他人给出的解决方案,看看哪个更适合你.

SELECT l1.*
FROM lives l1 LEFT OUTER JOIN lives l2
  ON (l1.usr_id = l2.usr_id AND (l1.time_stamp < l2.time_stamp 
   OR (l1.time_stamp = l2.time_stamp AND l1.trans_id < l2.trans_id)))
WHERE l2.usr_id IS NULL
ORDER BY l1.usr_id;

我假设trans_id至少在任何给定的值上都是唯一的time_stamp.



1> vladr..:

在具有158k伪随机行的表上(usr_id均匀分布在0到10k trans_id之间,均匀分布在0到30之间),

通过下面的查询成本,我指的是Postgres基于成本的优化器的成本估算(使用Postgres的默认xxx_cost值),这是对所需I/O和CPU资源的加权函数估计; 您可以通过启动PgAdminIII并在查询上运行"查询/解释(F7)"来获取此信息,并将"查询/解释选项"设置为"分析"

Quassnoy的查询有745k成本估算(!),并在1.3秒完成(假定在一个复合索引(usr_id,trans_id,time_stamp))

Bill的查询成本估计为93k,并在2.9秒内完成(给定(usr_id,trans_id)上的复合索引)

查询#低于1具有16K成本估算,和在800ms的结束(在给定的化合物指数(usr_id,trans_id,time_stamp))

查询#低于2具有14K成本估算,和在800ms的结束(在给定的化合物功能指数(usr_id,EXTRACT(EPOCH FROM time_stamp),trans_id))

这是Postgres特有的

查询#3的下方(Postgres的8.4+)具有成本估算和完成时间相当(或更好)的查询#2(在给定(一个复合索引usr_id,time_stamp,trans_id)); 它的优点是lives只扫描一次表,如果你暂时增加(如果需要)work_mem以适应内存中的排序,它将是所有查询中最快的.

以上所有时间都包括检索完整的10k行结果集.

您的目标是最小的成本估算最短的查询执行时间,并强调估计的成本.查询执行可能显着依赖于运行时条件(例如,相关行是否已经完全缓存在内存中),而成本估算则不然.另一方面,请记住,成本估算正是估计值.

在没有负载的情况下在专用数据库上运行时获得最佳查询执行时间(例如,在开发PC上使用pgAdminIII).查询时间将根据实际机器负载/数据访问传播而在生产中变化.当一个查询稍快出现(<20%)比其它但是具有更高的成本,这将通常是明智的选择具有较高的执行时间,但成本更低.

如果您希望在运行查询时生产机器上没有内存竞争(例如,RDBMS缓存和文件系统缓存不会被并发查询和/或文件系统活动破坏)那么您获得的查询时间独立的(例如开发PC上的pgAdminIII)模式将具有代表性.如果生产系统存在争用,则查询时间将与估计的成本比率成比例地降低,因为具有较低成本的查询不依赖于高速缓存,具有较高成本的查询将反复重新访问相同的数据(触发)在没有稳定缓存的情况下额外的I/O),例如:

              cost | time (dedicated machine) |     time (under load) |
-------------------+--------------------------+-----------------------+
some query A:   5k | (all data cached)  900ms | (less i/o)     1000ms |
some query B:  50k | (all data cached)  900ms | (lots of i/o) 10000ms |

ANALYZE lives创建必要的索引后不要忘记运行一次.


查询#1

-- incrementally narrow down the result set via inner joins
--  the CBO may elect to perform one full index scan combined
--  with cascading index lookups, or as hash aggregates terminated
--  by one nested index lookup into lives - on my machine
--  the latter query plan was selected given my memory settings and
--  histogram
SELECT
  l1.*
 FROM
  lives AS l1
 INNER JOIN (
    SELECT
      usr_id,
      MAX(time_stamp) AS time_stamp_max
     FROM
      lives
     GROUP BY
      usr_id
  ) AS l2
 ON
  l1.usr_id     = l2.usr_id AND
  l1.time_stamp = l2.time_stamp_max
 INNER JOIN (
    SELECT
      usr_id,
      time_stamp,
      MAX(trans_id) AS trans_max
     FROM
      lives
     GROUP BY
      usr_id, time_stamp
  ) AS l3
 ON
  l1.usr_id     = l3.usr_id AND
  l1.time_stamp = l3.time_stamp AND
  l1.trans_id   = l3.trans_max

查询#2

-- cheat to obtain a max of the (time_stamp, trans_id) tuple in one pass
-- this results in a single table scan and one nested index lookup into lives,
--  by far the least I/O intensive operation even in case of great scarcity
--  of memory (least reliant on cache for the best performance)
SELECT
  l1.*
 FROM
  lives AS l1
 INNER JOIN (
   SELECT
     usr_id,
     MAX(ARRAY[EXTRACT(EPOCH FROM time_stamp),trans_id])
       AS compound_time_stamp
    FROM
     lives
    GROUP BY
     usr_id
  ) AS l2
ON
  l1.usr_id = l2.usr_id AND
  EXTRACT(EPOCH FROM l1.time_stamp) = l2.compound_time_stamp[1] AND
  l1.trans_id = l2.compound_time_stamp[2]

2013/01/29更新

最后,从版本8.4开始,Postgres支持窗口函数,这意味着您可以编写简单有效的内容:

查询#3

-- use Window Functions
-- performs a SINGLE scan of the table
SELECT DISTINCT ON (usr_id)
  last_value(time_stamp) OVER wnd,
  last_value(lives_remaining) OVER wnd,
  usr_id,
  last_value(trans_id) OVER wnd
 FROM lives
 WINDOW wnd AS (
   PARTITION BY usr_id ORDER BY time_stamp, trans_id
   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
 );



2> Marco..:

我会建议一个基于DISTINCT ON(见文档)的干净版本:

SELECT DISTINCT ON (usr_id)
    time_stamp,
    lives_remaining,
    usr_id,
    trans_id
FROM lives
ORDER BY usr_id, time_stamp DESC, trans_id DESC;


这是一个非常简短而合理的答案.也有很好的参考!这应该是公认的答案.

3> Bill Karwin..:

这是另一种方法,碰巧没有使用相关的子查询或GROUP BY.我不是PostgreSQL性能调优的专家,所以我建议你尝试这个和其他人给出的解决方案,看看哪个更适合你.

SELECT l1.*
FROM lives l1 LEFT OUTER JOIN lives l2
  ON (l1.usr_id = l2.usr_id AND (l1.time_stamp < l2.time_stamp 
   OR (l1.time_stamp = l2.time_stamp AND l1.trans_id < l2.trans_id)))
WHERE l2.usr_id IS NULL
ORDER BY l1.usr_id;

我假设trans_id至少在任何给定的值上都是唯一的time_stamp.

推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有