Вставить Даты в возврате из запроса, где нет

Мы создаем запрос для подсчета количества событий в час, в день. В большинстве дней есть часы, в которых нет активности, и поэтому при запуске запроса подсчитывается количество действий в час, но есть пробелы, и запрос исключает их. Мы по-прежнему хотим показать часы, которые не имеют активности, и отображают нуль, чтобы затем можно было наметить нулевое значение. Используемый нами запрос выглядит так:

select datepart(Year, dev_time) as Year,
    datepart(Month, dev_time) as Month,
    datepart(Day, dev_time) as Day,
    datepart(Hour, dev_time) as Hour,
    count(tdm_msg) as Total_ACTIVITES
from TCKT_ACT
where tdm_msg = ‘4162′ and dev_time >= DATEADD(day, - 1, GETDATE())
group by datepart(Year, dev_time) ,
    datepart(Month, dev_time) ,
    datepart(Day, dev_time),
    datepart(Hour, dev_time)
order by datepart(Year, dev_time) asc,
    datepart(Month, dev_time) asc,
    datepart(Day, dev_time) asc,
    datepart(Hour, dev_time) asc

Ответ 1

Вам понадобится таблица дней и часов, а затем вам нужно будет выполнить внешнее соединение между этой таблицей и вашим запросом. Вот как я это сделаю. Обратите внимание, что это решение будет работать только в SQL Server 2005 и 2008. Если у вас нет этих платформ, вам нужно будет создать таблицу раз в вашей базе данных, из которой вы можете присоединиться:

DECLARE @MinDate DATETIME;
SET @MinDate =  CONVERT(varchar, GETDATE(), 101);

WITH times AS (
    SELECT @MinDate as dt, 1 as depth
    UNION ALL
    SELECT DATEADD(hh, depth, @MinDate), 1 + depth as depth
    FROM times
    WHERE DATEADD(hh, depth, @MinDate) <= GETDATE())
SELECT DATEPART(YEAR, t.dt) as [Year],
    DATEPART(MONTH, t.dt) as [Month],
    DATEPART(DAY, t.dt) as [Day],
    DATEPART(HOUR, t.dt) as [Hour],
    COUNT(tdm_msg) as Total_ACTIVITES
FROM times t
LEFT JOIN (SELECT * FROM TCKT_ACT WHERE tdm_msg = '4162' and dev_time >= @MinDate) a
    ON  DATEPART(HOUR, t.dt)  = DATEPART(HOUR, a.dev_time)
    AND MONTH(t.dt) = MONTH(a.dev_time)
    AND DAY(t.dt)   = DAY(a.dev_time)
    AND YEAR(t.dt)  = YEAR(a.dev_time)
GROUP BY DATEPART(YEAR, t.dt) ,
    DATEPART(MONTH, t.dt) ,
    DATEPART(DAY, t.dt),
    DATEPART(HOUR, t.dt)
ORDER BY DATEPART(YEAR, t.dt) asc,
    DATEPART(MONTH, t.dt) asc,
    DATEPART(DAY, t.dt) asc,
    DATEPART(HOUR, t.dt) asc
OPTION (MAXRECURSION 0); /* Just in case you want a longer timespan later on... */

Обратите внимание, что оператор WITH наверху называется рекурсивным общим табличным выражением и является хорошим способом создания последовательных таблиц с относительно небольшим количеством элементов, например, здесь.

Ответ 2

Сначала я создал функцию таблицы на основе рекурсивного общего запроса таблицы, описанного в Дэйв Маркл (спасибо, что показал мне этого Дэйва!). Это очень мило, потому что мне нужно только один раз выполнить функцию, и я могу использовать ее для анализа любых интервалов.

if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go

create function fn_daterange
   (
   @MinDate as datetime,
   @MaxDate as datetime,
   @intval  as datetime
   )
returns table
--**************************************************************************
-- Procedure: fn_daterange()
--    Author: Ron Savage
--      Date: 12/16/2008
--
-- Description:
-- This function takes a starting and ending date and an interval, then
-- returns a table of all the dates in that range at the specified interval.
--
-- Change History:
-- Date        Init. Description
-- 12/16/2008  RS    Created.
-- **************************************************************************
as
return
   WITH times (startdate, enddate, intervl) AS
      (
      SELECT @MinDate as startdate, @MinDate + @intval - .0000001 as enddate, @intval as intervl
         UNION ALL
      SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
      FROM times
      WHERE startdate + intervl <= @MaxDate
      )
   select startdate, enddate from times;

go

Итак, если вы сделаете выбор из этой функции, вы сами получите таблицу временных интервалов:

fn_daterange ('12/14/2008 10:00:00 ', '12/14/2008 20:00:00', '01: 00: 00 ')

возвращает:

startdate               enddate                 intervl                 
----------------------- ----------------------- ----------------------- 
2008-12-14 10:00:00.000 2008-12-14 10:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 11:00:00.000 2008-12-14 11:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 12:00:00.000 2008-12-14 12:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 13:00:00.000 2008-12-14 13:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 14:00:00.000 2008-12-14 14:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 15:00:00.000 2008-12-14 15:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 16:00:00.000 2008-12-14 16:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 17:00:00.000 2008-12-14 17:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 18:00:00.000 2008-12-14 18:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 19:00:00.000 2008-12-14 19:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 20:00:00.000 2008-12-14 20:59:59.997 1900-01-01 01:00:00.000 

Затем я сделал примерную таблицу данных событий:

    eventdate               eventnote            
    ----------------------- -------------------- 
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        

    22 Row(s) affected

Затем я подключил их вместе с LEFT OUTER JOIN следующим образом:

select
   dr.startdate,
   dr.enddate,
   count(me.eventdate) as eventcount
from
   fn_daterange('12/14/2008 10:00:00', '12/14/2008 20:00:00', '01:00:00' ) dr

   LEFT OUTER JOIN myevents me
      on ( me.eventdate between dr.startdate and dr.enddate)
group by
   dr.startdate,
   dr.enddate


startdate               enddate                 eventcount 
----------------------- ----------------------- ---------- 
2008-12-14 10:00:00.000 2008-12-14 10:59:59.993 7          
2008-12-14 11:00:00.000 2008-12-14 11:59:59.993 5          
2008-12-14 12:00:00.000 2008-12-14 12:59:59.993 0          
2008-12-14 13:00:00.000 2008-12-14 13:59:59.993 0          
2008-12-14 14:00:00.000 2008-12-14 14:59:59.993 3          
2008-12-14 15:00:00.000 2008-12-14 15:59:59.993 0          
2008-12-14 16:00:00.000 2008-12-14 16:59:59.993 0          
2008-12-14 17:00:00.000 2008-12-14 17:59:59.993 0          
2008-12-14 18:00:00.000 2008-12-14 18:59:59.993 0          
2008-12-14 19:00:00.000 2008-12-14 19:59:59.993 7          
2008-12-14 20:00:00.000 2008-12-14 20:59:59.993 0          

11 Row(s) affected

HOLY CRAP, который сладок - я могу использовать его для всех видов анализа на работе!: -)

Спасибо Фреду за вопрос и Дэйв за информацию о общих табличных запросах!

Рон

Ответ 3

У нас была аналогичная проблема с некоторым программным обеспечением для мониторинга производительности, но, находясь в магазине мейнфреймов DB2/z, мы мертвы, не имея необходимости делать гимнастику SQL, чтобы получить такие результаты. SQL-запросы, которые выполняют "функции" в каждой строке, которую они извлекают, заведомо незаменимы, и администраторы баз данных будут смеяться над нами, если мы попытаемся их использовать.

Вместо этого нам было проще реорганизовать схему базы данных, чтобы включить подсчет событий в каждой строке (по-видимому, наши администраторы баз данных не против использовать больше дискового пространства, а просто больше хруст процессора). В вашем случае это будет добавить столбец с именем tdm_quant, который вы бы установили в 1 для каждой строки, которую вы вставляете (т.е. Каждое событие).

Затем пятое поле вашего запроса изменяется от count(tdm_msg) до sum(tdm_quant), которое достигнет того же результата.

В дополнение к этому вы можете вставить специальную запись (один раз в час или 24 из них в начале каждого дня или заполнить все годы, которые стоит 1 января, если хотите), где установлено поле tdm_quant до нуля. Будучи нулевыми, эти записи не будут влиять на sum(tdm_quant), но вы получите желаемое поведение, строка, возвращаемая для каждого часа дня, которая будет иметь нулевое значение как Total_ACTIVITIES, где не произошло никаких событий в этот час.

Остальная часть вашего запроса не потребуется изменять.

Ответ 4

Похоже, вы можете использовать "левое внешнее соединение", используя другую таблицу с номерами от 1 до 24...

Ответ 5

Основной ответ здесь включает левое внешнее соединение (LOJ) и явное COUNT(column), поскольку оно не учитывает значения NULL, но COUNT (*) подсчитывает все строки. Жесткая часть создает таблицу, с помощью которой можно выполнить LOJ. Предложение WITH и рекурсивное решение будут работать в нескольких СУБД (MS SQL Server, по-видимому, и почти наверняка DB2 - возможно, и другие).

Многие СУБД поддерживают временные таблицы и хранимые процедуры; комбинацию можно использовать для заполнения таблицы с соответствующим набором значений для поля даты/времени, а затем сделать LOJ против этой таблицы (или, точнее, FROM temp_table LEFT OUTER JOIN main_table...). Не так аккуратно и аккуратно, но работает в большинстве мест.