如何根据列的更改值对记录进行分组/排名?

时间:2013-10-10 19:01:59

标签: sql sql-server tsql

我按照Id,Year DESC

排序了下表
Id   Year   Valid
1    2011   1
1    2010   1
1    2009   0
1    2002   1
4    2013   1
4    2012   1
4    2011   1
etc.

我想要的是一个额外的排名字段,如:

Id   Year   Valid   Rank
1    2011   1       1
1    2010   1       1
1    2009   0       2
1    2002   1       3
4    2013   1       1
4    2012   1       1
4    2011   1       1
etc.

对于Valid字段中的每个更改,基本上每个Id交替排名。以这种方式,我可以在rank = 1字段上查询,以使每个Id的所有Valid = 1记录直到第一个Valid = 0。或者是否有更简单的方法来选择匹配特定条件的第一个最大记录数(对于Id = 1仅前两个记录)。我已经玩过ROW_NUMBER(),RANK()和PARTITION BY,但我似乎无法让它工作。必须避免嵌套查询,因为实际查询是针对大型数据库运行的。

任何想法?

谢谢,欢呼, 奈奎斯特

3 个答案:

答案 0 :(得分:2)

这有点类似于@Anup Shah's suggestion但不使用连接而是使用窗口聚合函数:

WITH derived AS (
  SELECT
    Id,
    Year,
    Valid,
    LatestInvalidYear = ISNULL(
      MAX(CASE Valid WHEN 0 THEN Year END) OVER (PARTITION BY Id),
      0
    )
  FROM atable
)
SELECT
  Id,
  Year,
  Valid
FROM derived
WHERE Year > LatestInvalidYear
;

基本上,窗口MAX计算每Valid = 0的最新Id年。如果没有找到这样的年份,则MAX结果为NULL,由ISNULL替换为0。因此,对于您的示例,derived集将返回为:

Id   Year   Valid   LatestInvalidYear
--   ----   -----   -----------------
1    2011   1       2009
1    2010   1       2009
1    2009   0       2009
1    2002   1       2009
4    2013   1       0
4    2012   1       0
4    2011   1       0

显然,您现在可以轻松应用过滤器Year > LatestInvalidYear来获取所需的行,这显然是SELECT的主要功能。

答案 1 :(得分:1)

是的,使用Left JOIN我们可以做到这一点。 请参阅以下代码和结果。

第一张图片是插入的实际数据,第二张图片是预期的结果。

enter image description here

DECLARE @t TABLE
(
    id      INT
    ,_YEAR  INT
    ,valid  TINYINT
)
INSERT INTO @t( id, [_YEAR], valid )
            SELECT 1,2011,1
UNION ALL   SELECT 1,2010,1
UNION ALL   SELECT 1,2009,0
UNION ALL   SELECT 1,2002,1
UNION ALL   SELECT 4,2013,1
UNION ALL   SELECT 4,2012,1
UNION ALL   SELECT 4,2011,1
UNION ALL   SELECT 5,2013,0
UNION ALL   SELECT 5,2011,1
UNION ALL   SELECT 5,2010,1
UNION ALL   SELECT 6,2010,1
UNION ALL   SELECT 6,2011,0
UNION ALL   SELECT 6,2014,1


SELECT  q1.*
FROM @t q1
LEFT JOIN 
(
    SELECT id,MAX(_YEAR) ZeroYear
    FROM @t
    WHERE valid = 0
    GROUP BY id
)q2
    ON q1.id=q2.id
WHERE 
(q2.ID IS NULL)
OR
(q2.id IS NOT NULL AND q1.id IS NOT NULL AND q1.id=q2.id AND q1.[_YEAR] > q2.ZeroYear)

修改-1: 在上面的ZeroYear列的查询中,我之前做过MIN(_YEAR),但是你可以在“Andriy M”的评论中看到,而不是MIN,右边的函数是MAX。

答案 2 :(得分:0)

如果您使用的是SQL 2012,则可以使用lag

select id, year, valid,
    case when ch = 0 then 1 else lag(ch,1,0) over (order by id, year desc) + 2 end rank
from
    (
        select 
            * ,
            abs(valid - lag(valid,1,1) over (order by id, year desc)) as ch
        from YourTable
    ) t