找到一个更好的方法来做每组前1名

时间:2013-05-07 16:16:14

标签: sql sql-server sql-server-2005

我正在尝试查找早于value的{​​{1}} id {/ 1}}

1/1/2013

以下为我提供了解决方案,但我知道我这样做是错误的。

create table #foo
(
    id int,
    value money,
    entry_date datetime
)

insert into #foo values (1, 1.00, '1/1/2012')
insert into #foo values (1, 2.00, '2/1/2012')
insert into #foo values (1, 7.00, '1/1/2013')
insert into #foo values (2, 1.00, '1/1/2013')
insert into #foo values (2, 1.00, '2/1/2013')
insert into #foo values (3, 5.00, '3/1/2012')

由于没有任何超过1/1/2013的记录,因此未返回ID 2.

完成我想要做的事情的正确方法是什么?

5 个答案:

答案 0 :(得分:6)

您还可以使用子查询来获得结果:

select f1.id, f1.value
from #foo f1
inner join
(
  select id, max(entry_date) entry_date
  from #foo
  where entry_date < '1/1/2013'
  group by id
) f2
  on f1.id = f2.id
  and f1.entry_date = f2.entry_date;

请参阅SQL Fiddle with Demo

答案 1 :(得分:3)

为了完整性,其中:

  • id
  • 有许多符合条件的参赛作品
  • 有一种有效的方法来获取唯一ids的列表(可能通过外键引用表);和
  • 合适的索引

...使用APPLY的查询比表(或索引)的单次扫描更有效:

数据

create table foo
(
  id int not null primary key
);

create table bar
(
  id int not null references foo,
  value money not null,
  entry_date datetime not null,

  primary key (id, entry_date)
);

insert foo (id) 
values (1), (2), (3), (4);

insert bar
  (id, value, entry_date)
values
  (1, 1.00, '1/1/2012'),
  (1, 2.00, '2/1/2012'),
  (1, 7.00, '1/1/2013'),
  (2, 1.00, '1/1/2013'),
  (2, 1.00, '2/1/2013'),
  (3, 5.00, '3/1/2012');

查询

select
  foo.id,
  c.value
from foo
cross apply
(
  select top (1) -- with ties, if desired
    bar.value
  from bar
  where
    bar.id = foo.id
    and bar.entry_date < '20130101'
  order by
    bar.entry_date desc
) as c
order by
  foo.id;

参见 SQLfiddle

Execution plan

答案 2 :(得分:2)

这是相同的,但您也可以将TOP 1 WITH TIESROW_NUMBER()结合使用,以消除对子查询的需求:

select top 1 with ties id, value
from #foo
where entry_date < '1/1/2013'
order by row_number() over (partition by id order by entry_date desc)

在我看来,这有点清洁。不幸的是,它也可以执行稍慢。尽管如此,了解SQL函数的不同用途总是很好。

答案 3 :(得分:1)

使用SQL-Server 2005,您有排名函数和公用表表达式(CTE)。

WITH CTE AS
(
   SELECT id,value,entry_date,
          RN = ROW_NUMBER() OVER (PARTITION BY id ORDER BY entry_date DESC)
   FROM dbo.TableName
   WHERE entry_date < '1/1/2013'
)
SELECT id,value,entry_date FROM CTE WHERE RN = 1

会返回每个ID的最新记录,因此ORDER BY entry_date DESC代替value

如果您想要所有“max-recent”值(如果有多个),请将ROW_NUMBER替换为DENSE_RANK

答案 4 :(得分:1)

使用EXISTS运算符

选项
SELECT t.id, t.value
FROM #foo t
WHERE t.entry_date < '1/1/2013'
 AND EXISTS(
            SELECT 1
            FROM #foo t2
            WHERE t.id = t2.id
              AND t2.entry_date < '1/1/2013'
            HAVING MAX(t2.entry_date) = t.entry_date
            )

SQLFiddle上的演示

为了提高性能,请使用此索引:

CREATE INDEX x ON #foo(id, entry_date) INCLUDE(value)
相关问题