T-SQL查询 - 没有游标的行迭代

时间:2015-06-16 14:57:52

标签: sql sql-server tsql

我有一张桌子

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进行排序。但接下来该怎么办呢?给定行集中的当前行,如何访问“下一行”?

5 个答案:

答案 0 :(得分:8)

由于您没有指定哪个版本的SQL Server,我有多个解决方案。如果你还在摇摆SQL Server 2005,那么Giorgi就可以很好地使用CROSS APPLY了。

注意:对于这两种解决方案,我使用where子句来过滤掉不正确的值,因此即使数据不正确且行重叠,也会忽略这些值。

我的桌子版本

IndexError: Cursor instances do not support negative indices

SQL Server 2012及更高版本的解决方案

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 2008及以上版本的解决方案

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

答案 1 :(得分:3)

以下是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

答案 2 :(得分:0)

对于每个end_no,您应找到最近的start_no&gt; 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

答案 3 :(得分:0)

这是非常便携的,因为它不需要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

补充间隔的数量最多比您开始时的数量少一个。 (如果两个范围实际上是连续的,那么有些将被消除。)因此,很容易取出每个单独的间隔并计算右边的一个(或者如果你想要反转某些逻辑则左边。)

答案 4 :(得分:0)

这是一个似乎有用的非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)