选择每个 GROUP BY 组中的第一行?

Select first row in each GROUP BY group?

提问人:David Wolever 提问时间:9/27/2010 最后编辑:Erwin BrandstetterDavid Wolever 更新时间:10/30/2023 访问量:1744893

问:

我想选择使用 .GROUP BY

具体来说,如果我有一个看起来像这样的表:purchases

SELECT * FROM purchases;

我的输出:

编号 客户
1 5
2 莎莉 3
3 2
4 莎莉 1

我想查询每个 () 的最大购买量 ()。像这样的东西:idtotalcustomer

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

预期输出:

FIRST(同上) 客户 FIRST(合计)
1 5
2 莎莉 3
SQL PostgreSQL 每组 Greatest-n-Per-Group

评论

0赞 phil294 10/19/2019
既然你只在寻找每一个最大的,为什么不查询呢?MAX(total)
38赞 gwideman 2/7/2020
@phil294查询 max(total) 不会将该总数与发生该总数的行的“id”值相关联。
2赞 mafu 4/24/2021
这回答了你的问题吗?如何在 SQL 查询中选择每个组的第一行?
0赞 jdhao 9/30/2022
stackoverflow.com/q/121387/6064933 相关或重复

答:

1489赞 OMG Ponies 9/27/2010 #1

支持 CTE 和窗口函数的数据库上:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rank
      FROM PURCHASES p)
 SELECT *
   FROM summary
 WHERE rank = 1

任何数据库都支持:

但是你需要添加逻辑来打破联系:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

评论

