正如标题所示,我想选择用GROUP BY分组的每组行中的第一行。

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

SELECT * FROM purchases;

我的输出:

id customer total
1 Joe 5
2 Sally 3
3 Joe 2
4 Sally 1

我想查询每个客户的最大购买量(总购买量)。类似于:

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

预期输出:

FIRST(id) customer FIRST(total)
1 Joe 5
2 Sally 3

当前回答

查询:

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),其中它返回一个给定名称和id的值为了证明给定的总数(T(customer,id))是最高的,我们必须证明我们想证明

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

OR

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

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

第二个将需要一个聪明的方式来表示,没有比这个更高的记录了。


返回SQL

如果我们在表的名称和总数小于连接表的情况下留下连接表:

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 |      |        |         |
+--------------+----------------+-----------------+------+--------+---------+

这就是我们需要的答案。

其他回答

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

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

解释:这里,分组依据是根据客户进行的,然后按总数进行排序,然后给每个这样的组指定序列号为StRank,我们将选出第一个StRank为1的客户

对于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

不要忘记为使用过的列创建聚集索引

查询:

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),其中它返回一个给定名称和id的值为了证明给定的总数(T(customer,id))是最高的,我们必须证明我们想证明

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

OR

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

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

第二个将需要一个聪明的方式来表示,没有比这个更高的记录了。


返回SQL

如果我们在表的名称和总数小于连接表的情况下留下连接表:

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 |      |        |         |
+--------------+----------------+-----------------+------+--------+---------+

这就是我们需要的答案。

在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一起工作。arrayagg允许您指定仅限于自身的排序,因此它不会约束整个查询的结构。如果您需要执行与默认值不同的操作,还可以使用语法对NULL进行排序。一旦我们构建了数组,我们就使用第一个元素。(Postgres数组是1索引的,而不是0索引的)。您可以以类似的方式对第三个输出列使用array_agg,但max(total)更简单。与DISTINCT ON不同,使用array_agg可以保留GROUP BY,以防出于其他原因需要。

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

请注意,MySQL手册中可以意外地找到解决这个常见问题的一系列解决方案——尽管您的问题是在Postgres中,而不是MySQL中,但给出的解决方案应该适用于大多数SQL变体。请参见常见查询的示例::保持某一列的组最大值的行。