Выборка данных по датам с помощью GROUP BY, включая даты для которые данные отстутствуют

04.07.2010 23:38 / Артём Волк / 1017 просмотров / ...

К примеру, в БД MS SQL Server есть таблица Logs в которую заносятся данные об активности пользователей. По этим данным необходимо сделать выборку, например, подсчитать количество действий пользователей по дням месяца. По выходным и праздникам пользователи приложением не пользуются, поэтому при использовании обычного GROUP BY в результате выборки будут представлены только те даты, для которых нашлись данные, что, например, для построения графиков не подходит.

Общая идея такова: данные, полученные с помощью GROUP BY необходимо объединить с помощью JOIN со списком всех дат за период. Даты могут быть заготовлены в виде отдельной таблицы, но существует и более изящное решение с использованием CTE:

SqlCommand command = new SqlCommand();
command.Parameters.AddWithValue("DateFrom", dateFrom);
command.Parameters.AddWithValue("DateTo", dateTo.AddDays(1));
command.Parameters.AddWithValue("DateToExclusive", dateTo);

command.CommandText = String.Format(@"
	DECLARE @LowDate DATETIME
	SET @LowDate = @DateFrom;

	DECLARE @HighDate DATETIME
	SET @HighDate = @DateToExclusive;

	WITH [Dates]([MyDate])
	AS
	(
		SELECT @LowDate [MyDate]
		UNION ALL
		SELECT ([MyDate]+1) [MyDate]
		FROM [Dates]
		WHERE [MyDate] < @HighDate
	)
	SELECT 
		[Dates].[MyDate] AS [Date],
		ISNULL([Totals].[Count], 0) AS [Count]
	FROM [Dates]
	LEFT JOIN (
		SELECT 
			COUNT([NUserAudit]) AS [Count],
			CAST(FLOOR(CAST([LogTimestamp] AS FLOAT)) AS DATETIME) AS [Date]
		FROM [dbo].[Logs]
		WHERE 
			([LogTimestamp] BETWEEN @DateFrom AND @DateTo) 
		GROUP BY CAST(FLOOR(CAST([LogTimestamp] AS FLOAT)) AS DATETIME)
	) AS [Totals]	
	ON [Dates].[MyDate] = [Totals].[Date]
	ORDER BY [Date]
	OPTION (MAXRECURSION {0})					
", 
	(DateTo-DateFrom).Days)
 );

Несколько комментариев:

  • Для получения только даты из поля типа DATETIME применяется один из возможных способов: CAST(FLOOR(CAST([LogTimestamp] AS FLOAT)) AS DATETIME)
  • При большом диапазоне дат возможны проблемы с глубиной рекурсии в CTE, поэтому значение MAXRECURSION можно заранее ограничить.