是否按组选择每组中的第一行?

正如标题所示,我想选择每组行中的第一行,这些行以分组依据进行分组

具体来说,如果我有一个purchases表,如下所示:

SELECT*FROM purchases;

我的输出:

总数

id 客户
一, 五,
二, 萨莉 三,
三, 二,
四, 萨莉 一,

我想查询每个客户进行的最大一次购买(total)的id。大概是这样的:

选择第一个(id)、客户、第一个(总计)
从购买
按客户分组
按总说明排序;

预期输出:

第一个(id) 客户 第一(总计)
一, 五,
二, 萨莉 三,

上的DISTINCT ONPostgreSQL中通常是最简单和最快的
(有关特定工作负载的性能优化,请参见下文)。

在上选择不同的(客户)
id、客户、总计
从购买
客户订单,总描述,id

或更短(如果不清楚)的输出列序号:

在(2)上选择不同
id、客户、总计
从购买
按2、3、1顺序排列;

如果total可以为NULL(这两种方法都不会有任何影响,但您需要匹配现有索引):

。。。
按客户的订单,总描述上次为空,id

要点

DISTINCT ON是标准的PostgreSQL扩展(其中仅定义了整个SELECT列表的DISTINCT

DISTINCT ON子句中列出任意数量的表达式,组合行值定义重复项。手册:

显然,如果两行至少在以下方面不同,则认为它们是不同的
一列值Null值在此情况下被视为相等
比较。

我的

上的DISTINCT ON可与按顺序组合使用ORDER BY中的前导表达式必须位于DISTINCT ON中的表达式集中,但您可以在这些表达式中自由重新排列顺序。示例。
您可以将附加的表达式添加到orderby中,以从每组对等点中选择特定的行。或者,正如手册所说:

表达式上的DISTINCT必须与最左边的ORDER BY
表达式。orderby子句通常包含额外的
表达式,用于确定所需的行优先级
每个组上的每个都不同

我添加了id作为断开关系的最后一项:
&引用;从共享最高总计的每组中选择id最小的行

若要以与确定每个组的第一个排序顺序不一致的方式对结果进行排序,可以将上述查询嵌套在外部查询中,并使用另一个order BY。例如

如果total可以为NULL,则很可能需要具有最大非NULL值的行。添加上一次为空,如所示。见:

  • 按列ASC排序,但先按空值排序

选择列表不受上的DISTINCT ONORDER by中表达式的约束。(在上述简单情况下不需要):

  • 您不必按顺序中包含任何表达式

  • 您可以在选择列表中包含任何其他表达式。这有助于用子查询和聚合/窗口函数替换更复杂的查询

我用Postgres版本8.3-13进行了测试。但该功能至少从7.1版开始就存在了,所以基本上一直都存在

索引

上述查询的perfect索引将是一个多列索引,以匹配顺序跨越所有三列,并具有匹配的排序顺序:

创建采购索引\u 3c\u采购idx(客户、总描述、id);

可能太专业了。但是,如果特定查询的读取性能至关重要,请使用它。如果查询中有DESC NULLS LAST,请在索引中使用相同的值,以便排序顺序匹配且索引适用

有效性/性能优化

在为每个查询创建定制索引之前,请权衡成本和收益。上述指数的潜力很大程度上取决于数据分布

使用索引是因为它提供预排序的数据。在Postgres 9.2或更高版本中,如果索引小于基础表,则查询还可以受益于仅索引扫描。不过,索引必须全部扫描

对于每个客户很少的(列客户中的高基数),这是非常有效的。如果您仍然需要排序输出,则更是如此。随着每个客户的行数不断增加,好处也随之减少。
理想情况下,您有足够的工作\u mem来处理RAM中涉及的排序步骤,而不会溢出到磁盘。但通常将工作记忆设置得太高会产生不良影响。考虑为异常大的查询设置本地。使用EXPLAIN ANALYZE了解您需要多少。提到磁盘:"在排序步骤中,表示需要更多:

  • Linux上PostgreSQL中的配置参数work_mem
  • 使用按日期和文本排序优化简单查询

对于每个客户的多行(列客户中的基数较低),松散索引扫描(也称“跳过扫描”)将(更)高效,但在Postgres 14之前还没有实现。(Postgres 15正在开发一个仅索引扫描的实现。请参见此处和此处。)
目前,有更快的查询技术可以替代此功能。特别是如果您有一个单独的表来容纳唯一的客户,这是典型的用例。但如果你没有:

  • SELECT DISTINCT比PostgreSQL中我的表上的预期速度慢
  • 按查询优化分组以检索每个用户的最新行
  • 优化分组最大查询
  • 每行查询最后N个相关行

基准

请参阅单独的答案

发表评论