聚合SQL函数以仅从每个组中获取第一个

Mat*_*att 30 t-sql sql-server-2005 aggregate-functions

我有2个表 - 一个Account表和一个Users表.每个帐户可以有多个用户.我有一个场景,我想对这两个表执行单个查询/连接,但我想要所有的帐户数据(帐户.*)和只有第一组用户数据(特别是他们的名字).

我没有在我的聚合小组上做"最小"或"最大",而是想做一个"第一次".但是,显然,TSQL中没有"First"聚合函数.

有关如何获取此查询的任何建议?显然,很容易获得Account x Users的笛卡尔积:

 SELECT User.Name, Account.* FROM Account, User
 WHERE Account.ID = User.Account_ID
Run Code Online (Sandbox Code Playgroud)

但是我怎样才能根据User.ID的顺序从产品中获取第一个用户?

Ada*_*son 25

而不是分组,像这样去做...

select
    *

from account a

join (
    select 
        account_id, 
        row_number() over (order by account_id, id) - 
            rank() over (order by account_id) as row_num from user
     ) first on first.account_id = a.id and first.row_num = 0
Run Code Online (Sandbox Code Playgroud)

  • @MikeTeeVee同意; 这是一个更好的解决方案,如果我今天解决这个问题,那就是我想出的. (4认同)
  • 我看到你在这里使用了Rank(),然后从Row_Number()中减去它,并查找0.我将使用ONLY Row_Number()(由Account_ID分区)并在Row_Num = 1上过滤.结果将是相同的(并且可能在技术上更快).请参阅@AaronLS的示例:http://stackoverflow.com/a/9220232/555798 (2认同)

Dom*_*let 9

我知道我的答案有点晚了,但这可能对其他人有所帮助.有一种方法可以在SQL Server中实现First()和Last(),这里是:

Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')
Run Code Online (Sandbox Code Playgroud)

对First()使用Min(),对Last()使用Max().DATE_FIELD应该是确定它是第一个还是最后一个记录的日期.DESIRED_FIELD是您想要第一个或最后一个值的字段.它的作用是:

  1. 在字符串的开头添加ISO格式的日期(长度为23个字符)
  2. 将DESIRED_FIELD附加到该字符串
  3. 获取该字段的MIN/MAX值(因为它从日期开始,您将获得第一个或最后一个记录)
  4. 填充串联的字符串以删除前23个字符(日期部分)

干得好!

编辑:我遇到第一个公式的问题:当DATE_FIELD以.000为毫秒时,SQL Server将日期作为字符串返回NO毫秒,从而删除DESIRED_FIELD中的前4个字符.我只是将格式更改为"20"(没有毫秒)并且它的工作非常好.唯一的缺点是如果你有两个在同一秒创建的字段,那么排序可能会很混乱......在cas中你可以恢复为"126"格式.

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')
Run Code Online (Sandbox Code Playgroud)

编辑2:我最初的意图是返回最后一个(或第一个)NON NULL行.我被问到如何返回最后一行或第一行,无论是否为null.只需将一个ISNULL添加到DESIRED_FIELD即可.当您使用+运算符连接两个字符串时,如果其中一个为NULL,则结果为NULL.所以使用以下内容:

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
Run Code Online (Sandbox Code Playgroud)


Aar*_*nLS 8

Select *
From Accounts a
Left Join (
    Select u.*, 
    row_number() over (Partition By u.AccountKey Order By u.UserKey) as Ranking
    From Users u
  ) as UsersRanked
  on UsersRanked.AccountKey = a.AccountKey and UsersRanked.Ranking = 1
Run Code Online (Sandbox Code Playgroud)

这可以通过使用Partition By子句来简化.在上面,如果一个帐户有三个用户,那么子查询将它们编号为1,2和3,对于不同的AccountKey,它将重置numnbering.这意味着对于每个唯一的AccountKey,总会有1,可能是2,3,4等.

因此,您在Ranking = 1上过滤以从每个组中获取第一个.

这将为每个帐户提供一行,如果该帐户至少有一个用户,那么它将为您提供最低密钥的用户(因为我使用左连接,即使没有,您也将始终获得帐户列表用户存在).Order By u.UserKey如果您希望按字母顺序或其他标准选择第一个用户,请替换为其他字段.


fir*_*ole 6

我已经对所有方法进行了基准测试,实现这一点的最简单、最快的方法是使用外部/交叉应用

SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u
Run Code Online (Sandbox Code Playgroud)

CROSS APPLY 的工作方式与 INNER JOIN 类似,并获取两个表相关的行,而 OUTER APPLY 的工作方式与 LEFT OUTER JOIN 类似,并从左表中获取所有行(Account here)

  • 此查询可能会给出不一致的结果。没有 SORT BY 的 SELECT TOP 1 可以返回查询的任何匹配项,这取决于 SqlServer 引擎。因此这样的结果可以给出“随机结果”。 (2认同)