Sql server 如何加上「;“空的”;在SQL server中查询每分钟的记录(无数据)

Sql server 如何加上「;“空的”;在SQL server中查询每分钟的记录(无数据),sql-server,tsql,Sql Server,Tsql,我的查询由日期、时间(基本上是时间戳)和计算每小时薪酬的字段组成 date time comp/H ---------- ----- ---------------------- 2019-09-10 07:01 13640,416015625 2019-09-10 07:02 8970,3193359375 2019-09-10 07:03 6105,4990234375 2019-09-10 07:04 7189,77880859375 2019-09-10 07:0

我的查询由日期、时间(基本上是时间戳)和计算每小时薪酬的字段组成

date        time      comp/H
---------- ----- ----------------------
2019-09-10 07:01 13640,416015625
2019-09-10 07:02 8970,3193359375
2019-09-10 07:03 6105,4990234375
2019-09-10 07:04 7189,77880859375
2019-09-10 07:08 2266,73657226563
2019-09-10 07:57 163,527984619141
我想填补时间戳之间的空白,并为没有分配任何数据的每分钟添加一条新记录(例如,添加07:05、07:06、07:07的记录)。我会为这些记录的comp/h字段指定一个0值,但我不知道如何做

最终的目标是为上面的数据绘制一个折线图,从中可以直观地看到停机时间。 (因此“空记录”的值为0)

原始查询:

select cast(p_timestamp as date) as 'datum', CONVERT(VARCHAR(5), p_timestamp, 108) as 'time', avg(((AantalPCBperPaneel*(AantalCP+AantalQP))/deltasec)* 3600) as 'comp/h'
from Testview3
where p_timestamp > '2019-09-01' 
group by CONVERT(VARCHAR(5), p_timestamp, 108), cast(p_timestamp as date)
order by cast(p_timestamp as date) asc , CONVERT(VARCHAR(5), p_timestamp, 108) asc

您可以尝试以下代码:

填充一个模拟场景

SET DATEFORMAT ymd;

DECLARE @mockTable TABLE([date] DATE,[time] TIME,[comp/H] DECIMAL(20,5));
INSERT INTO @mockTable VALUES
 ('2019-09-10','07:01',13640.416015625)
,('2019-09-10','07:02',8970.3193359375)
,('2019-09-10','07:03',6105.4990234375)
,('2019-09-10','07:04',7189.77880859375)
,('2019-09-10','07:08',2266.73657226563)
,('2019-09-10','07:57',163.527984619141);
--过滤到一天(只是为了保持简单…)

--询问

WITH CountMinutes(Nmbr) AS
(
    SELECT TOP((SELECT DATEDIFF(MINUTE,MIN([time]),MAX([time])) 
                FROM @mockTable 
                WHERE [date]=@TheDate)+1) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1  
    FROM master..spt_values
)
SELECT @TheDate AS [date]
      ,CAST(DATEADD(MINUTE,mc.Nmbr,(SELECT MIN(t.[time]) FROM @mockTable t WHERE t.[date]=@TheDate)) AS TIME) AS [time]
      ,t2.[comp/H]
FROM CountMinutes mc 
LEFT JOIN @mockTable t2 ON t2.[date]=@TheDate AND t2.[time]=CAST(DATEADD(MINUTE,mc.Nmbr,(SELECT MIN(t.[time]) FROM @mockTable t WHERE t.[date]=@TheDate)) AS TIME);
简而言之:

我们需要一个理货台,只是一个运行数字的列表。我使用的是
master..spt\u值
,它只不过是一个包含大量行的预填充表。您可以选择任何现有表,其中有足够的行覆盖该范围。我们不需要行的值,只需要集合的计数器。您还可以阅读有关理货表的内容,以及如何结合
值()
交叉连接创建理货表。这里的神奇之处在于计算出的
TOP()
子句和
行数()
的组合

因此,CTE将返回反映分钟数的数字列表

select将使用此列表和
DATEADD()
创建时间值的无间隙列表。现在我们必须
LEFT JOIN
您的集合才能查看数据,其中有数据

更新关于性能的一些想法 在Konstantin Surkov回答下面的评论中,我指出,使用循环的计数器函数将非常慢。康斯坦丁让我测量一下:

这里我将比较三种方法

  • 康斯坦丁斯环
  • 正在进行的简单统计
  • 基于表的方法
试一试:

USE master;
GO
CREATE DATABASE testCounter;
GO
USE testCounter;
GO
--Konstantins多语句TVF使用WHILE

create function rangeKonstantin(@from int, @to int) returns @table table(val int) as
begin
    while @from <= @to begin
        insert @table values(@from)
        set @from = @from + 1;
    end;
    return;
end;
GO
--和一个简单的静态数字表
--以及使用此表的函数

CREATE TABLE persistantNumbers(val INT NOT NULL UNIQUE);
GO
--let's fill it 
INSERT INTO persistantNumbers SELECT val FROM rangeKonstantin(0,1500000) --1.5 mio rows
GO

create function rangeTable(@from int,@to int) returns table as
return
SELECT val FROM persistantNumbers WHERE val BETWEEN @from AND @to;
GO
--在这里我们可以保存结果

CREATE TABLE Result (ID INT IDENTITY,Measurement VARCHAR(100),TimeKonst INT, TimeShnugo INT, TimeTable INT, tmpCount INT)
GO
SELECT Measurement
      ,AVG(TimeKonst) AS AvgKonst
      ,AVG(TimeShnugo) AS AvgShnugo
      ,AVG(TimeTable) AS AvgTable 
FROM Result 
GROUP BY Measurement;

SELECT * FROM Result ORDER BY Measurement,ID;
--您可以使用这些行来测试代码,或者将其保留在注释之外,以测试引擎缓存和使用统计信息的能力