69赞 Sam 10/2/2014
ROW_NUMBER() OVER(PARTITION BY [...])以及其他一些优化帮助我将查询时间从 30 秒缩短到几毫秒。谢谢!(PostgreSQL 9.2)
1赞 Solomon Tesfaye 7/13/2022
ROW_NUMBER() OVER(PARTITION 满足了我的需求,但是有没有办法将行号限制为组中的 1 个以减小视图的大小?
0赞 hemp 6/20/2023
@SolomonTesfaye 使用子查询,并在视图中针对子查询进行指定。WHERE row_number = 1
0赞 Solomon Tesfaye 6/20/2023
@hemp 摘要视图已经有大数据,我必须使用 .但我首先要问的是,有没有办法减少视野。where rank=1
0赞 hemp 6/20/2023
@SolomonTesfaye 您可以使用其他方法,例如 DISTINCT ON。
1734赞 Erwin Brandstetter 10/3/2011 #2

PostgreSQL 中,DISTINCT ON 通常是最简单和最快的。
(有关某些工作负载的性能优化,请参阅下文。

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

或者更短(如果不是那么清晰)的输出列序数:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

如果可以,则添加:totalnullNULLS LAST

...
ORDER  BY customer, total DESC NULLS LAST, id;

无论哪种方式都有效,但您需要匹配现有索引

db<>fiddle 在这里

要点

DISTINCT ON 是该标准的 PostgreSQL 扩展,其中仅定义了整个列表。DISTINCTSELECT

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

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

大胆强调我的。

DISTINCT ON可与 ORDER BY 结合使用。中的前导表达式必须位于 中的表达式集中,但您可以自由地重新排列这些表达式之间的顺序。例。
您可以添加其他表达式,以便从每组对等节点中选取特定行。或者,正如手册所说
ORDER BYDISTINCT ONORDER BY

表达式必须与最左边的表达式匹配。该条款通常包含额外的 用于确定 内行所需优先级的表达式 每个组。DISTINCT ONORDER BYORDER BYDISTINCT ON

我添加了最后一项来打破联系:“
从每个组中选择具有最小 id 的行,共享最高的总数
id

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

如果可以,您很可能希望具有最大非 null 值的行。添加 NULLS LAST,如所示。看:totalnull

SELECT 列表不受表达式的约束,也不受以下任何方式的约束:DISTINCT ONORDER BY

  • 不必在 或 中包含任何表达式。DISTINCT ONORDER BY

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

我使用 Postgres 版本 8.3 – 16 进行了测试。但是该功能至少从 7.1 版本开始就已经存在了,所以基本上总是如此。

指数

上述查询的完美索引是跨所有三列的多列索引,按匹配的顺序和匹配的排序顺序:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

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

有效性/性能优化

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

之所以使用索引,是因为它提供了预先排序的数据。在 Postgres 9.2 或更高版本中,如果索引小于基础表,则查询也可以从仅索引扫描中受益。但是,必须对索引进行整体扫描。例。

对于每个客户的(列中的高基数),这是非常有效的。如果您无论如何都需要排序输出,则更是如此。随着每个客户的行数不断增加,好处会缩小。
理想情况下,您有足够的work_mem来处理 RAM 中涉及的排序步骤,而不会溢出到磁盘。但一般来说,设置得太高会产生不利影响。考虑异常大的查询。使用 .在排序步骤中提及“Disk:”表示需要更多:
customerwork_memSET LOCALEXPLAIN ANALYZE

对于每个客户的(列中的低基数),“索引跳过扫描”或“松散索引扫描”将(远)更有效。但这并没有在 Postgres 16 之前实现。多年来,以一种或另一种方式实施它的认真工作一直在进行,但迄今为止尚未成功。请参阅此处和此处
目前,有更快的查询技术可以替代这一点。特别是,如果您有一个单独的表来容纳唯一客户,这是典型的用例。但是,如果您不这样做:
customer

基准

请参阅单独的答案。

评论

0赞 zoltankundi 1/12/2023
可悲的是,如果你想通过不同的逻辑进行排序和区分,DISTINCT ON是没有用的,你必须使用子查询
0赞 Erwin Brandstetter 1/14/2023
@zoltankundi:为什么子查询会变得无用?我猜是关于这样的案例?stackoverflow.com/a/9796104/939860DISTINCT ON
0赞 zoltankundi 1/16/2023
我并不是说子查询使它毫无用处,只是您必须使用子查询,如果您可以执行 DISTINCT ON 而不必按同一列排序,那就太好了
2赞 Erwin Brandstetter 1/16/2023
您可以在没有 .只是没有矛盾.为此,您需要一个子查询。DISTINCT ONORDER BYORDER BY
18赞 cosmos 6/18/2013 #3

正如 Erwin 所指出的那样,由于存在 SubQ,该解决方案不是很有效

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;
71赞 Tomas 6/27/2013 #4

这是常见的 个问题,它已经有经过充分测试和高度优化的解决方案。就我个人而言,我更喜欢 Bill Karwin 的左联接解决方案(原始帖子中有很多其他解决方案)。

请注意,令人惊讶的是,在MySQL手册中可以找到许多解决此常见问题的解决方案 - 即使您的问题在Postgres中,而不是MySQL中,给出的解决方案应该适用于大多数SQL变体。请参阅常见查询示例:: 包含特定列的按组最大值的行

评论

32赞 Erwin Brandstetter 7/9/2013
MySQL手册如何以任何方式“正式”解决Postgres / SQLite(更不用说SQL)问题?此外,需要明确的是,该版本更短、更简单,并且在 Postgres 中通常比具有 self 或半反连接的替代方案更好。它也“经过充分测试”。DISTINCT ONLEFT JOINNOT EXISTS
0赞 Timo 8/15/2023
正如在上述“左联接”解决方案下所评论的那样,请注意,自联接会导致组大小中的性能二次方,因此它们不适合组可能较大的情况。有关更多详细信息,请参阅该答案下有关自加入的评论。
11赞 Alejandro Salamanca Mazuelo 4/9/2014 #5

非常快速的解决方案

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

如果表按 ID 索引,则速度非常快:

create index purchases_id on purchases (id);
47赞 Paul A Jungwirth 8/28/2014 #6

在 Postgres 中,你可以这样使用:array_agg

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

这将为您提供每个客户的最大购买量。id

需要注意的一些事项:

  • array_agg是一个聚合函数,因此它适用于 .GROUP BY
  • array_agg允许您指定范围限定为自身的排序,因此它不会约束整个查询的结构。如果需要执行与默认值不同的操作,还可以使用语法对 NULL 进行排序。
  • 一旦我们构建了数组,我们就取第一个元素。(Postgres 数组是 1 索引的,而不是 0 索引的)。
  • 您可以以类似的方式用于第三个输出列,但更简单。array_aggmax(total)
  • 与 不同,using 可以让你保留你的 ,以防你出于其他原因想要它。DISTINCT ONarray_aggGROUP BY
12赞 matiu 3/10/2015 #7

我使用这种方式(仅限postgresql):https://wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

那么你的示例应该几乎按原样工作:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

注意:它忽略 NULL 行


编辑 1 - 改用 postgres 扩展

现在我用这种方式:http://pgxn.org/dist/first_last_agg/

要在 ubuntu 上安装 14.04:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

它是一个 postgres 扩展,为您提供第一个和最后一个函数;显然比上述方式更快。


编辑 2 - 排序和筛选

如果使用聚合函数(如下所示),则可以对结果进行排序,而无需对数据进行排序:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

因此,排序的等效示例是这样的:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

当然,您可以根据自己认为适合的聚合进行排序和筛选;这是非常强大的语法。

279赞 Erwin Brandstetter 1/11/2016 #8

基准

我测试了最有趣的候选人:

  • 最初使用 Postgres 9.49.5
  • 稍后为 Postgres 13 添加了重音测试。

基本测试设置

主表: :purchases

CREATE TABLE purchases (
  id          serial  -- PK constraint added below
, customer_id int     -- REFERENCES customer
, total       int     -- could be amount of money in Cent
, some_column text    -- to make the row bigger, more realistic
);

虚拟数据(带有一些死元组)、PK、索引:

INSERT INTO purchases (customer_id, total, some_column)    -- 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k distinct customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9;  -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);

VACUUM ANALYZE purchases;

customertable - 用于优化查询:

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

在我对 9.5 的第二次测试中,我使用了相同的设置,但使用 100000 个不同的设置来获得每 .customer_idcustomer_id

表的对象大小purchases

基本设置:200k 行,10k 不同行,每个客户平均 20 行。
对于 Postgres 9.5,我添加了第 2 个测试,其中包含 86446 个不同的客户 - 每个客户平均 2.3 行。
purchasescustomer_id

使用从此处获取的查询生成:

为 Postgres 9.5 收集:

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

查询

1. 在 CTE 中,(见其他答案row_number())

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

2. 在子查询中(我的优化)row_number()

SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3.(见其他答案DISTINCT ON)

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4. 带有子查询的 rCTE(请参阅此处LATERAL)

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5.表与(见这里customerLATERAL)

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. 与(见其他答案array_agg()ORDER BY)

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

结果

使用 EXPLAIN (ANALYZE、TIMING OFF、COSTS OFF、BEST OF 5 RUNS)的上述查询的执行时间,以便与热缓存进行比较。

所有查询都使用“仅索引扫描”(以及其他步骤)。有些只是从较小的指数中获益,而另一些则更有效。purchases2_3c_idx

A. Postgres 9.4,具有 200k 行和 ~ 20 个customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  -- !
5.  37.679 ms  -- winner
6. 189.495 ms

B. 与 Postgres 9.5 的 A 相同

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  -- !
5.  33.944 ms  -- winner
6. 211.540 ms  

C. 与 B. 相同,但每行 ~ 2.3 行customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

在 2021-08-11 使用 Postgres 13 重新测试

简化的测试设置:没有删除的行,因为对于简单的情况,可以完全清理表格。VACUUM ANALYZE

Postgres 的重要更改:

  • 一般性能改进。
  • 从 Postgres 12 开始,CTE 可以内联,因此查询 1。和 2.现在执行基本相同的(相同的查询计划)。

D. 喜欢 B. ~ 每customer_id 20 行

1. 103 ms
2. 103 ms  
3.  23 ms  -- winner  
4.  71 ms  
5.  22 ms  -- winner
6.  81 ms  

db<>fiddle 在这里

E. Like C. ~ 2.3 行/customer_id

1. 127 ms
2. 126 ms  
3.  36 ms  -- winner  
4. 620 ms  
5. 145 ms
6. 203 ms  

db<>fiddle 在这里

使用 Postgres 13 进行重音测试

1M 行,每个客户 10.000 行 vs. 100 行 vs. 1.6 行。

F. 每个客户 ~ 10.000 行

1. 526 ms
2. 527 ms  
3. 127 ms
4.   2 ms  -- winner !
5.   1 ms  -- winner !
6. 356 ms  

db<>fiddle 在这里

G. 每个客户 ~ 100 行

1. 535 ms
2. 529 ms  
3. 132 ms
4. 108 ms  -- !
5.  71 ms  -- winner
6. 376 ms  

db<>fiddle 在这里

H. 每个客户 ~ 1.6 行

1.  691 ms
2.  684 ms  
3.  234 ms  -- winner
4. 4669 ms
5. 1089 ms
6. 1264 ms  

db<>fiddle 在这里

结论

  • DISTINCT ON有效地使用索引,并且通常在每组行中表现最佳。即使每组有很多行,它的表现也不错。

  • 对于每组的多行,使用 rCTE 模拟索引跳过扫描效果最佳 - 仅次于具有单独查找表(如果可用)的查询技术。

  • 目前公认的答案中演示的 row_number() 技术从未赢得任何性能测试。不是那时,不是现在。它甚至永远不会接近 ,即使数据分布对后者不利。唯一的好处是:它的扩展性并不高,只是平庸。DISTINCT ONrow_number()

更多基准测试

Postgres 11.5 上以 10M 行和 60k 唯一“客户”的“ogr”进行基准测试。结果与我们目前所看到的一致:

2011 年的原始(过时)基准

我使用 PostgreSQL 9.1 对一个包含 65579 行的真实表运行了三个测试,并在所涉及的三列中的每一列上运行了单列 btree 索引,并花费了 5 次运行的最佳执行时间
将@OMGPonies的第一个查询 (A) 与上述 DISTINCT ON 解决方案B) 进行比较:

  1. 选择整个表,在本例中为 5958 行。
