在ms sql中选择每组的前1条记录,而不使用Max或Min

时间:2014-06-10 02:38:48

标签: sql sql-server-2008

我知道谷歌搜索这个问题会带来许多解决方案,但它们都不适用于我的情况。

我有一张表:

CREATE TABLE [Batch](
    [batch_id]  [int]       NOT NULL, 
...(more columns)
    [date] [datetime]   NULL)
 CONSTRAINT [pk_Positions] PRIMARY KEY CLUSTERED 
(   
[batch_id] ASC, 
...(more columns)
 )
  1. batch_iddate有一对一的关系。即,对于给定的batch_id,所有日期都相同,对于给定日期,所有batch_id都是相同的。 (我知道这是糟糕的设计。如果我要设计表格,我可能会为batch_id和日期创建一个单独的表格)
  2. 可以有多个具有相同batch_id
  3. 的记录

    现在我想获得所有不同date的列表。

    由于表非常庞大且date不是索引列,我不想尝试以下任何内容:

    select distinct date from Batch
    

    出于类似的原因,我已经排除了在date上创建非聚集索引的选项

    相反,我想做类似的事情:

    select First(date) from Batch 
    Group by batch_id
    

    select Top 1 date from Batch 
    Group by batch_id
    

    但MS SQL不提供First()函数,后者返回“不在聚合函数中”错误。

    据我所知,我应该使用Min()Max()代替First(),例如:

    select Max(date) from Batch 
    Group by batch_id
    

    但是,由于有时会有超过100k的记录具有相同的batch_id,因此使用Min()Max()的效率不如仅返回第一条记录而没有任何比较。那么如何优化上一个查询以获得更好的性能呢?

4 个答案:

答案 0 :(得分:0)

既然你说batch_iddate之间存在一对一的关系,那就可以胜任:

SELECT DISTINCT batch_id, date FROM Batch

如果不是这样,您可以将行号与每个记录相关联,并仅检索第一个:

WITH BatchWithRowNum AS
(
    SELECT 
        * 
        , RowNum = ROW_NUMBER() OVER (PARTITION BY batch_id ORDER BY date)
    FROM Batch
)
SELECT * FROM BatchWithRowNum WHERE RowNum = 1

我希望比行数方法更快的第三种方法是:

SELECT B.batch_id, T.MinDate AS date
FROM Batch B
INNER JOIN 
(
    SELECT B2.batch_id, MIN(B2.date) AS MinDate
    FROM Batch B2 
    GROUP BY B2.batch_id
) T 
ON B.batch_id = T.batch_id 
GROUP BY B.batch_id, T.MinDate

以下通常不是一种有效的解决方案,但在您的情况下可能会有更好的性能,因为它只依赖于batch_id上现有的索引:

SELECT 
    DISTINCT B.batch_id
    , date = (SELECT TOP 1 date FROM Batch B2 WHERE B2.batch_id = B.batch_id)
FROM 
    Batch B

如果您遇到严重的性能问题并且无法添加索引,除非您使用WHERE子句缩小结果集,否则以上任何一项都不会对您有所帮助。例如,使用一组batch-id s或特定date范围内的批次来引入批次的子集。

答案 1 :(得分:0)

尽管我的SQL业力很难说,但我认为这可能是迭代处理有用的一种情况。在伪代码中:

declare #WorkingTable(batchID, date)
declare @CurrentBatchID = NULL
declare @BatchDate = NULL;

select top 1 
    @Current BatchID = batch_id,
    @BatchDate = [Date]
from Batch
where batch_id > -1  -- less than the smallest in the table
order by batch_id asc;

while @CurrentBatchID is not NULL
begin
    insert #WorkingTable values (@BatchID, @BatchDate);

    select top 1 
        @CurrentBatchID = batch_id,
        @BatchDate = [Date]
    from Batch
    where batch_id > @CurrentBatchID
    order by batch_id asc;
end

select * from #WorkingTable

虽然每次迭代将有一个表访问权限,但它将位于群集密钥上,具有带来的所有优势。但仍然很难看。

如果您打算定期执行此操作,最好创建一个只包含batch_id和[Date]的查找表,该表由ETL和清除进程维护。

答案 2 :(得分:0)

如果您创建此功能: -

CREATE FUNCTION [dbo].GetDateForBatch_id
(
    @batch_id int
)
RETURNS datetime
AS
BEGIN
    RETURN (SELECT TOP 1 [date]
    FROM dbo.Batch
    WHERE batch_id=@batch_id)
END
go

然后运行此查询: -

select 
    b.batch_id,
    dbo.GetDateForBatch_id(b.batch_id) AS [date]
FROM (SELECT DISTINCT batch_id
FROM Batch) b

您应该使用现有的索引策略获得最佳性能。

答案 3 :(得分:0)

- 只需删除重复记录 - 最好的方法 DECLARE @juvenileid int,@ luCountyName varchar(40),@ DispHearDate datetime,@ vid int 设置@vid = 0 声明db_cursor游标 SELECT juvenileid,luCountyName,DispHearDate FROM#TEMP48 ORDER BY juvenileid 打开db_cursor FETCH NEXT FROM db_cursor INTO @juvenileid,@ luCountyName,@ PlDt WHILE @@ FETCH_STATUS = 0
BEGIN
        BEGIN100:         如果@vid = 0         开始             SET @vid = @juvenileid         END
        其他         开始         IF @ vid = @juvenileid         开始             从#TEMP48中删除juvenileid = @juvenileid             和luCountyName = @luCountyName             和DispHearDate = @DispHearDate         结束         其他         开始             SET @vid = 0             GOTO BEGIN100         END
        END
  FETCH NEXT FROM db_cursor INTO @juvenileid,@ luCountyName,@ DispHearDate END
关闭db_cursor
DEALLOCATE db_cursor