Transact-SQL - 满足条件前的行数

Transact-SQL - number rows until condition met

我正在尝试根据字段“eq”中的值在“x”列中生成数字,在某种程度上它应该为每条记录分配一个数字,直到它满足值“1”,并且下一行应该重置并重新开始计数。我试过 row_number,但问题是我需要评估的列中只有 1 和 0,而我看到的使用 row_number 的情况是在列中使用不断增长的值.也试过等级,但我没能成功。

nInd    Fecha       Tipo    @Inicio     @contador_I  @Final     @contador_F eq  x
1       18/03/2002  I       18/03/2002  1            null       null        0   1
2       20/07/2002  F       18/03/2002  1            20/07/2002 1           1   2
3       19/08/2002  I       19/08/2002  2            20/07/2002 1           0   1
4       21/12/2002  F       19/08/2002  2            21/12/2002 2           1   2
5       17/03/2003  I       17/03/2003  3            21/12/2002 2           0   1
6       01/04/2003  I       17/03/2003  4            21/12/2002 2           0   2
7       07/04/2003  I       17/03/2003  5            21/12/2002 2           0   3
8       02/06/2003  F       17/03/2003  5            02/06/2003 3           0   4
9       31/07/2003  F       17/03/2003  5            31/07/2003 4           0   5
10      31/08/2003  F       17/03/2003  5            31/08/2003 5           1   6
11      01/09/2005  I       01/09/2005  6            31/08/2003 5           0   1
12      05/09/2005  I       01/09/2005  7            31/08/2003 5           0   2
13      31/12/2005  F       01/09/2005  7            31/12/2005 6           0   3
14      14/01/2006  F       01/09/2005  7            14/01/2006 7           1   4

我这里有两个答案。一个基于 ROW_NUMBER(),另一个基于您的索引 (nInd)。我不确定你的索引中是否会有差距,所以我也制作了 ROW_NUMBER()

我的table格式如下-

myIndex int identity(1,1) NOT NULL number int NOT NULL

第一个是ROW_NUMBER()...

WITH rn AS (SELECT *, ROW_NUMBER() OVER (ORDER BY myIndex) AS rn, COUNT(*) AS max 
                  FROM counting c GROUP BY c.myIndex, c.number)
,cte (myIndex, number, level, row) AS (

    SELECT r.myIndex, r.number, 1, r.rn + 1 FROM rn r WHERE r.rn = 1
    UNION ALL
    SELECT r1.myIndex, r1.number, 
                       CASE WHEN r1.number = 0 AND r2.number = 1 THEN 1
                                                                 ELSE c.level + 1
                       END,
                       row + 1
    FROM cte c 
        JOIN rn r1 
            ON c.row = r1.rn
        JOIN rn r2
            ON c.row - 1 = r2.rn
    )

SELECT c.myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);

现在索引...

WITH cte (myIndex, number, level) AS (

    SELECT c.myIndex + 1, c.number, 1 FROM counting c WHERE c.myIndex = 1
    UNION ALL
    SELECT c1.myIndex + 1, c1.number, 
                           CASE WHEN c1.number = 0 AND c2.number = 1    THEN 1
                                                                        ELSE c.level + 1
                           END
    FROM cte c 
        JOIN counting c1
            ON c.myIndex = c1.myIndex
        JOIN counting c2
            ON c.myIndex - 1 = c2.myIndex
    )

SELECT c.myIndex - 1 AS myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);

我现在的答案是使用

Cursor

我知道如果有另一种没有游标的解决方案,它在性能方面会更好

这是我的解决方案的快速演示:

  -- Create DBTest
  use master
  Go
  Create Database DBTest
  Go
  use DBTest
  GO
  -- Create table
  Create table Tabletest
  (nInd    int , eq  int)
  Go
  -- insert dummy data
  insert into Tabletest (nInd,eq) 
  values    (1,0),
            (2,1),
            (3,0),
            (4,1),
            (5,0),
            (6,0),
            (7,0),
            (8,0),
            (9,1),
            (8,0),
            (9,1)



  Create table #Tabletest (nInd int ,eq int ,x int )
  go

  DECLARE  @nInd int , @eq int , @x int
  set @x = 1
  DECLARE db_cursor CURSOR FOR  
  SELECT nInd , eq
  FROM Tabletest  
  order by nInd

  OPEN db_cursor   
  FETCH NEXT FROM db_cursor INTO @nInd , @eq   

  WHILE @@FETCH_STATUS = 0   
  BEGIN   

   if (@eq = 0) 
     begin

            insert into #Tabletest (nInd ,eq  ,x) values (@nInd , @eq , @x)
            set @x = @x +1
     end 
     else if (@eq = 1)
     begin
            insert into #Tabletest (nInd ,eq  ,x) values (@nInd , @eq , @x)
            set @x = 1
     end

    FETCH NEXT FROM db_cursor INTO @nInd , @eq   

  END   

  CLOSE db_cursor   
  DEALLOCATE db_cursor


  select * from #Tabletest

最终结果集如下:

希望对您有所帮助。

还有另一种解决方案:

select 
  nind, eq, row_number() over (partition by s order by s) 
from (
  select 
    nind, eq, coalesce((
      select sum(eq) +1 from mytable pre where pre.nInd < mytable.nInd)
    ,1) s --this is the sum of eq!
  from mytable) g

内部子查询为 eq 中每次出现的 1 按顺序创建 groups。然后我们可以使用 row_number() over partition 来获取我们的计数器。

Here is an example 使用 Sql 服务器

从稍微不同的角度来看(这可能不是真的,但消除了对递归 CTE 游标的需要),看起来您在数据集中构建有序组。因此,首先找到这些组,然后确定每个组的顺序。

真正的关键是确定找到更正分组的规则。根据您的描述和评论,我猜分组是从一开始(按 nInd 列排序)在每一行结束并且 eq 值为 1,因此您可以做类似的事情:

;with ends(nInd, ord) as (
    --Find the ending row for each set
    SELECT nInd, row_number() over(order by nInd)
    FROM mytable
    WHERE eq=1
), ranges(sInd, eInd) as (
    --Find the previous ending row for each ending row, forming a range for the group
    SELECT coalesce(s.nInd,0), e.nInd
    FROM ends s
        right join ends e on s.ord=e.ord-1
)

然后,使用这些组范围,您可以找到每个组的最终排序:

select t.nInd, t.Fecha, t.eq
    ,[x] = row_number() over(partition by sInd order by nInd)
from ranges r
    join mytable t on r.sInd < t.nInd
                    and t.nInd <= r.eInd
order by t.nInd