2017-09-05 95 views
2

注:我有一个正在运行的查询,但正在寻找优化以在大型表上使用它。在特定值后跳过连续行

假设我有一个这样的表:

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的: 跳跃组,值1直接遵循相同的session_id内的值7。

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

所需的输出显示的ID:

5 
6 
7 
11 
13 

我花了一些灵感来自于this post并结束了这段代码:

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个选择块我认为在大型表上使用它效率太低。

有没有更干净,更快捷的方法来做到这一点?

+0

看看LAG:https://docs.microsoft.com/en-us/sql/t-sql/functions/lag-transact-sql您可以查看上一行。 – Leonidas199x

+0

他们用'LAG'在@ Leonidas199x – scsimon

+3

我觉得这属于在代码审查原始查询,不能叠加交流。这是工作代码。 –

回答

0
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 
+0

这似乎是对服务器影响最小的解决方案。到目前为止,我还没有在输出中找到任何差距,所以这很好。 :) 编辑:对不起,我得到了我的意见错误的方式.. –

0

您可以使用下面的查询:

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 

获得:

id session_id value grp 
---------------------------- 
1 5   7  1 
2 5   1  1 
3 5   1  1 
4 5   12  2 
5 5   1  2 
6 5   1  2 
7 5   1  2 
8 6   7  1 
9 6   1  1 
10 6   3  2 
11 6   1  2 
12 7   7  1 
13 8   1  0 
14 8   2  1 
15 8   3  2 

所以,这个查询检测属于同一,由指定的连续1记录岛屿第一行前行value <> 1

您可以再次使用窗口函数来检测所有的7岛屿。如果你把这包裹在第二CTE,那么你终于可以过滤掉所有7岛屿得到期望的结果:

;with session_islands as (
    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 
), islands_with_7 as (
    select id, grp, value, 
      count(case when value = 7 then 1 end) 
      over (partition by session_id, grp) as cnt_7 
    from session_islands 
) 
select id 
from islands_with_7 
where cnt_7 = 0 and value = 1 
+0

@NicoKempe很高兴我能帮助,欢迎堆栈溢出。如果它帮助你解决你的问题,请将它标记为或接受任何其他答案。 –

+0

这可以工作,尽管它在有1000条记录的桌子上需要大约15ms的时间。为了比较,其他脚本需要3-12ms。根据实际执行计划,有两种分类行为,每种分别占32%。 编辑:对不起,我得到了我的意见的方式不对周围.. –

2

以下应为这项工作做得很好。

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; 

结果...

id   session_id value 
----------- ----------- ----------- 
5   5   1 
6   5   1 
7   5   1 
11   6   1 
13   8   1 

编辑:如何以及为什么它的工作原理... 的基本前提是从Itzik Ben-Gan's "The Last non NULL Puzzle"拍摄。

本质上讲,我们是靠2种不同的行为,大多数人通常不会去想......

1)NULL +什么= NULL。 2)你可以将一个INT CAST或CONVERT转换成一个固定长度的BINARY数据类型,它将继续排序为一个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  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列寻找,我们看到一个8字节的十六进制值的所有非[值] = 1行和NULLS其中[值] = 1。 ..第一个4个字节是Id(用于排序),第二个4个字节是[value](用于设置“以前的非1值”或将整个事物设置为NULL。

第二步是“拖影”的非NULL值到使用窗口的NULL陷害MAX功能,通过SESSION_ID分区和ID进行排序。

的第三步骤是将解析出最后4个字节,并将其转换回一个INT数据类型(SecondHalfAsINT)和处理来自不具有任何非1先前值(ControlValue)产生的任何空值。因为我们不能在WHERE子句中引用窗口函数,所以我们必须将查询引入CTE(派生表也可以),以便我们可以在where子句中使用新的ControlValue。

+2

这是一个聪明的解决方案,但它确实需要一个解释。 – GarethD

+2

这是伊茨克奔甘的解决方案,以最后一个非NULL拼图的变化。 http://sqlmag.com/t-sql/last-non-null-puzzle ... 在这种情况下,Idead是,我们希望所有行的[值] = 1,但只有前面的非1值不是= 7 ...因此,通过将[value] = 1视为零,我们可以使用Itzik的方法来获取“最后一个非空值”......一旦我们有了这个,最后的where子句变成真的很容易写。我会继续并更新答案以及更好的解释。 –

+0

一个聪明的解决方案,非常有帮助的解释,谢谢。在性能方面,它似乎排在第二位,但我不是衡量这一点的专家。 (我使用“SET STATISTICS TIME ON”和“SET STATISTICS IO ON”来获得一些统计数据) –