SQL для подсчета событий по дате - PullRequest
2 голосов
/ 06 апреля 2009

Мне кажется, что я уже видел этот вопрос, но ни SO, ни Google не помогают мне ... может быть, я просто не знаю, как сформулировать вопрос. Мне нужно посчитать количество событий (в данном случае, логинов) в день за определенный промежуток времени, чтобы я мог составить график использования сайта. На данный момент у меня есть следующий запрос:

select 
   count(userid) as numlogins, 
   count(distinct userid) as numusers, 
   convert(varchar, entryts, 101) as date 
from 
   usagelog 
group by 
   convert(varchar, entryts, 101)

Это делает большую часть того, что мне нужно (я получаю строку за дату как вывод, содержащий общее количество входов в систему и количество уникальных пользователей на эту дату). Проблема в том, что если никто не входит в систему в определенную дату, в наборе данных не будет строки для этой даты. Я хочу, чтобы он добавил в строках, указывающих ноль логинов для этих дат. Есть два подхода к решению этой проблемы, и ни один из них не кажется мне очень элегантным.

  1. Добавить в результирующий набор столбец, в котором указано количество дней между началом периода и датой текущей строки. Когда я строю вывод своей диаграммы, я буду отслеживать это значение и, если следующая строка не равна текущей строке плюс один, вставлять в диаграмму нули для каждого из пропущенных дней.
  2. Создайте таблицу «date», в которой есть все даты в интересующем периоде и внешнее объединение против него. К сожалению, в системе, над которой я работаю, уже есть таблица для этой цели, которая содержит строку для каждой даты в далеком будущем ... Мне это не нравится, и я предпочел бы избегать ее использования, тем более что Таблица предназначена для другого модуля системы и, таким образом, вводит зависимость от того, что я сейчас разрабатываю.

Какие-нибудь лучшие решения или советы по улучшению условий поиска для Google? Спасибо.

Ответы [ 6 ]

3 голосов
/ 06 апреля 2009

Честно говоря, я бы делал это программно при построении окончательного результата. По сути, вы пытаетесь прочитать что-то из базы данных, которой нет (данные за дни, которые не имеют данных). SQL на самом деле не предназначен для такого рода вещей.

Однако, если вы действительно хотите это сделать, таблица «даты» кажется вашим лучшим вариантом. Чтобы сделать его немного лучше, вы можете сгенерировать его на лету, используя функции даты вашей БД и производную таблицу.

2 голосов
/ 06 апреля 2009

Недавно мне пришлось сделать то же самое. Вот как я это сделал в T-SQL ( YMMV по скорости, но я нашел его достаточно быстродействующим на пару миллионов строк данных о событиях):

DECLARE @DaysTable TABLE ( [Year] INT, [Day] INT )

DECLARE @StartDate DATETIME
SET @StartDate = whatever

WHILE (@StartDate <= GETDATE())
BEGIN

  INSERT INTO @DaysTable ( [Year], [Day] )
  SELECT DATEPART(YEAR, @StartDate), DATEPART(DAYOFYEAR, @StartDate)

  SELECT @StartDate = DATEADD(DAY, 1, @StartDate)
END

-- This gives me a table of all days since whenever
-- you could select @StartDate as the minimum date of your usage log)

SELECT days.Year, days.Day, events.NumEvents
FROM @DaysTable AS days
LEFT JOIN (
  SELECT
    COUNT(*) AS NumEvents
    DATEPART(YEAR, LogDate) AS [Year],
    DATEPART(DAYOFYEAR, LogDate) AS [Day]
  FROM LogData
  GROUP BY
    DATEPART(YEAR, LogDate),
    DATEPART(DAYOFYEAR, LogDate)
) AS events ON days.Year = events.Year AND days.Day = events.Day
1 голос
/ 06 апреля 2009

Вариант 1 Вы можете создать временную таблицу и вставить даты с диапазоном, а также выполнить левое внешнее объединение с помощью uselog Вариант 2 Вы можете программно вставить пропущенные даты при оценке результирующего набора для получения окончательного результата

1 голос
/ 06 апреля 2009

Я обычно использую стратегию UNION с противоположным запросом, обычно запросом, который извлекает данные для несуществующих строк.

Если бы я хотел получить среднюю оценку за курс, но некоторые курсы не посещали какие-либо студенты, мне нужно было бы объединиться с теми, которые никто не делал, чтобы отобразить строку для каждого класса:

SELECT AVG(mark), course FROM `marks` 
    UNION
SELECT NULL, course FROM courses WHERE course NOT IN
    (SELECT course FROM marks)

Ваш запрос будет более сложным, но должен применяться тот же принцип. Вам действительно может понадобиться таблица дат для вашего второго запроса

1 голос
/ 06 апреля 2009

Создайте таблицу памяти (табличную переменную), в которую вы вставляете диапазоны дат, а затем внешне присоединяете к ней таблицу логинов. Группируйте по дате начала, после чего вы сможете выполнять агрегирование и вычисления.

0 голосов
/ 06 апреля 2009
WITH q(n) AS
          (
          SELECT  0
          UNION   ALL
          SELECT  n + 1
          FROM    q
          WHERE   n < 99
          ),
    qq(n) AS 
          (
          SELECT  0
          UNION   ALL
          SELECT  n + 1
          FROM    q
          WHERE   n < 99
          ),
    dates AS
          (
          SELECT  q.n * 100 + qq.n AS ndate
          FROM    q, qq
          )
SELECT    COUNT(userid) as numlogins,
          COUNT(DISTINCT userid) as numusers,
          CAST('2000-01-01' + ndate AS DATETIME) as date
FROM      dates
LEFT JOIN
          usagelog
ON        entryts >= CAST('2000-01-01' AS DATETIME) + ndate
          AND entryts < CAST('2000-01-01' AS DATETIME) + ndate + 1
GROUP BY
          ndate

Это выберет до 10,000 дат, построенных на лету, что должно быть достаточно для 30 лет.

SQL Server имеет ограничение 100 рекурсий на CTE, поэтому внутренние запросы могут возвращать до 100 строк в каждой.

Если вам нужно больше, чем 10,000, просто добавьте третий CTE qqq(n) и перекрестное соединение с ним в dates.

...