我有一个表,其中一列的值将是这样的。
C H C H H H H C H H H
它将有一个“C”和一个或多个“H”记录。
我试图为每组“C”和一个或多个“H”提供一个组号。
C 1 H 1 C 2 H 2 H 2 H 2 H 2 C 3 H 3 H 3 H 3
由于担心性能,我不想使用游标。如何为“C”的每个子集和一个或多个“H”记录提供唯一的编号?
我有一个表,其中一列的值将是这样的。
C H C H H H H C H H H
它将有一个“C”和一个或多个“H”记录。
我试图为每组“C”和一个或多个“H”提供一个组号。
C 1 H 1 C 2 H 2 H 2 H 2 H 2 C 3 H 3 H 3 H 3
由于担心性能,我不想使用游标。如何为“C”的每个子集和一个或多个“H”记录提供唯一的编号?
只要您的数据干净且一致,这在支持标准 SQL 窗口函数的平台上并不太难。您确实需要另一列可以有意义地排序。
让我们一次一个地构建它。(用 PostgreSQL 9.3 编写。)
create table test (
test_id serial primary key,
test_val char(1)
);
insert into test(test_val) values
('C'), ('H'),
('C'),('H'),('H'),('H'),('H'),
('C'),('H'),('H'),('H');
我们可以通过查看下一行来判断一个组何时开始。
select test_id, test_val,
lead(test_val) over (order by test_id) next_test_val
from test;
该查询的前三行。
test_id test_val next_test_val -- 1 通道 2 碳氢化合物 3通道 ...
通过检查“C”和“H”组合,我们可以识别组的开始。(前面的查询变成了公用表表达式。)
with next_vals as (
select test_id, test_val,
lead(test_val) over (order by test_id) next_test_val
from test
)
select *, case when test_val = 'C' and next_test_val = 'H' then test_id
end as grp
from next_vals;
这是该结果集中的前四行。id 号便于识别组。
test_id test_val next_test_val grp -- 1 通道 1 2 碳氢化合物 3 通道 3 4 小时 ...
另一个窗口函数填补了空白。同样,前面的查询变成了 CTE。WHERE 子句防止出现“C”行后跟另一个“C”行。
with next_vals as (
select test_id, test_val,
lead(test_val) over (order by test_id) next_test_val
from test
), group_starts as (
select *
, case when test_val = 'C' and next_test_val = 'H' then test_id
end as grp
from next_vals
)
select test_id, test_val, max(grp) over (order by test_id) as ch_group
from group_starts
where not (test_val = 'C' and next_test_val = 'C')
order by test_id;
test_id test_val ch_group -- 1 C 1 2 小时 1 3 C 3 4 小时 3 5 小时 3 6 小时 3 7 小时 3 8 C 8 9 小时 8 10 小时 8 11 小时 8
我添加了一些换行符以使其更易于阅读。
我不知道这是否会比游标更好。
对于连续组号。. .
with next_vals as (
select test_id, test_val,
lead(test_val) over (order by test_id) next_test_val
from test
), group_starts as (
select *
, case when test_val = 'C' and next_test_val = 'H' then test_id
end as grp
from next_vals
), grouped_values as (
select test_id, test_val, max(grp) over (order by test_id) as ch_group
from group_starts
where not (test_val = 'C' and next_test_val = 'C')
)
select test_id, test_val,
dense_rank() over (order by ch_group)
from grouped_values
order by test_id;
这是一种适用于 MS SQL Server 2008 的可能解决方案,它没有LEAD
功能(在更高版本中添加)。此外,此解决方案编号按顺序分组,没有间隙,如所需输出所示。
它只使用ROW_NUMBER()
函数和CROSS APPLY
。
有必要拥有ID
唯一标识每一行的列,我们可以使用它来对结果进行排序。
使用样本数据创建一个测试表:
DECLARE @TT TABLE (ID int IDENTITY(1,1) PRIMARY KEY, Val char(1));
INSERT INTO @TT VALUES('C');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('C');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('C');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('H');
INSERT INTO @TT VALUES('H');
获取所有具有C
值的行的列表。每个组都以 开头C
,因此C
数据中的组数将与 s 一样多。此列中的其他值无关紧要,也可以有其他值,而不仅仅是H
. 查询中没有硬编码H
,只有C
.
WITH
CTE_C
AS
(
SELECT ID, Val, ROW_NUMBER() OVER(ORDER BY ID) AS rn
FROM @TT AS T
WHERE Val = 'C'
)
此 CTE 的输出为: ( SELECT * FROM CTE_C
)
ID Val rn
1 C 1
3 C 2
8 C 3
现在我们只需要为原始数据的每一行在 CTE 中找到一个合适的行,该行具有合适的 ID,因此具有合适的 rn。我们用CROSS APPLY
它。
WITH
CTE_C
AS
(
SELECT ID, Val, ROW_NUMBER() OVER(ORDER BY ID) AS rn
FROM @TT AS T
WHERE Val = 'C'
)
SELECT T.ID, T.Val, CTE_rn.rn
FROM
@TT AS T
CROSS APPLY
(
SELECT TOP(1) CTE_C.rn
FROM CTE_C
WHERE CTE_C.ID <= T.ID
ORDER BY CTE_C.ID DESC
) AS CTE_rn
ORDER BY T.ID;
这是最终结果:
ID Val rn
1 C 1
2 H 1
3 C 2
4 H 2
5 H 2
6 H 2
7 H 2
8 C 3
9 H 3
10 H 3
11 H 3
在性能方面,您需要使用实际数据和实际系统测试各种解决方案。ID 应该有唯一的索引。Val 的指数很可能也是有益的。