T-SQL 查询 - 没有游标的行迭代
Posted
技术标签:
【中文标题】T-SQL 查询 - 没有游标的行迭代【英文标题】:T-SQL query - row iteration without cursor 【发布时间】:2015-06-16 14:57:52 【问题描述】:我有一张桌子
T (variable_name, start_no, end_no)
包含以下值:
(x, 10, 20)
(x, 30, 50)
(x, 60, 70)
(y, 1, 3)
(y, 7, 8)
保证所有区间都是不相交的。
我想在 T-SQL 中编写一个查询来计算不搜索变量的间隔:
(x, 21, 29)
(x, 51, 59)
(y, 4, 6)
我可以在没有光标的情况下执行此操作吗?
我正在考虑按 variable_name 分区,然后按 start_no 排序。但是接下来如何进行呢?给定行集中的当前行,如何访问“下一个”行?
【问题讨论】:
所以每个变量的范围都隐含在最低范围和最高范围内,对吗? 您使用的是 SQL Server 2012 或更高版本吗?滞后/领先可以帮助你 只需加入 variable_name 和 rownumber + 1 是的,这是正确的 David W. 例如,对于 x,我不包括 (-inf, 10) 和 (70, +inf)。 JamesZ,SQL Server 2014,我宁愿让它在 2008 年也能工作。 【参考方案1】:由于您没有指定 SQL Server 的哪个版本,我有多种解决方案。如果您还在使用 SQL Server 2005,那么 Giorgi 的 CROSS APPLY 使用得非常好。
注意:对于这两种解决方案,我都使用 where 子句来过滤掉不正确的值,因此即使数据错误并且行重叠,它也会忽略这些值。
你的表我的版本
DECLARE @T TABLE (variable_name CHAR, start_no INT, end_no INT)
INSERT INTO @T
VALUES ('x', 10, 20),
('x', 30, 50),
('x', 60, 70),
('y', 1, 3),
('y', 7, 8);
SQL Server 2012 及更高版本的解决方案
SELECT *
FROM
(
SELECT variable_name,
LAG(end_no,1) OVER (PARTITION BY variable_name ORDER BY start_no) + 1 AS start_range,
start_no - 1 AS end_range
FROM @T
) A
WHERE end_range > start_range
SQL 2008 及更高版本的解决方案
WITH CTE
AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY variable_name ORDER BY start_no) row_num,
*
FROM @T
)
SELECT A.variable_name,
B.end_no + 1 AS start_range,
A.start_no - 1 AS end_range
FROM CTE AS A
INNER JOIN CTE AS B
ON A.variable_name = B.variable_name
AND A.row_num = B.row_num + 1
WHERE A.start_no - 1 /*end_range*/ > B.end_no + 1 /*start_range*/
【讨论】:
这仅适用于 2012+。但它会给你结果。 :-) 是的,当您对此发表评论时,我正在使用 ROW_NUMBER() 开发我的解决方案。请参阅编辑后的答案。 是的,这在旧版本上看起来好多了。 :-) 感谢您提供两种解决方案。【参考方案2】:这是另一个带有cross apply
的版本:
DECLARE @t TABLE ( v CHAR(1), sn INT, en INT )
INSERT INTO @t
VALUES ( 'x', 10, 20 ),
( 'x', 30, 50 ),
( 'x', 60, 70 ),
( 'y', 1, 3 ),
( 'y', 7, 8 );
SELECT t.v, t.en + 1, c.sn - 1 FROM @t t
CROSS APPLY(SELECT TOP 1 * FROM @t WHERE v = t.v AND sn > t.sn ORDER BY sn)c
WHERE t.en + 1 < c.sn
小提琴http://sqlfiddle.com/#!3/d6458/3
【讨论】:
这个有一个问题。如果没有间隙怎么办?假设第二行是 ('x',21,50)。然后,您将返回一个不正确的 ('x',21,20) 行。您应该添加一个 where 子句 WHERE t.en + 1 太好了,谢谢!我没有将您的答案标记为 t-h-e 答案,因为我确实没有在初始帖子中指定 SQL Server 版本。【参考方案3】:对于每个end_no
,您应该找到最近的start_no
> end_no
,然后排除没有最近的start_no
的行(variable_name
的最后一行)
WITH A AS
(
SELECT variable_name, end_no+1 as x1,
(SELECT MIN(start_no)-1 FROM t
WHERE t.variable_name = t1.variable_name
AND t.start_no>t1.end_no) as x2
FROM t as t1 )
SELECT * FROM A WHERE x2 IS NOT NULL
ORDER BY variable_name,x1
SQLFiddle demo
这也是我对类似问题的旧答案:
Allen's Interval Algebra operations in SQL
【讨论】:
【参考方案4】:这是一个似乎可以工作的非 CTE 版本:http://sqlfiddle.com/#!9/4fdb4/1
鉴于保证的不相交范围,我只是将 T 加入到自身,计算下一个范围作为相邻范围的增量/减量,然后确保新范围不与任何现有范围重叠。
select t1.variable_name, t1.end_no+1, t2.start_no-1
from t t1
join t t2
on t1.variable_name=t2.variable_name
where t1.start_no < t2.start_no
and t1.end_no < t2.end_no
and not exists (select *
from t
where ((t2.start_no-1< t.end_no
and t1.end_no+1 > t.start_no) or
(t1.end_no + 1 < t.end_no and
t2.start_no-1 > t.end_no))
and t.variable_name=t1.variable_name)
【讨论】:
连接将是一对多的,不是吗?not exists
查询是否处理多余的行?
目的是让不存在来处理新的可能未搜索范围的一端与现有范围重叠的边缘情况;至于生成太多行,我在测试中没有看到,但也许我忽略了一个条件。
我只是看着小提琴。它确实会生成额外的行,这些行最终会重叠,因此最终会被剔除。【参考方案5】:
这是非常便携的,因为它不需要 CTE 或分析函数。如果有必要,我也可以很容易地在没有派生表的情况下重写。
select * from (
select
variable_name,
end_no + 1 as start_no,
(
select min(start_no) - 1
from T as t2
where t2.variable_name = t1.variable_name and t2.start_no > t1.end_no
) as end_no
from T as t1
) as intervals
where start_no <= end_no
补足间隔的数量最多会比您开始时的数量少一个。 (如果两个范围实际上是连续的,则有些范围将被消除。)因此很容易获取每个单独的间隔并计算其右侧(或如果您想反转某些逻辑,则在左侧。)
【讨论】:
以上是关于T-SQL 查询 - 没有游标的行迭代的主要内容,如果未能解决你的问题,请参考以下文章