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
我正在尝试根据字段“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