2

I have the following table ordered by 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.

What I would like to have is an extra rank field like:

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.

Basically per Id alternating ranks for every change in the Valid field. In such a way I can query on the rank=1 field to have all Valid=1 records for each and every Id up to the first Valid=0. Or is there an easier way to select the first top number of records matching a certain condition (For Id=1 only the first two records). I already played with ROW_NUMBER(), RANK() and PARTITION BY but I can't seem to get it to work. Must avoid nesting of queries since the actual query is run against a large database.

Anyone any ideas?

Thanks and cheers, Nyquist

4

3 回答 3

2

这有点类似于@Anup Shah 的建议,但不使用连接,而是使用窗口聚合函数:

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 所做的。

于 2013-10-11T19:32:04.713 回答
1

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

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

在此处输入图像描述

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。

于 2013-10-10T20:55:07.423 回答
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 
于 2013-10-10T20:24:30.260 回答