Sql 跳过特定值后的连续行

Sql 跳过特定值后的连续行,sql,sql-server,tsql,sql-server-2014,Sql,Sql Server,Tsql,Sql Server 2014,注意:我有一个有效的查询,但我正在寻找在大型表上使用它的优化 假设我有一张这样的桌子: id session_id value 1 5 7 2 5 1 3 5 1 4 5 12 5 5 1 6 5 1 7 5 1 8 6 7 9

注意:我有一个有效的查询,但我正在寻找在大型表上使用它的优化

假设我有一张这样的桌子:

id  session_id  value
1       5           7
2       5           1
3       5           1
4       5           12
5       5           1
6       5           1
7       5           1
8       6           7
9       6           1
10      6           3
11      6           1
12      7           7
13      8           1
14      8           2
15      8           3
我想要值为1的所有行的id,但有一个例外: 跳过在同一会话id中直接跟在值7后面的值为1的组

基本上,我会寻找值为1的组,这些组直接跟随值7,受session_id的限制,然后忽略这些组。然后显示所有剩余的值1行

显示id的所需输出:

5
6
7
11
13
我从中获得了一些灵感,并以以下代码结束:

declare @req_data table (
    id int primary key identity,
    session_id int,
    value int
)

insert into @req_data(session_id, value) values (5, 7)
insert into @req_data(session_id, value) values (5, 1)  -- preceded by value 7 in same session, should be ignored
insert into @req_data(session_id, value) values (5, 1)  -- ignore this one too
insert into @req_data(session_id, value) values (5, 12)
insert into @req_data(session_id, value) values (5, 1)  -- preceded by value != 7, show this
insert into @req_data(session_id, value) values (5, 1)  -- show this too
insert into @req_data(session_id, value) values (5, 1)  -- show this too
insert into @req_data(session_id, value) values (6, 7)
insert into @req_data(session_id, value) values (6, 1)  -- preceded by value 7 in same session, should be ignored
insert into @req_data(session_id, value) values (6, 3)
insert into @req_data(session_id, value) values (6, 1)  -- preceded by value != 7, show this
insert into @req_data(session_id, value) values (7, 7)
insert into @req_data(session_id, value) values (8, 1)  -- new session_id, show this
insert into @req_data(session_id, value) values (8, 2)
insert into @req_data(session_id, value) values (8, 3)



select id
from (
    select session_id, id, max(skip) over (partition by grp) as 'skip'
    from (
        select tWithGroups.*,
            ( row_number() over (partition by session_id order by id) - row_number() over (partition by value order by id) ) as grp
        from (
            select session_id, id, value,
                case
                    when lag(value) over (partition by session_id order by session_id) = 7
                        then 1
                    else 0
                end as 'skip'
            from @req_data
        ) as  tWithGroups
    ) as tWithSkipField
    where tWithSkipField.value = 1
) as tYetAnotherOutput
where skip != 1
order by id
这提供了所需的结果,但对于4个select块,我认为在大型表上使用它效率太低


是否有一种更干净、更快的方法来执行此操作?

您可以使用以下查询:

select id, session_id, value,
          coalesce(sum(case when value <> 1 then 1 end) 
                   over (partition by session_id order by id), 0) as grp
from @req_data
因此,此查询检测属于同一组的连续1条记录的孤岛,如前一行值为1所指定的

您可以再次使用窗口功能来检测所有7个孤岛。如果将其包装在第二个cte中,则通过过滤掉所有7个岛,最终可以获得所需的结果:


以下内容应该可以很好地实现这一点

WITH
    cte_ControlValue AS (
        SELECT 
            rd.id, rd.session_id, rd.value,
            ControlValue = ISNULL(CAST(SUBSTRING(MAX(bv.BinVal) OVER (PARTITION BY rd.session_id ORDER BY rd.id), 5, 4) AS INT), 999)
        FROM
            @req_data rd
            CROSS APPLY ( VALUES (CAST(rd.id AS BINARY(4)) + CAST(NULLIF(rd.value, 1) AS BINARY(4))) ) bv (BinVal)
        )
