我有2个表 - 一个Account表和一个Users表。每个帐户可以拥有多个用户。我有一个场景,我想对这两个表执行单个查询/连接,但我想要所有帐户数据(帐户。*)和只有第一个用户数据集(特别是他们的名字) )。
我没有在我的聚合群体上做“最小”或“最大”,而是想做“第一次”。但是,显然,TSQL中没有“第一”聚合函数。
有关如何获取此查询的任何建议?显然,很容易获得Account x Users的笛卡尔积:
SELECT User.Name, Account.* FROM Account, User
WHERE Account.ID = User.Account_ID
但是我怎样才能根据User.ID的顺序从产品中获取第一个用户?
答案 0 :(得分: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
答案 1 :(得分:9)
我知道我的答案有点晚,但这可能对其他人有所帮助。有一种方法可以在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是您想要第一个或最后一个值的字段。它的作用是:
你走了!
编辑:我遇到第一个公式的问题:当DATE_FIELD以.000为毫秒时,SQL Server将日期作为字符串返回NO毫秒,从而从DESIRED_FIELD中删除前4个字符。我只是将格式更改为“20”(没有毫秒)并且它的工作非常好。唯一的缺点是如果你有两个在同一秒创建的字段,那么排序可能会很混乱......在这种情况下你可以恢复为“126”格式。Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')
编辑2:我最初的意图是返回最后一个(或第一个)NON NULL行。我被问到如何返回最后一行或第一行,无论是否为null。只需将一个ISNULL添加到DESIRED_FIELD即可。当您使用+运算符连接两个字符串时,如果其中一个为NULL,则结果为NULL。请使用以下内容:
Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
答案 2 :(得分:7)
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,它将重置numnbering。这意味着对于每个唯一的AccountKey,总会有1,可能是2,3,4等。
因此,您在Ranking = 1上过滤以从每个组中获取第一个。
这将为每个帐户提供一行,如果该帐户至少有一个用户,那么它将为您提供最低密钥的用户(因为我使用左连接,您将始终获得一个帐户列表即使没有用户存在)。如果您希望按字母顺序或其他标准选择第一个用户,请将Order By u.UserKey
替换为其他字段。
答案 3 :(得分:3)
来自Dominic Goulet的STUFF回应很光滑。但是,如果您的DATE_FIELD是SMALLDATETIME(而不是DATETIME),则ISO 8601长度将为19而不是23(因为SMALLDATETIME没有毫秒) - 因此相应地调整STUFF参数或者STUFF函数的返回值将不正确(缺少前四个字符。)
答案 4 :(得分:2)
在Sql Server 2005或2008中不存在First和Last,但在Sql Server 2012中有一个First_Value,Last_Value函数。我试图实现Sql Server 2005的聚合First和Last,并且遇到了sql server确保以定义的顺序计算聚合的障碍。 (请参阅属性SqlUserDefinedAggregateAttribute.IsInvariantToOrder属性,但未实现。)这可能是因为查询分析器尝试在多个线程上执行聚合计算并合并结果,从而加快执行速度,但不保证订单哪些元素是聚合的。
答案 5 :(得分:2)
您可以使用外部申请,请参阅documentation。
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
答案 6 :(得分:2)
我已经对所有方法进行了基准测试,最简单,最快捷的方法就是使用外部/交叉应用
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)
答案 7 :(得分:1)
SELECT (SELECT TOP 1 Name
FROM User
WHERE Account_ID = a.AccountID
ORDER BY UserID) [Name],
a.*
FROM Account a
答案 8 :(得分:0)
有很多方法可以做到这一点,这里有一个快速而肮脏的方法。
Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
A.*
FROM Account A
答案 9 :(得分:0)
定义“第一”。你首先想到的是巧合,通常与聚集索引顺序有关但不应该依赖(你可以设想破坏它的例子)。
你不使用MAX()或MIN()。虽然诱人,但请考虑您的名字和姓氏位于不同字段中的情况。您可能会从不同的记录中获取名称。
因为听起来您真正关心的是每个组只能获得一条任意记录,您可以做的只是MIN或MAX该记录的ID字段,然后将表加入到该ID的查询中
答案 10 :(得分: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
答案 11 :(得分: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