SQL-запрос для сравнения продаж продуктов по месяцам

У меня есть представление базы данных «Ежемесячный статус», на основе которого мне нужно построить отчет. Данные в представлении выглядят примерно так:

Category | Revenue  |  Yearh  |  Month
Bikes      10 000      2008        1
Bikes      12 000      2008        2
Bikes      12 000      2008        3
Bikes      15 000      2008        1
Bikes      11 000      2007        2
Bikes      11 500      2007        3
Bikes      15 400      2007        4


... И так далее

Представление имеет категорию продукта, доход, год и месяц. Я хочу создать отчет, сравнивающий 2007 и 2008 годы, показывающий 0 для месяцев без продаж. Таким образом, отчет должен выглядеть примерно так:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400


Главное, на что следует обратить внимание, это то, что в месяце 1 есть продажи только в 2008 году, поэтому для 2007 года – 0. Кроме того, в месяце 4 нет продаж только в 2008 году, поэтому 0 – продажи в 2007 году, и по-прежнему отображается вверх.

Кроме того, отчет на самом деле относится к финансовому году, поэтому я хотел бы иметь пустые столбцы с 0 в обоих случаях, если в 5-м месяце не было продаж ни за 2007, ни за 2008 год.

Получившийся у меня запрос выглядит примерно так:

SELECT 
    SP1.Program,
    SP1.Year,
    SP1.Month,
    SP1.TotalRevenue,
    IsNull(SP2.TotalRevenue, 0) AS LastYearTotalRevenue

FROM PVMonthlyStatusReport AS SP1 
     LEFT OUTER JOIN PVMonthlyStatusReport AS SP2 ON 
                SP1.Program = SP2.Program AND 
                SP2.Year = SP1.Year - 1 AND 
                SP1.Month = SP2.Month
WHERE 
    SP1.Program = 'Bikes' AND
    SP1.Category = @Category AND 
    (SP1.Year >= @FinancialYear AND SP1.Year <= @FinancialYear + 1) AND
    ((SP1.Year = @FinancialYear AND SP1.Month > 6) OR 
     (SP1.Year = @FinancialYear + 1 AND SP1.Month <= 6))

ORDER BY SP1.Year, SP1.Month

Проблема с этим запросом заключается в том, что он не вернет четвертую строку в приведенном выше примере данных, поскольку в 2008 году у нас не было продаж, а в 2007 году они были.

Вероятно, это распространенный запрос/проблема, но мой SQL заржавел после столь долгой разработки внешнего интерфейса. Любая помощь приветствуется!

О, кстати, я использую SQL 2005 для этого запроса, поэтому, если есть какие-либо полезные новые функции, которые могут мне помочь, дайте мне знать.


person Jonas Follesø    schedule 19.08.2008    source источник


Ответы (6)


Case Statement — мой лучший друг по sql. Вам также нужна таблица времени, чтобы сгенерировать 0 оборотов за оба месяца.

Предположения основаны на наличии следующих таблиц:

продажи: Категория | Доход | год | Месяц

а также

тм: Год | Месяц (заполняется всеми датами, необходимыми для отчетности)

Пример 1 без пустых строк:

select
    Category
    ,month
    ,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
    ,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year

from
    sales

where
    year in (2008,2007)

group by
    Category
    ,month

ВОЗВРАТ:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400

Пример 2 с пустыми строками: я собираюсь использовать подзапрос (но другие не могут) и верну пустую строку для каждого продукта и комбинации года и месяца.

select
    fill.Category
    ,fill.month
    ,SUM(CASE WHEN YEAR = 2008 THEN Revenue ELSE 0 END) this_year
    ,SUM(CASE WHEN YEAR = 2007 THEN Revenue ELSE 0 END) last_year

from
    sales
    Right join (select distinct  --try out left, right and cross joins to test results.
                   product
                   ,year
                   ,month
               from
                  sales --this ideally would be from a products table
                  cross join tm
               where
                    year in (2008,2007)) fill


where
    fill.year in (2008,2007)

group by
    fill.Category
    ,fill.month