SELECT 
    cv.id, cv.session_id, cv.value
FROM
    cte_ControlValue cv
WHERE 
    cv.value = 1
    AND cv.ControlValue <> 7;
编辑:它的工作原理和原因。。。 基本前提取自

本质上,我们依靠的是两种大多数人通常不会想到的不同行为

1空+任何内容=空。 2您可以将INT转换为固定长度的二进制数据类型,它将继续作为INT排序,而不是像文本字符串那样排序

将间歇步骤添加到CTE中的查询时,这更容易看到

SELECT 
    rd.id, rd.session_id, rd.value, 
    bv.BinVal,
    SmearedBinVal = MAX(bv.BinVal) OVER (PARTITION BY rd.session_id ORDER BY rd.id),
    SecondHalfAsINT = CAST(SUBSTRING(MAX(bv.BinVal) OVER (PARTITION BY rd.session_id ORDER BY rd.id), 5, 4) AS INT),
    ControlValue = ISNULL(CAST(SUBSTRING(MAX(bv.BinVal) OVER (PARTITION BY rd.session_id ORDER BY rd.id), 5, 4) AS INT), 999)
FROM
    #req_data rd
    CROSS APPLY ( VALUES (CAST(rd.id AS BINARY(4)) + CAST(NULLIF(rd.value, 1) AS BINARY(4))) ) bv (BinVal)
结果

id          session_id  value
----------- ----------- -----------
5           5           1
6           5           1
7           5           1
11          6           1
13          8           1
id          session_id  value       BinVal             SmearedBinVal      SecondHalfAsINT ControlValue
----------- ----------- ----------- ------------------ ------------------ --------------- ------------
1           5           7           0x0000000100000007 0x0000000100000007 7               7
2           5           1           NULL               0x0000000100000007 7               7
3           5           1           NULL               0x0000000100000007 7               7
4           5           12          0x000000040000000C 0x000000040000000C 12              12
5           5           1           NULL               0x000000040000000C 12              12
6           5           1           NULL               0x000000040000000C 12              12
7           5           1           NULL               0x000000040000000C 12              12
8           6           7           0x0000000800000007 0x0000000800000007 7               7
9           6           1           NULL               0x0000000800000007 7               7
10          6           3           0x0000000A00000003 0x0000000A00000003 3               3
11          6           1           NULL               0x0000000A00000003 3               3
12          7           7           0x0000000C00000007 0x0000000C00000007 7               7
13          8           1           NULL               NULL               NULL            999
14          8           2           0x0000000E00000002 0x0000000E00000002 2               2
15          8           3           0x0000000F00000003 0x0000000F00000003 3               3
查看BinVal列,我们看到所有非[value]=1行都有一个8字节的十六进制值,其中[value]=1。。。前4个字节是用于排序的Id,第2个4个字节是用于设置前一个非1值或将整个设置为NULL的[value]

第二步是使用window-framed-MAX函数将非空值涂抹到空值中,该函数按session_id进行分区,并按id排序

第三步是解析最后4个字节,并将它们转换回INT数据类型secondhalvasint,并处理由于没有任何非1前导值ControlValue而导致的任何空值


因为我们不能在WHERE子句中引用窗口函数,所以我们必须将查询放入CTE中。派生表也可以工作,这样我们就可以在WHERE子句中使用新的控制值。