A: 567.218 ms
B: 386.673 ms
  1. 使用条件产生 1000 行。WHERE customer BETWEEN x AND y
A: 249.136 ms
B:  55.111 ms
  1. 选择具有 的单个客户。WHERE customer = x
A:   0.143 ms
B:   0.072 ms

用另一个答案中描述的指数重复相同的测试:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

评论

1赞 Adithya Sama 11/2/2021
你能把自定义聚合方法添加到基准测试中吗?类似于“从按客户划分的采购组中选择优先(按 ID 划分的采购订单)”wiki.postgresql.org/wiki/First/last_(聚合)
0赞 na_ka_na 2/1/2022
是的,我们可以在基准测试中按查询添加组。我知道在重复的情况下并不完全相同。但它可能在很多用例(如时间戳)中,这是人们想到的第一个解决方案: SELECT id, customer_id, total FROM purchases a JOIN ( SELECT customer_id, MAX(total) AS total GROUP BY customer_id ) b ON a.customer_id = b.customer AND a.total = b.total
5赞 Johnny Wong 1/4/2017 #9

从我的测试中,公认的 OMG Ponies 的“受任何数据库支持”解决方案的速度很好。

在这里,我提供了一个相同的方法,但更完整、更干净的任意数据库解决方案。考虑平局(假设希望每个客户只获得一行,甚至每个客户的最大总数获得多条记录),并且将为购买表中的实际匹配行选择其他购买字段(例如purchase_payment_id)。