ВОЗВРАТ:

Category  |  Month  |  Rev. This Year  |  Rev. Last Year
Bikes          1          10 000               0
Bikes          2          12 000               11 000
Bikes          3          12 000               11 500
Bikes          4          0                    15 400
Bikes          5          0                    0
Bikes          6          0                    0
Bikes          7          0                    0
Bikes          8          0                    0

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

Вот некоторые дополнительные ресурсы. ДЕЛО http://www.4guysfromrolla.com/webtech/102704-1.shtml SQL SERVER 2005 PIVOT http://msdn.microsoft.com/en-us/library/ms177410.aspx

person jason saldo    schedule 20.08.2008

@Christian -- редактор уценки -- UGH; особенно когда предварительный просмотр и окончательная версия вашего поста не совпадают ... @Christian -- полное внешнее соединение -- полное внешнее соединение отменяется тем фактом, что в предложении WHERE есть ссылки на SP1, и применяется предложение WHERE после ПРИСОЕДИНЕНИЯ. Чтобы выполнить полное внешнее соединение с фильтрацией для одной из таблиц, вам нужно поместить предложение WHERE в подзапрос, чтобы фильтрация происходила перед соединением, или попытаться построить все ваши критерии WHERE на предложение JOIN ON, которое безумно уродливо. Ну, на самом деле нет красивого способа сделать это.

@Jonas: Учитывая это:

Кроме того, отчет на самом деле относится к финансовому году, поэтому мне бы хотелось иметь пустые столбцы с 0 в обоих случаях, если, скажем, в пятом месяце 2007 или 2008 года не было продаж.

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

Итак, я предлагаю сделать это в 5 шагов:
1. создать временную таблицу в том формате, в котором вы хотите, чтобы ваши результаты соответствовали
2. заполнить ее двенадцатью строками, от 1 до 12 в столбце месяца< br> 3. обновите столбец «Этот год», используя логику SP1
4. обновите столбец «Прошлый год», используя логику SP2
5. выберите из временной таблицы

Конечно, я думаю, что я работаю исходя из предположения, что для этого можно создать хранимую процедуру. Технически вы можете запустить всю эту партию в потоке, но такое уродство встречается очень редко. Если вы не можете сделать SP, я предлагаю вам вернуться к полному внешнему соединению через подзапрос, но это не даст вам строки, если в месяце не было продаж ни в одном году.

person Kevin Crumley    schedule 20.08.2008

Хитрость заключается в том, чтобы выполнить ПОЛНОЕ СОЕДИНЕНИЕ с ISNULL, чтобы получить объединенные столбцы из любой таблицы. Я обычно оборачиваю это в представление или производную таблицу, в противном случае вам также нужно использовать ISNULL в предложении WHERE.

SELECT 
    Program,
    Month,
    ThisYearTotalRevenue,
    PriorYearTotalRevenue
FROM (
    SELECT 
        ISNULL(ThisYear.Program, PriorYear.Program) as Program,
        ISNULL(ThisYear.Month, PriorYear.Month),
        ISNULL(ThisYear.TotalRevenue, 0) as ThisYearTotalRevenue,
        ISNULL(PriorYear.TotalRevenue, 0) as PriorYearTotalRevenue
    FROM (
        SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue 
        FROM PVMonthlyStatusReport 
        WHERE Year = @FinancialYear 
        GROUP BY Program, Month
    ) as ThisYear 
    FULL OUTER JOIN (
        SELECT Program, Month, SUM(TotalRevenue) as TotalRevenue 
        FROM PVMonthlyStatusReport 
        WHERE Year = (@FinancialYear - 1) 
        GROUP BY Program, Month
    ) as PriorYear ON
        ThisYear.Program = PriorYear.Program
        AND ThisYear.Month = PriorYear.Month
) as Revenue
WHERE 
    Program = 'Bikes'
ORDER BY 
    Month

Это должно дать вам ваши минимальные требования — строки с продажами либо в 2007, либо в 2008 году, либо в обоих. Чтобы получить строки без продаж в любом году, вам просто нужно ВНУТРЕННЕЕ СОЕДИНЕНИЕ с таблицей чисел 1-12 (вы делаете есть такой, не так ли?).

person Mark Brackett    schedule 25.08.2008

Насчет уценки - да, это расстраивает. Редактор сделал предварительный просмотр моей таблицы HTML, но после публикации она исчезла, поэтому пришлось удалить все форматирование HTML из сообщения...

@kcrumley Думаю, мы пришли к аналогичным выводам. Этот запрос легко становится очень уродливым. Я действительно решил это, прежде чем читать ваш ответ, используя аналогичный (но все же другой подход). У меня есть доступ для создания хранимых процедур и функций в базе данных отчетов. Я создал функцию Table Valued, принимая в качестве параметра категорию продукта и финансовый год. На основании этого функция заполнит таблицу, содержащую 12 строк. Строки будут заполнены данными из представления, если доступны какие-либо продажи, в противном случае строка будет иметь 0 значений.

Затем я соединяю две таблицы, возвращаемые функциями. Поскольку я знаю, что все столы будут иметь двенадцать рядов, это намного проще, и я могу присоединиться к категории продукта и месяцу:

SELECT 
    SP1.Program,
    SP1.Year,
    SP1.Month,
    SP1.TotalRevenue AS ThisYearRevenue,
    SP2.TotalRevenue AS LastYearRevenue
FROM GetFinancialYear(@Category, 'First Look',  2008) AS SP1 
     RIGHT JOIN GetFinancialYear(@Category, 'First Look',  2007) AS SP2 ON 
         SP1.Program = SP2.Program AND 
         SP1.Month = SP2.Month

Я думаю, что ваш подход, вероятно, немного чище, поскольку функция GetFinancialYear довольно грязная! Но, по крайней мере, это работает - что меня пока радует ;)

person Jonas Follesø    schedule 20.08.2008

Я могу ошибаться, но разве вы не должны использовать полное внешнее соединение, а не просто левое соединение? Таким образом, вы будете получать «пустые» столбцы из обеих таблиц.

http://en.wikipedia.org/wiki/Join_(SQL)#Full_outer_join< /а>

person Christian Hagelid    schedule 19.08.2008

Используя свод и Dynamic Sql, мы можем добиться этого результата.

SET NOCOUNT ON
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP

;With cte(Category , Revenue  ,  Yearh  ,  [Month])
AS
(
SELECT 'Bikes', 10000, 2008,1 UNION ALL
SELECT 'Bikes', 12000, 2008,2 UNION ALL
SELECT 'Bikes', 12000, 2008,3 UNION ALL
SELECT 'Bikes', 15000, 2008,1 UNION ALL
SELECT 'Bikes', 11000, 2007,2 UNION ALL
SELECT 'Bikes', 11500, 2007,3 UNION ALL
SELECT 'Bikes', 15400, 2007,4
)
SELECT * INTO #Temp FROM cte

Declare @Column nvarchar(max),
        @Column2 nvarchar(max),
        @Sql nvarchar(max)


SELECT @Column=STUFF((SELECT DISTINCT ','+ 'ISNULL('+QUOTENAME(CAST(Yearh AS VArchar(10)))+','+'''0'''+')'+ 'AS '+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp order by 1 desc FOR XML PATH ('')),1,1,'')

SELECT @Column2=STUFF((SELECT DISTINCT ','+ QUOTENAME(CAST(Yearh AS VArchar(10)))
FROM #Temp FOR XML PATH ('')),1,1,'')

SET @Sql= N'SELECT Category,[Month],'+ @Column +'FRom #Temp
            PIVOT
            (MIN(Revenue) FOR yearh IN ('+@Column2+')
            ) AS Pvt

            '
EXEC(@Sql)
Print @Sql

Результат

Category    Month   2008    2007
----------------------------------
Bikes       1       10000   0
Bikes       2       12000   11000
Bikes       3       12000   11500
Bikes       4       0       15400
person Community    schedule 29.06.2017