看看LAG:你可以看看前一行。他们在原始查询@Leonidas199xI中使用了LAG,认为这属于代码检查,不是堆栈交换。它是工作代码。@ TabAlman如果它没有扩展到实际的数据卷,那么我可能仍然认为它是错误的。比如,如果你的算法开启了!要排序数组,我不认为这是可行的。这段代码看起来是在^2上执行一个明显的On任务,我们应该对一个有效的基于集合的解决方案感兴趣,以避免在T-SQL中编写自定义循环。这是一个聪明的解决方案,但它确实需要解释。这是伊兹克·本·甘对上一个非空谜题的解决方案的变体。。。在本例中,Idead是,我们希望所有行的[value]=1,但前提是前面的非1值不是=7。。。因此,通过将[value]=1视为空值,我们可以使用伊兹克的方法获得最后一个非空值。。。一旦我们有了它,最后的where子句就变得非常容易写了。我会继续更新答案,并提供更好的解释。这是一个聪明的解决方案,有非常有用的解释,谢谢。就性能而言,它似乎排在第二位,但我不是衡量这一点的专家。我用“设置统计时间”和“设置统计IO”来获取一些状态,我很想看看你的测试。使用临时表代替@table变量,以便可以创建静态。。。使用原始测试数据,无附加索引。我将提出以下建议。。。二进制concat:总扫描计数=20;逻辑读取总数=93;cpu时间=0运行时间=00:00:00.001///间隙和孤岛:总扫描计数=23;逻辑读取总数=156;cpu时间=0;耗用时间=00:00:00.046在会话_id&id上添加覆盖非聚集索引将从二进制concat解决方案中移除77%的计划成本排序,使其速度提高约5倍。@Nicokepe很高兴我能够提供帮助并欢迎使用堆栈溢出。如果此答案有助于您解决问题,请将此答案或任何其他答案标记为已接受。此方法有效,但在包含1000条记录的表上大约需要15毫秒的时间。作为比较,其他脚本需要3-12毫秒。根据实际执行计划,有两种排序操作,各占32%。编辑:对不起,我的评论是错误的。这似乎是对服务器影响最小的解决方案。到目前为止,我还没有在输出中找到任何差距,所以这很好编辑:对不起,我的评论搞错了。。
SELECT CRow.id
FROM @req_data AS CRow
CROSS APPLY (SELECT MAX(id) AS id FROM @req_data PRev WHERE PRev.Id < CRow.id  AND PRev.session_id = CRow.session_id AND  PRev.value <> 1 ) MaxPRow
LEFT JOIN @req_data AS PRow ON MaxPRow.id = PRow.id
WHERE CRow.value = 1 AND ISNULL(PRow.value,1) <> 7
id          session_id  value       BinVal             SmearedBinVal      SecondHalfAsINT ControlValue
----------- ----------- ----------- ------------------ ------------------ --------------- ------------
1           5           7           0x0000000100000007 0x0000000100000007 7               7
2           5           1           NULL               0x0000000100000007 7               7
3           5           1           NULL               0x0000000100000007 7               7
4           5           12          0x000000040000000C 0x000000040000000C 12              12
5           5           1           NULL               0x000000040000000C 12              12
6           5           1           NULL               0x000000040000000C 12              12
7           5           1           NULL               0x000000040000000C 12              12
8           6           7           0x0000000800000007 0x0000000800000007 7               7
9           6           1           NULL               0x0000000800000007 7               7
10          6           3           0x0000000A00000003 0x0000000A00000003 3               3
11          6           1           NULL               0x0000000A00000003 3               3
12          7           7           0x0000000C00000007 0x0000000C00000007 7               7
13          8           1           NULL               NULL               NULL            999
14          8           2           0x0000000E00000002 0x0000000E00000002 2               2
15          8           3           0x0000000F00000003 0x0000000F00000003 3               3
SELECT CRow.id
FROM @req_data AS CRow
CROSS APPLY (SELECT MAX(id) AS id FROM @req_data PRev WHERE PRev.Id < CRow.id  AND PRev.session_id = CRow.session_id AND  PRev.value <> 1 ) MaxPRow
LEFT JOIN @req_data AS PRow ON MaxPRow.id = PRow.id
WHERE CRow.value = 1 AND ISNULL(PRow.value,1) <> 7