(NOTE: I'm answering this several days after the original post because it raises an interesting question.)
In the data provided there actually isn't a row missing for 1:45, its timestamp is 1:44:59.727. Log entries prior to 1:46 all have timestamps less than one second before the target time, then it switched to less than one second after. To account for this, the SQL should allow for a "gap" of 1 or more seconds before and after the target time.
Suggested approach:
1. Create a permanent table, daily_minutes, containing one row for each minute in a day (60 * 24 = 1440 rows).
2. Write a query that returns missing rows by comparing log timestamp to daily_minutes (allowing before/after gap).
NOTE: INSERT of missing rows is more complicated, you have to figure out what "average" to use, and deal with things like multiple consecutive missing rows.
-----------------
-- STEP 1: CREATE and load table daily_minutes (only need to do this once)
-----------------
-- DROP table if it already exists
IF EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_name = 'daily_minutes')
DROP TABLE daily_minutes;
CREATE TABLE daily_minutes
(minute_num INT, minute_ts TIME);
DECLARE @minute_num INT, @minute_ts TIME, @NTot INT;
SELECT @minute_num = 1,
@minute_ts = '00:00:00', -- start at 12AM
@NTot = (60 * 24); -- 1440 minutes in a day
-- Loop to INSERT a row for each minute
WHILE (@minute_num <= @NTot)
BEGIN
INSERT INTO daily_minutes SELECT @minute_num, @minute_ts;
SELECT @minute_num = @minute_num + 1; -- increment by 1
SELECT @minute_ts = DATEADD(minute, 1, @minute_ts); -- increment by 1 minute
END;
-----------------
-- STEP 2: SQL to identify missing rows
-- (NOTE: Log table name = log_table, columns = serial, advance, log_ts)
-----------------
DECLARE
@log_day DATETIME, -- the day of the log rows
@gap INT; -- allowed gap before/after each minute
SELECT
@log_day = CONVERT(CHAR(10), getdate()-1, 120), -- yesterday 12AM
@gap = 1; -- 1 second before or after
-- CTE includes rows for target minutes that DO exist in the log table
-- (DATEADDs allow for the before/after gap)
;WITH CTE AS
(
SELECT DM.minute_num, DM.minute_ts, LT.serial, LT.average, LT.log_ts
FROM daily_minutes DM INNER JOIN log_table LT
ON LT.[log_ts] BETWEEN DATEADD(s, -@gap, @log_day + DM.minute_ts)
AND DATEADD(s, +@gap, @log_day + DM.minute_ts)
)
-- Query returns rows for minutes that are NOT in CTE
SELECT (@log_day + DM.minute_ts) AS missed_target_ts,
DM.minute_num AS DM_minute_num,
DM.minute_ts AS DM_minute_ts
FROM daily_minutes DM
WHERE DM.minute_num
BETWEEN (SELECT MIN(minute_num) FROM CTE)
AND (SELECT MAX(minute_num) FROM CTE)
AND NOT EXISTS
(SELECT 1 FROM CTE
WHERE minute_num = DM.minute_num);