簡體   English   中英

如何在 SQL 服務器中使用帶有框架的 window 函數進行 COUNT(DISTINCT)

[英]How to do a COUNT(DISTINCT) using window functions with a frame in SQL Server

捎帶這個可愛的問題: Partition Function COUNT() OVER possible using DISTINCT

我希望計算不同值的移動計數。 類似於以下內容:

Count(distinct machine_id) over(partition by model order by _timestamp rows between 6 preceding and current row)

顯然,SQL Server 不支持該語法。 不幸的是,我不太了解(沒有內化會更准確)dense_rank 繞行是如何工作的:

dense_rank() over (partition by model order by machine_id) 
+ dense_rank() over (partition by model order by machine_id) 
- 1

因此我無法對其進行調整以滿足我對移動 window 的需求。 如果我按 machine_id 訂購,是否也可以按 _timestamp 訂購並使用 _timestamp rows between的行?

dense_rank()給出當前記錄的密集排名。 當您首先使用ASC排序順序運行它時,您會從第一個元素中獲得當前記錄的密集排名(唯一值排名)。 當您使用DESC命令運行時,您會從最后一條記錄中獲得當前記錄的密集排名。 然后你刪除 1 因為當前記錄的密集排名被計算了兩次。 這給出了整個分區中的總唯一值(並為每一行重復)。

因為, dense_rank不支持frames ,你不能直接使用這個解決方案。 您需要通過其他方式生成frame 一種方法是通過正確的unique id比較來JOIN同一個表。 然后,您可以在組合版本上使用dense_rank

請查看以下解決方案建議。 假設您的表中有一個唯一的記錄鍵 ( record_id )。 如果您沒有唯一鍵,請在第一個 CTE 之前添加另一個 CTE 並為每條記錄生成一個唯一鍵(使用new_id() function 或使用concat()組合多個列,中間帶有分隔符以解釋NULLs

; WITH cte AS (
SELECT 
  record_id
  , record_id_6_record_earlier = LEAD(machine_id, 6, NULL) OVER (PARTITION BY model ORDER BY _timestamp)
  , .... other columns
FROM mainTable
)
, cte2 AS (
SELECT 
  c.*
  , DistinctCntWithin6PriorRec = dense_rank() OVER (PARTITION BY c.model, c.record_id ORDER BY t._timestamp)
    + dense_rank() OVER (PARTITION BY c.model, c.record_id ORDER BY t._timestamp DESC)
    - 1
  , RN = ROW_NUMBER() OVER (PARTITION BY c.record_id ORDER BY t._timestamp )
FROM cte c
     LEFT JOIN mainTable t ON t.record_id BETWEEN c.record_id_6_record_earlier  and c.record_id
)
SELECT *
FROM cte2
WHERE RN = 1

此解決方案有 2 個限制:

  1. 如果幀的記錄少於 6 條,則LAG() function 將為NULL ,因此此解決方案將不起作用。 這可以通過不同的方式處理:我能想到的一種快速方法是生成 6 個 LEAD 列(1 個之前的記錄,2 個之前的記錄等),然后將BETWEEN子句更改為類似這樣的東西BETWEEN COALESCE(c.record_id_6_record_earlier, c.record_id_5_record_earlier, ...., c.record_id_1_record_earlier, c.record_id) and c.record_id

  2. COUNT()不計算NULL 但是DENSE_RANK可以。 如果它適用於您的數據,您也需要考慮這一點

只需使用outer apply

select t.*, t2.num_machines
from t outer apply
     (select count(distinct t2.machine_id) as num_machines
      from (select top (6) t2.*
            from t t2
            where t2.model = t.model and
                  t2.timestamp <= t.timestamp
            order by t2.timestamp desc
           ) t2
      ) t2;

如果每個 model 有很多行,您還可以使用lag()的(繁瑣)技巧:

select t.*, v.num_machines
from (select t.*,
             lag(machine_id, 1) over (partition by model order by timestamp) as machine_id_1,
             lag(machine_id, 2) over (partition by model order by timestamp) as machine_id_2,
             lag(machine_id, 3) over (partition by model order by timestamp) as machine_id_3,
             lag(machine_id, 4) over (partition by model order by timestamp) as machine_id_4,
             lag(machine_id, 5) over (partition by model order by timestamp) as machine_id_5
      from t
     ) t cross apply
     (select count(distinct v.machine_id) as num_machines
      from (values (t.machine_id),
                   (t.machine_id_1),
                   (t.machine_id_2),
                   (t.machine_id_3),
                   (t.machine_id_4),
                   (t.machine_id_5)
           ) v(machine_id)
      ) v;

在許多情況下,這可能在 SQL 服務器中具有最佳性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM