Как я могу создать серию месяцев для присоединения разреженных данных? - PullRequest
2 голосов
/ 25 июня 2010

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

Допустим, я выполнил следующий запрос в SQL Server, чтобы узнать ежемесячные продажи.

SELECT
    YEAR([timestamp]),
    MONTH([timestamp]),
    COUNT(*)
FROM table1
WHERE YEAR([timestamp]) = YEAR(GETDATE())
GROUP BY
    YEAR([timestamp]),
    MONTH([timestamp])
ORDER BY
    YEAR([timestamp]) DESC,
    MONTH([timestamp]) DESC;

Однако, если продажи произошли, например, только в мае и августе этого года, результат возврата будет выглядеть следующим образом:

2010    August    1234
2010    May       5678

Я хочу, чтобы мой набор результатов выглядел так:

2010    January
2010    February
2010    March
2010    April
2010    May        1234
2010    June
2010    July
2010    August     5678
2010    September
2010    October
2010    November
2010    December

Единственный способ, которым я знаю, это сделать так:

SELECT
    YEAR(GETDATE()),
    month_index.month_name,
    sales_data.sales
FROM (
    SELECT 'January' as month_name, 1 as month_number
    UNION
    SELECT 'February', 2
    UNION
    SELECT 'March', 3
    UNION
    SELECT 'April', 4
    UNION
    SELECT 'May', 5
    UNION
    SELECT 'June', 6
    UNION
    SELECT 'July', 7
    UNION
    SELECT 'August', 8
    UNION
    SELECT 'September', 9
    UNION
    SELECT 'October', 10
    UNION
    SELECT 'November', 11
    UNION
    SELECT 'December', 12
) as month_index
LEFT JOIN (
    SELECT
        YEAR([timestamp]) AS year_name,
        MONTH([timestamp]) AS month_name,
        COUNT(*) AS sales
    FROM table1
    WHERE YEAR([timestamp]) = GETDATE()
    GROUP BY
        YEAR([timestamp]),
        MONTH([timestamp])
) AS sales_data
ON month_index.month_name = sales_data.month_name
ORDER BY
    month_index.month_number DESC;

Есть ли лучший способ создания полной даты и буквенно-цифровой серии для объединения данных? И что это называется ?

Спасибо!

Ответы [ 5 ]

6 голосов
/ 25 июня 2010

попробуйте что-то вроде этого:

DECLARE @StartDate datetime
       ,@EndDate datetime
SELECT @StartDate=DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETDATE()),0) )
      ,@EndDate=GETDATE()

;with AllDates AS
(
    SELECT @StartDate AS DateOf
    UNION ALL
    SELECT DateAdd(month,1,DateOf)
        FROM AllDates
    WHERE DateOf<@EndDate
)
SELECT * FROM AllDates

вывод:

DateOf
-----------------------
2009-12-01 00:00:00.000
2010-01-01 00:00:00.000
2010-02-01 00:00:00.000
2010-03-01 00:00:00.000
2010-04-01 00:00:00.000
2010-05-01 00:00:00.000
2010-06-01 00:00:00.000
2010-07-01 00:00:00.000

(8 row(s) affected)
3 голосов
/ 26 июня 2010

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

SELECT 
  DATENAME(mm, date_val) AS month_name,  
  MONTH(date_val) AS month_number,  
  date_val as dt
FROM ( 
  SELECT DATEADD(mm, number, '2010-01-01') AS date_val
  FROM master.dbo.spt_values
  WHERE type = 'P'
  AND number BETWEEN 0 AND 11
) months

На основании моих тестов он работает быстрее, чем CTE.Я использую SQL Server 2008 Express.

Вот результаты теста с использованием SET STATISTICS IO ON и SET STATISTICS TIME ON

CTE:

(12 row(s) affected)
Table 'Worktable'. Scan count 2, logical reads 73, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 15 ms,  elapsed time = 64 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

Подзапрос:

(12 row(s) affected)
Table 'spt_values'. Scan count 1, logical reads 2, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 4 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

Хотя ваш оригинальный вопрос спрашивает, как это называется.Я не знаю имени для этого;может быть что-то вроде «левого внешнего соединения с серией»?

Еще одна дополнительная деталь, которую нужно добавить: когда вы соединяетесь с таблицей месяцев или даже когда делаете исходный запрос, обычно рекомендуется избегать использования такой функции, как YEAR([отметка времени]) в левой части предложения WHERE.

Таким образом, этот код:

SELECT                     
    YEAR([timestamp]),                     
    MONTH([timestamp]),                     
    COUNT(*)                     
FROM table1                     
WHERE YEAR([timestamp]) = YEAR(GETDATE())                     
GROUP BY                     
    YEAR([timestamp]),                     
    MONTH([timestamp])

... вызовет сканирование индекса (при условии, что временная метка проиндексирована), поскольку ГОД ([временная метка]) должен оцениваться для каждой строки.В таблице строк размером более 1 м это будет означать низкую производительность.

Таким образом, вместо этого вы обычно увидите следующую рекомендацию:

SELECT                     
    YEAR([timestamp]),                     
    MONTH([timestamp]),                     
    COUNT(*)                     
FROM #table1                     
WHERE [timestamp] >= DATEADD(YY, DATEDIFF(YY, 0, GETDATE()), 0) -- First day of this year
AND   [timestamp] < DATEADD(YY, DATEDIFF(YY, 0, GETDATE()) + 1, 0) -- First day of next year
GROUP BY                     
    YEAR([timestamp]),                     
    MONTH([timestamp])

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

3 голосов
/ 25 июня 2010

Подобные запросы являются одной из основных причин того, что многие опытные администраторы баз данных или программисты баз данных хранят календарную таблицу в своих базах данных.

2 голосов
/ 25 июня 2010

Я с KM, что для SQL Server 2005+ вы можете использовать рекурсивный CTE:

WITH months AS (
  SELECT DATENAME(mm, '2010-01-01') AS month_name, 
         MONTH('2010-01-01') AS month_number, 
         CAST('2010-01-01' AS DATETIME) AS dt
  UNION ALL
  SELECT DATENAME(mm, DATEADD(mm, 1, m.dt)),
         MONTH(DATEADD(mm, 1, m.dt)),
         DATEADD(mm, 1, m.dt)
    FROM months m
   WHERE DATEADD(mm, 1, m.dt) <= '2010-12-01')
   SELECT x.month_name,
          y.*
     FROM months x
LEFT JOIN your_table y ON MONTH(y.date) = x.month_number

В конце концов, в прошлый раз, когда KM & поболтал об этом - мы обнаружили, что рекурсивный CTE несколько более эффективен, чем использование таблицы чисел .

1 голос
/ 25 июня 2010

как насчет создания новой таблицы под названием Месяцы: затем заполнить ее данными, к которым вы можете присоединиться?

...