38

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

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

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

 SELECT User.Name, Account.* FROM Account, User
 WHERE Account.ID = User.Account_ID

但是我怎么可能只根据他们的 User.ID 的顺序从产品中获得第一个用户呢?

4

12 回答 12

26

与其分组,不如这样进行......

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
于 2009-04-21T16:19:22.693 回答
12

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

Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')

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

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

干得好!

编辑:我遇到了第一个公式的问题:当 DATE_FIELD 的毫秒数为 .000 时,SQL Server 将日期作为字符串返回,根本没有毫秒,因此从 DESIRED_FIELD 中删除了前 4 个字符。我只是将格式更改为“20”(没有毫秒),效果很好。唯一的缺点是如果您有两个在同一秒内创建的字段,排序可能会很混乱......在这种情况下,您可以将格式恢复为“126”。

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')

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

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
于 2012-04-11T17:37:15.693 回答
9
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

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

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

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

于 2012-02-09T22:56:48.320 回答
6

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

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

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

于 2015-10-29T12:48:52.697 回答
4

您可以使用 OUTER APPLY,请参阅文档

SELECT User1.Name, Account.* FROM Account
OUTER APPLY 
    (SELECT  TOP 1 Name 
    FROM [User]
    WHERE Account.ID = [User].Account_ID
    ORDER BY Name ASC) User1
于 2015-09-28T21:23:00.080 回答
3
SELECT (SELECT TOP 1 Name 
        FROM User 
        WHERE Account_ID = a.AccountID 
        ORDER BY UserID) [Name],
       a.*
FROM Account a
于 2009-04-21T16:19:15.190 回答
3

Dominic Goulet 的 STUFF 反应很巧妙。但是,如果您的 DATE_FIELD 是 SMALLDATETIME(而不是 DATETIME),那么 ISO 8601 长度将为 19 而不是 23(因为 SMALLDATETIME 没有毫秒) - 因此请相应地调整 STUFF 参数,否则 STUFF 函数的返回值将不正确(缺少前四个字符)。

于 2012-07-05T14:05:03.210 回答
2

Sql Server 2005 或 2008 中不存在 First 和 Last,但 Sql Server 2012 中有 First_Value, Last_Value 函数。我尝试为 Sql Server 2005 实现聚合 First 和 Last 并遇到障碍,即 sql server 确实保证以定义的顺序计算聚合。(见属性 SqlUserDefinedAggregateAttribute.IsInvariantToOrder 属性,未实现。)这可能是因为查询分析器尝试在多个线程上执行聚合的计算并组合结果,这加快了执行速度,但不保证顺序聚合了哪些元素。

于 2011-12-02T10:05:58.660 回答
1

定义“第一”。您首先想到的巧合通常与聚集索引顺序有关,但不应依赖(您可以设计破坏它的示例)。

不使用 MAX() 或 MIN() 是对的。虽然很诱人,但请考虑您的名字和姓氏在不同字段中的情况。您可能会从不同的记录中获得名称。

因为听起来你真正关心的是你得到每个组的一个任意记录,你可以做的只是 MIN 或 MAX 一个记录的 ID 字段,然后将表加入到该 ID 的查询中。

于 2009-04-21T16:20:54.093 回答
0

有很多方法可以做到这一点,这里有一种快速而肮脏的方法。

Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
    A.*
FROM Account A
于 2009-04-21T16:19:29.200 回答
0

使用返回每个帐户的第一个用户的子选择“FirstUser”创建并加入

SELECT User.Name, Account.* 
FROM Account, User, 
 (select min(user.id) id,account_id from User group by user.account_id) as firstUser
WHERE Account.ID = User.Account_ID 
 and User.id = firstUser.id and Account.ID = firstUser.account_id
于 2017-04-21T14:37:18.177 回答
0

(有点题外话,但是)我经常运行聚合查询来列出异常摘要,然后我想知道为什么客户在结果中,所以使用 MIN 和 MAX 提供 2 个半随机样本,我可以查看详细信息,例如

SELECT Customer.Id, COUNT(*) AS ProblemCount
      , MIN(Invoice.Id) AS MinInv, MAX(Invoice.Id) AS MaxInv
FROM Customer
INNER JOIN Invoice on Invoice.CustomerId = Customer.Id
WHERE Invoice.SomethingHasGoneWrong=1
GROUP BY Customer.Id
于 2017-01-09T22:38:24.093 回答