--DBCC FREESESSIONCACHE
--DBCC FREEPROCCACHE
--DBCC DROPCLEANBUFFERS
--我们需要一个
DATETIME2
来获取行动前的时刻

DECLARE @d DATETIME2; 
--以及一个带有可变部分的范围,以避免通过缓存结果产生任何偏差

DECLARE @range INT=300 + (SELECT COUNT(*) FROM Result)
--现在让我们开始:从简单的计数到x范围

SET @d=SYSUTCDATETIME();
SELECT * into tmp FROM rangeKonstantin(0,@range*@range);
INSERT INTO Result(Measurement,TimeKonst,tmpCount) SELECT 'a count to @range*@range',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;

SET @d=SYSUTCDATETIME();
SELECT * into tmp FROM rangeShnugo(0,@range*@range);
INSERT INTO Result(Measurement,TimeShnugo,tmpCount) SELECT 'a count to @range*@range',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;

SET @d=SYSUTCDATETIME();
SELECT * into tmp FROM rangeTable(0,@range*@range); 
INSERT INTO Result(Measurement,TimeTable,tmpCount) SELECT 'a count to @range*@range',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;
--和-更重要的-使用
APPLY
调用具有逐行更改参数的函数

SET @d=SYSUTCDATETIME();
select h.val hour, m.val minute into tmp from rangeKonstantin(0, @range) h cross apply rangeKonstantin(0, h.val) m;
INSERT INTO Result(Measurement,TimeKonst,tmpCount) SELECT 'c @range apply',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;

SET @d=SYSUTCDATETIME();
select h.val hour, m.val minute into tmp from rangeShnugo(0, @range) h cross apply rangeShnugo(0, h.val) m;
INSERT INTO Result(Measurement,TimeShnugo,tmpCount) SELECT 'c @range apply',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;

SET @d=SYSUTCDATETIME();
select h.val hour, m.val minute into tmp from rangeTable(0, @range) h cross apply rangeTable(0, h.val) m;
INSERT INTO Result(Measurement,TimeTable,tmpCount) SELECT 'c @range apply',DATEDIFF(millisecond,@d,SYSUTCDATETIME()),(SELECT Count(*) FROM tmp);
DROP TABLE tmp;
--我们用一个简单的
GO 10

GO 10 --do the whole thing 10 times
--现在让我们获取结果

CREATE TABLE Result (ID INT IDENTITY,Measurement VARCHAR(100),TimeKonst INT, TimeShnugo INT, TimeTable INT, tmpCount INT)
GO
SELECT Measurement
      ,AVG(TimeKonst) AS AvgKonst
      ,AVG(TimeShnugo) AS AvgShnugo
      ,AVG(TimeTable) AS AvgTable 
FROM Result 
GROUP BY Measurement;

SELECT * FROM Result ORDER BY Measurement,ID;
--清理

使用在强大机器上运行的v2014上的缓存和统计数据,范围=300的结果:

Measurement                 AvgKonst    AvgShnugo   AvgTable
a count to @range*@range    626         58          34
c @range apply              357         17          56
我们可以看到,使用
WHILE
的TVF比其他方法慢得多

在真实场景中,使用的范围(300到90k)相当小。在这里我重复了一遍,范围为1000(计数超过1百万),仍然不是很大

Measurement                 AvgKonst    AvgShnugo   AvgTable
a count to @range*@range    6800        418         321
c @range apply              3422        189         177
我们学到的是:

  • 对于小范围计数,飞行计数似乎是最好的
  • 当集合大小增加时,任何计数方法都会严重缩放
  • 基于表格的方法最好用于大型集合
循环不起作用时,带
的多站TVF不起作用

更新2在v2017上重复了上述内容
在本地运行SQL Server 2017的中型笔记本电脑上,对于范围=1000,我得到以下结果:

Measurement                 AvgKonst    AvgShnugo   AvgTablea 
count to @range*@range      10704       282         214
c @range apply              5671        1133        210
我们看到,在更大的局数下,表格法显然获胜

值得一提的是:引擎尝试预测行数,以便找到最佳计划。多语句TVF总是仅用一行进行估计。一个简单的计数器也将使用一行进行估算。但是使用索引表,引擎将能够预测行并找到更好的计划。

创建或更改函数范围(@from int,@to int)将@table table(val int)返回为
create or alter function range(@from int, @to int) returns @table table(val int) as
begin
    while @from <= @to begin
        insert @table values(@from)
        set @from = @from + 1;
    end;
    return;
end;

select h.val hour, m.val minute from range(0, 23) h cross join range(0, 59) m;
开始
而@from嗯。。。也许做一个“分钟”表,然后离开加入它?我也这么想,虽然这是该公司的SQL server,我没有创建表的授权。。。我只能编辑视图和运行查询。您需要覆盖的时间范围有多大?小时?年?@JacobH在这一点上,我将满足于1周的数据。一段时间的循环和多步TVF是需要避免的。。。更好的方法是基于集合的方法。这是基于集合的方法。交叉连接,如果你没有注意到的话。它不是。测量它,你会惊讶它的速度有多快。康斯坦丁,在我的回答(更新部分)中,你会找到我的测量值。@Shnugo如果你仔细看,我的解决方案是交叉连接小范围。和你一样,真的。区别在于-在我的解决方案中,您不必显式键入范围内的所有数字。您的“性能度量”歪曲了我的解决方案。
create or alter function range(@from int, @to int) returns @table table(val int) as
begin
    while @from <= @to begin
        insert @table values(@from)
        set @from = @from + 1;
    end;
    return;
end;

select h.val hour, m.val minute from range(0, 23) h cross join range(0, 59) m;