任何数据库都支持:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

此查询速度相当快,尤其是当购买表上有复合索引(如 (customer, total) 时。

备注:

  1. t1、t2 是子查询别名,可以根据数据库删除。

  2. 注意:截至 2017 年 1 月的编辑,MS-SQL 和 Oracle db 目前不支持该子句。您必须自己将其扩展为例如 等。USING 语法适用于 SQLite、MySQL 和 PostgreSQL。using (...)on t2.id = purchase.id

20赞 khaled_gomaa 3/25/2018 #10

查询:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

这是怎么回事!(我去过那里)

我们希望确保每次购买的总额最高。


一些理论上的东西(如果你只想理解查询,请跳过这一部分)

设 Total 是一个函数 T(customer,id),它返回给定 name 和 id 的值 为了证明给定的总数 (T(customer,id)) 是最高的,我们必须证明 我们想证明

  • ∀x T(customer,id) > T(customer,x)(此总数高于所有其他总数 该客户的总数)

  • ¬∃x T(customer, id) < T(customer, x) (不存在更高的总数 该客户)

第一种方法需要我们获取该名称的所有记录,我不太喜欢。

第二个需要一种聪明的方式来说明没有比这更高的记录了。


返回 SQL

如果我们离开 join,则名称和总数小于 join 表:

LEFT JOIN purchases as p 
ON 
p.customer = purchases.customer 
AND 
purchases.total < p.total

我们确保所有记录都具有同一用户的另一条记录,并且总数较高,以便加入:

+--------------+---------------------+-----------------+------+------------+---------+
| purchases.id |  purchases.customer | purchases.total | p.id | p.customer | p.total |
+--------------+---------------------+-----------------+------+------------+---------+
|            1 | Tom                 |             200 |    2 | Tom        |     300 |
|            2 | Tom                 |             300 |      |            |         |
|            3 | Bob                 |             400 |    4 | Bob        |     500 |
|            4 | Bob                 |             500 |      |            |         |
|            5 | Alice               |             600 |    6 | Alice      |     700 |
|            6 | Alice               |             700 |      |            |         |
+--------------+---------------------+-----------------+------+------------+---------+

这将有助于我们筛选出每次购买的最高总额,而无需分组:

WHERE p.total IS NULL
    
+--------------+----------------+-----------------+------+--------+---------+
| purchases.id | purchases.name | purchases.total | p.id | p.name | p.total |
+--------------+----------------+-----------------+------+--------+---------+
|            2 | Tom            |             300 |      |        |         |
|            4 | Bob            |             500 |      |        |         |
|            6 | Alice          |             700 |      |        |         |
+--------------+----------------+-----------------+------+--------+---------+

这就是我们需要的答案。

评论

1赞 kdmitry 7/18/2023
非常简洁的解决方案。我很好奇它与其他产品相比的性能如何。即使它不是最好的,它仍然是一个有趣的问题。在当前版本的MariaDB中,我没有LATERAL,DISTINCT ON和ARRAY_AGG(),因此我只能在此解决方案和ROW_NUMBER()之间进行选择
1赞 kdmitry 8/28/2023
我做了一些测试,似乎 ROW_NUMBER() 解决方案在我的情况下具有更好的性能。简而言之:500 万条记录,这个解决方案 - 5 m 24 s,ROW_NUMBER() 解决方案 - 1 m 40 s。添加索引后,差异更大:该解 - 59.5 s,ROW_NUMBER() 解 - 9.5 s。请记住,您的里程可能会有所不同
3赞 Eugen Konkov 9/28/2018 #11
  • 如果要从聚合行集中选择任何(根据某些特定条件)行。

  • 如果要使用除 之外的其他 () 聚合函数。因此,您不能将线索与sum/avgmax/minDISTINCT ON

您可以使用下一个子查询:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

您可以替换为所需的任何条件,但有一个限制:此子查询不得返回多行amount = MAX( tf.amount )

但是,如果你想做这样的事情,你可能会寻找窗口函数

11赞 Diwas Poudel 12/30/2018 #12

在 SQL Server 中,可以执行以下操作:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

解释:这里的分组是根据客户完成的,然后按总数排序,然后每个这样的组都被赋予序列号作为 StRank,我们取出前 1 个 StRank 为 1 的客户

3赞 BazSTR 1/18/2019 #13

对于 SQl Server,最有效的方法是:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

不要忘记为已用列创建聚集索引

12赞 Valentin Podkamennyi 4/5/2019 #14

使用 PostgreSQL、U-SQLIBM DB2Google BigQuery SQL 的函数:ARRAY_AGG

SELECT customer, (ARRAY_AGG(id ORDER BY total DESC))[1], MAX(total)
FROM purchases
GROUP BY customer
6赞 Lukasz Szozda 11/18/2019 #15

Snowflake/Teradata 支持 QUALIFY 子句,其工作方式与窗口函数类似:HAVING

SELECT id, customer, total
FROM PURCHASES
QUALIFY ROW_NUMBER() OVER(PARTITION BY p.customer ORDER BY p.total DESC) = 1
7赞 user8870331 12/9/2019 #16

在PostgreSQL中,另一种可能性是将first_value窗口函数与以下功能结合使用:SELECT DISTINCT

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

我创建了一个复合,因此这两个值都由同一个聚合返回。当然,您始终可以申请两次。(id, total)first_value()

6赞 uncle bob 7/17/2020 #17

这样它对我有用:

SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article
              GROUP BY s2.article)
ORDER BY article;

选择每件商品的最高价格

0赞 Eugen Konkov 5/13/2021 #18

我通过窗口函数 dbfiddle 的方法:

  1. 在每个组分配:row_numberrow_number() over (partition by agreement_id, order_id ) as nrow
  2. 仅取组第一排:filter (where nrow = 1)
with intermediate as (select 
 *,
 row_number() over ( partition by agreement_id, order_id ) as nrow,
 (sum( suma ) over ( partition by agreement_id, order_id ))::numeric( 10, 2) as order_suma,
from <your table>)

select 
  *,
  sum( order_suma ) filter (where nrow = 1) over (partition by agreement_id)
from intermediate
0赞 Salman Sabir 12/16/2021 #19

这可以通过 MAX FUNCTION 对 total 和 GROUP BY id 和 customer 轻松实现。

SELECT id, customer, MAX(total) FROM  purchases GROUP BY id, customer
ORDER BY total DESC;

评论

2赞 Erwin Brandstetter 12/17/2021
这不符合 OP 的要求。
2赞 Sergey Shcherbakov 7/26/2022
如果我们知道该组总是包含相同的值,或者如果我们不关心从该组中选择哪一个,为什么不呢?在许多情况下,这是最好的解决方案(只需要“排序依据”)
1赞 bfontaine 8/29/2023
“或者我们是否不在乎从小组中选择哪一个”,但我们确实在乎,因此问题来了。
6赞 PraveenP 2/7/2022 #20

这就是我们如何使用 windows 函数来实现这一点:

    create table purchases (id int4, customer varchar(10), total integer);
    insert into purchases values (1, 'Joe', 5);
    insert into purchases values (2, 'Sally', 3);
    insert into purchases values (3, 'Joe', 2);
    insert into purchases values (4, 'Sally', 1);
    
    select ID, CUSTOMER, TOTAL from (
    select ID, CUSTOMER, TOTAL,
    row_number () over (partition by CUSTOMER order by TOTAL desc) RN
    from purchases) A where RN = 1;

enter image description here

0赞 Programmer 9/15/2023 #21

您可以使用 CTE(公用表表达式)获取每个组中的第一行,下面是示例示例

with cte as (SELECT t1.*
FROM table_one t1
INNER JOIN (
    SELECT id,MAX(date) AS max_date
    FROM table1
    GROUP BY id
) t2 ON t1.id = t2.id AND t1.max_date= t2.date)

谢谢

评论

1赞 Community 9/19/2023
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。