如何在 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服务器不支持语法。不幸的是,我不太了解(没有内化会更准确)dense_rank walk-around 是如何工作的:
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 订购并使用 rows between
?
dense_rank()
给出当前记录的密集排名。当您 运行 首先使用 ASC
排序顺序时,您会从第一个元素获得当前记录的密集排名(唯一值排名)。当您 运行 和 DESC
顺序时,您会从上一条记录中获取当前记录的密集排名。然后你去掉1,因为当前记录的密集排名被统计了两次。这给出了整个分区中的总唯一值(并为每一行重复)。
由于dense_rank
不支持frames
,您不能直接使用此解决方案。您需要通过其他方式生成 frame
。一种方法是 JOIN
对相同的 table 进行适当的 unique id
比较。然后,您可以在组合版本上使用dense_rank
。
请查看以下解决方案。假设您在 table 中有一个唯一的记录密钥 (record_id
)。如果您没有唯一键,请在第一个 CTE 之前添加另一个 CTE 并为每条记录生成一个唯一键(使用 new_id()
函数或使用 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 个限制:
如果帧少于 6 条记录,则 LAG()
函数将为 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
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;
如果每个模型有很多行,您还可以使用一个(麻烦的)技巧使用 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 服务器中具有最佳性能。
顺带回答这个可爱的问题: 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服务器不支持语法。不幸的是,我不太了解(没有内化会更准确)dense_rank walk-around 是如何工作的:
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 订购并使用 rows between
?
dense_rank()
给出当前记录的密集排名。当您 运行 首先使用 ASC
排序顺序时,您会从第一个元素获得当前记录的密集排名(唯一值排名)。当您 运行 和 DESC
顺序时,您会从上一条记录中获取当前记录的密集排名。然后你去掉1,因为当前记录的密集排名被统计了两次。这给出了整个分区中的总唯一值(并为每一行重复)。
由于dense_rank
不支持frames
,您不能直接使用此解决方案。您需要通过其他方式生成 frame
。一种方法是 JOIN
对相同的 table 进行适当的 unique id
比较。然后,您可以在组合版本上使用dense_rank
。
请查看以下解决方案。假设您在 table 中有一个唯一的记录密钥 (record_id
)。如果您没有唯一键,请在第一个 CTE 之前添加另一个 CTE 并为每条记录生成一个唯一键(使用 new_id()
函数或使用 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 个限制:
如果帧少于 6 条记录,则
LAG()
函数将为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
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;
如果每个模型有很多行,您还可以使用一个(麻烦的)技巧使用 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 服务器中具有最佳性能。