如何在“分组依据”查询中仅为每个组选择一个完整行?

时间:2010-06-21 18:32:42

标签: sql tsql group-by

我有一个(似乎是)非常简单的问题,但在搜索了几个小时之后,我找不到任何有用的东西。

问题在于:

在Microsoft SQL中,我有一个表,其中 A 列存储了一些数据。此数据可以包含重复项(即,两列或更多行对于列 A 具有相同的值)。

我可以轻松找到重复项by doing

select A, count(A) as CountDuplicates
from TableName
group by A having (count(A) > 1)

现在,我想检索其他列的值,例如 B C 。当然,即使对于共享相同 A 值的行, B C 值也可能不同,但对我来说无关紧要。我只想要任何 B 值和任何 C 值,第一个,最后一个或随机值。

如果我有一个小桌子和一两列要检索,我会做类似的事情:

select A, count(A) as CountDuplicates, (
    select top 1 child.B from TableName as child where child.A = base.A) as B
)
from TableName as base group by A having (count(A) > 1)

问题是我有更多的行可以获得,并且表格非常大,因此选择几个孩子会有很高的性能成本。

那么,是否有一个不那么难看的纯SQL解决方案呢?


不确定我的问题是否足够清楚,因此我根据 AdventureWorks 数据库给出了一个示例。假设我想列出可用的州,并为每个州提供其代码,城市(任何城市)和地址(任何地址)。最简单,最低效的方法是:

var q = from c in data.StateProvinces select new { c.StateProvinceCode, c.Addresses.First().City, c.Addresses.First().AddressLine1 };

在LINQ-to-SQL中,将为181个状态中的每一个执行两次选择,因此363选择。我的情况是,我正在寻找一种方法,最多可以选择182个。

3 个答案:

答案 0 :(得分:11)

CTE中的ROW_NUMBER功能是实现此目的的方法。例如:

DECLARE @mytab TABLE (A INT, B INT, C INT)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 1, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 1, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 2, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (1, 3, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (2, 2, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 1)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 2)
INSERT INTO @mytab ( A, B, C ) VALUES (3, 3, 3)
;WITH numbered AS 
(
    SELECT *, rn=ROW_NUMBER() OVER (PARTITION BY A ORDER BY B, C)
        FROM @mytab AS m
)
SELECT *
    FROM numbered
    WHERE rn=1

正如我在对HLGEM和Philip Kelley的评论中提到的,他们对聚合函数的简单使用并不一定会为每个A组返回一个“可靠”记录;相反,它可以从许多单独的行返回列值,所有行都拼接在一起,好像它们是单个记录一样。例如,如果这是一个PERSON表,PersonID是“A”列,并且不同的联系人记录(例如,Home和Word),您最终可能会返回该人的本地城市,但他们的办公室邮政编码 - 以及这显然是在寻找麻烦。

在这里使用ROW_NUMBER与CTE一起使用起初有点困难,因为语法很笨拙。但它已经成为一种非常常见的模式,所以最好去了解它。

在我的示例中,我定义了一个CTE,它在表格中添加了一个额外的列rn(代表“行号”),它本身按A列分组。对该结果的SELECT,仅过滤到行号为1的那些(即,为该值的A找到的第一个记录),为每个A组返回一个“可靠”记录 - 在上面的示例中,你肯定会得到Work Home地址,但不能将两者的元素混合在一起。

答案 1 :(得分:5)

我担心你想要字段b和c的任何旧值。如果它们毫无意义,你为什么要归还它们呢?

如果真的没关系(我真的无法想象我会想要这个的情况,但这就是你所说的)而且b和c的值甚至不必来自同一个记录,分组使用mon或max是要走的路。如果你想要所有字段的特定记录的值,那就更复杂了。

select A, count(A) as CountDuplicates, min(B) as B , min(C) as C
from TableName as base 
group by A 
having (count(A) > 1) 

答案 2 :(得分:-1)

如果你的表中有id作为主键,你可以做这样的事情

select id,b,c from tablename 
inner join
(
select id, count(A) as CountDuplicates
from TableName as base group by A,id having (count(A) > 1) 
)d on tablename.id= d.id
相关问题