2017-09-05 87 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」來獲得一些統計數據) –