Рассчитать среднее значение за последние 3 месяца за каждый прошедший 3 - PullRequest
0 голосов
/ 27 сентября 2018

Я использую SQL Server 2014. У меня есть такая таблица

   create table revenue (id varchar(2), trasdate date, revenue int);
   insert into revenue(id, trasdate, revenue)
   values ('aa', '2018/09/01', 1234.5),
   ('aa' , '2018/08/04', 450),
   ('aa', '2018/07/03',500),
   ('aa', '2018/06/04',600),
  ('ab', '2018/09/01', 1234.5),
  ('ab' , '2018/08/04', 450),
    ('ab', '2018/07/03',500),
    ('ab', '2018/06/04',600),
   ('ab', '2018/05/03', 200),
   ('ab', '2018/04/02', 150),
  ('ab', '2018/03/01', 350),
  ('ab', '2018/02/05', 700),
  ('aa', '2018/01/07', 400)
;

Я готовлю запрос SQL для создания отчета SSRS.Я хочу рассчитать среднее значение за последние 3 месяца для текущего и каждого прошедшего 3-го месяца с результатом, как показано ниже.Как мы в сентябре месяце прямо сейчас.Результат должен показать что-то вроде этого:

**id    Period  Revenue_3Mon**
aa  March-May   233
aa  June-Aug    516
ab  March-May   233
ab  June-Aug    516

Хотя я могу разобраться с колонкой Period.В основном я фокусировался на получении дохода_3Мон.Поэтому я сначала попытался с приведенным ниже запросом после некоторого поиска.Но этот запрос выдает ошибку как неверный синтаксис рядом с «строками», и если я удаляю строки из запроса, он выдает ошибку как неправильный синтаксис рядом с ключевым словом «между».И неправильный синтаксис рядом с i.

select i.id,i.mon,
   avg([i.mon_revenue]) over (partition by i.id, i.mon order by [i.id], 
  [i.mon] rows between 3  preceding and 1 preceding row) as revenue_3mon -- 
--  using 3 preceding and 1 preceding row you exclude the current row
 from (select a.id, month(a.trasdate) as mon,
         sum(a.revenue) as mon_revenue
  from revenue a
  group by a.id, month(a.trasdate)) i
 group by i.id, i.mon
 order by i.id,i.mon;

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

Declare @count as int;
declare @max as int;
set @count = 4
declare @temp as table (id varchar(2), monthoftrasdate int, revenue int, 
[3monavg] int);
SET @MAX = (SELECT distinct MAX(a.ROWNUM) FROM (SELECT id, month(trasdate) 
 as mon, SUM(revenue) TotalRevenue,
       -- sum(revenue) as mon_revenue,
       ROW_NUMBER() OVER(PARTITION BY ID ORDER BY MONTH(TRASDATE)) AS ROWNUM
        FROM revenue
       GROUP BY ID, MONTH(TRASDATE)         
        ) A GROUP BY A.ID);

     while (@count <= @max )
    begin

WITH CTE AS (
SELECT id, month(trasdate) as mon, SUM(revenue) TotalRevenue,
       -- sum(revenue) as mon_revenue,
        ROW_NUMBER() OVER(PARTITION BY ID ORDER BY MONTH(TRASDATE)) AS 
ROWNUM
FROM revenue
GROUP BY ID, MONTH(TRASDATE)
 )

  insert into @temp
  SELECT A.ID,A.MON, a.TotalRevenue
    ,( SELECT avg(b.TotalRevenue) as avgrev
    FROM CTE B
    WHERE B.ROWNUM BETWEEN  A.ROWNUM-3 AND A.ROWNUM-1
    AND A.ID = B.ID --AND A.mon = B.mon
    --and b.ROWNUM < a.ROWNUM
    and (a.mon > 3 and a.ROWNUM > 3)
    GROUP BY B.id

    ) AS REVENUE_3MON
  FROM CTE A

 set @count = @count + 1
 end

 select distinct a.* from @temp a

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

id  MonthofTrasdate Revenue 3MonAvg
aa  1                400    NULL
aa  2                700    NULL
aa  3                350    NULL
aa  4                150    483
aa  5                200    400
aa  6                600    233
aa  7                500    316
aa  8                450    433
aa  9               1234    516
ab  1                400    NULL
ab  2                700    NULL
ab  3                350    NULL
ab  4                150    483
ab  5                200    400
ab  6                600    233
ab  7                500    316
ab  8                450    433
ab  9               1234    516

Это вытягивает среднее значение за последние 3 месяца для каждого месяца.Но я буду просто манипулировать остальными на SSRS так, как я хочу.

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

Не могли бы вы помочь мне с этим вопросом?А также дайте мне знать, что не так с моим прежним запросом.

Ответы [ 3 ]

0 голосов
/ 27 сентября 2018

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

Я переключил нашу временную таблицу на переменную и изменил столбец дохода, чтобы он не был INT, так как там есть десятичные значенияно кроме этого ваша исходная таблица образцов не изменилась

   DECLARE @revenue table (id varchar(2), trasdate date, revenue float)
   insert into @revenue(id, trasdate, revenue)
   values ('aa', '2018/09/01', 1234.5),
   ('aa' , '2018/08/04', 450),
   ('aa', '2018/07/03',500),
   ('aa', '2018/06/04',600),
  ('ab', '2018/09/01', 1234.5),
  ('ab' , '2018/08/04', 450),
    ('ab', '2018/07/03',500),
    ('ab', '2018/06/04',600),
   ('ab', '2018/05/03', 200),
   ('ab', '2018/04/02', 150),
  ('ab', '2018/03/01', 350),
  ('ab', '2018/02/05', 700),
  ('aa', '2018/01/07', 400)

SELECT 
        * 
    FROM
        ( 
          SELECT 
                *
                , MONTH(trasdate) as MonthNumber
                , AVG(revenue) OVER (PARTITION BY id
                                     ORDER BY 
                                        id
                                        , MONTH(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) as ThreeMonthAvg
            FROM @revenue
        ) a
    WHERE MONTH(GETDATE()) - MonthNumber IN (0, 3, 6, 9)

Это дает следующие результаты

aa  2018-06-04  600     6   400
aa  2018-09-01  1234.5  9   516.666666666667
ab  2018-03-01  350     3   700
ab  2018-06-04  600     6   233.333333333333
ab  2018-09-01  1234.5  9   516.666666666667
0 голосов
/ 28 сентября 2018

Проблемы с первой попыткой:

  • Некоторые псевдонимы и имена столбцов заключены в квадратные скобки, например [i.mon_revenue].Квадратные скобки не нужны, но если вы хотите использовать их, вы должны разбить их на точку: [i].[mon_revenue].
  • В вашем выражении оконной функции есть одна строка слишком много (в конце).
  • Оконные функции применяются в самом конце (после остальной части соответствующего запроса), поэтому вы также должны включить i.mon_revenue в предложение GROUP BYвнешний запрос.
  • Зная, что внутренний запрос будет выдавать по одной строке на id и mon, в разделе id-mon никогда не будет предшествующих строк.Следовательно, вы не должны разделять оба, а только на id.

. Чтобы упростить запрос после решения проблем: упорядочение по столбцу разделения, как правило, не имеет смысла, и поскольку - как уже упоминалось- внутренний запрос возвращает уникальные комбинации id-mon , их не нужно группировать по внешнему запросу.Глядя на этот запрос, мы видим, что внешний запрос просто напрямую выбирает и использует значения из внутреннего запроса, что делает ненужным разделение в двух запросах.Итак, на самом деле вы хотели выполнить следующий запрос, который даст скользящее среднее значение за 3 месяца (я также добавил ежемесячное значение TotalRevenue):

SELECT id, MONTH(trasdate) AS mon, SUM(revenue) AS TotalRevenue,
   AVG(SUM(revenue)) OVER (PARTITION BY id ORDER BY MONTH(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS revenue_3mon
FROM revenue
GROUP BY id, MONTH(trasdate)
ORDER BY id, MONTH(trasdate);

Предложения по второй попытке:

  • При расчете значения @MAX вы полагаетесь на тот факт, что каждый id имеет доход за одно и то же количество месяцев.Вы уверены?
  • Код внутри цикла WHILE не зависит от @count, поэтому он будет добавлять одни и те же данные в таблицу @temp несколько раз, что, вероятно, является причиной, по которой вы думали, что вам нужноОТЛИЧИЕ.Therfore: нет необходимости в переменных, нет необходимости в цикле и @temp, нет необходимости в DISTINCT.
  • Условия A.mon > 3 и A.rownum > 3 являются избыточными с вашими текущими данными.В общем, я думаю, вы не хотите явно исключать месяцы с января по март, поэтому A.mon > 3 следует удалить.A.rownum > 3 также может быть удалено, если вы действительно не хотите видеть среднее значение за 3 месяца, когда есть только 2 предыдущих месяца или меньше.
  • Поскольку подзапрос для среднего числа ограничен только однимid, нет необходимости в GROUP BY.
  • Поскольку функция ROW_NUMBER не заботится о пробелах в месяцах, я предлагаю использовать другую функцию нумерации, например DATEDIFF(month, MAX(trasdate), GETDATE()) AS mnum.Конечно, сравнение в предложении WHERE подзапроса должно быть изменено на B.mnum BETWEEN A.mnum+1 AND A.mnum+3.

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

WITH CTE AS (
    SELECT id, MONTH(trasdate) AS mon, SUM(revenue) AS TotalRevenue,
        DATEDIFF(month, MAX(trasdate), GETDATE()) AS mnum
    FROM revenue
    GROUP BY id, MONTH(trasdate)
)
SELECT id, mon, TotalRevenue
  , (SELECT AVG(B.TotalRevenue)
     FROM CTE B
     WHERE B.mnum BETWEEN A.mnum+1 AND A.mnum+3
       AND A.id = B.id
    ) AS revenue_3mon
FROM CTE A
ORDER BY id, mnum DESC;

Теперь, угадайте, что такое выражение, как мое mnum, использующее DATEDIFF, увеличивается на единицу каждый месяц, когда мы движемсяв прошлое, независимо от смены лет, так что это может быть полезно и для группировки, хотите ли вы (или можете?) использовать функции окна или нет:

с OVER ()

SELECT id, MONTH(MIN(trasdate)) AS mon, YEAR(MIN(trasdate)) AS yr, SUM(revenue) AS TotalRevenue,
   AVG(SUM(revenue)) OVER (PARTITION BY id ORDER BY MIN(trasdate) ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS revenue_3mon
FROM revenue
GROUP BY id, DATEDIFF(month, trasdate, GETDATE())
ORDER BY id, DATEDIFF(month, trasdate, GETDATE()) DESC;

Без OVER ()

WITH CTE AS (
    SELECT id, MIN(trasdate) AS min_dt, SUM(revenue) AS TotalRevenue,
        DATEDIFF(month, trasdate, GETDATE()) AS mnum
    FROM revenue
    GROUP BY id, DATEDIFF(month, trasdate, GETDATE())
)
SELECT id, MONTH(min_dt) AS mon, YEAR(min_dt) AS yr, TotalRevenue
  , (SELECT AVG(B.TotalRevenue)
     FROM CTE B
     WHERE B.mnum BETWEEN A.mnum+1 AND A.mnum+3
       AND A.id = B.id
    ) AS revenue_3mon
FROM CTE A
ORDER BY id, mnum DESC;

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

Если вы вместо этого хотели получить то, что первоначально разместили в Результат должен показать что-то вроде этого (просто группировка по предыдущим 3-месячным интервалам), вам просто нужно сгруппировать ваш исходный revenueтаблица по id и (DATEDIFF(month, trasdate, GETDATE())-1)/3 (фильтрация WHERE DATEDIFF(month, trasdate, GETDATE()) > 0).Если это так, то такая группировка и агрегация, конечно же, может выполняться и сервером отчетов.

0 голосов
/ 27 сентября 2018

Я думаю, что это должно делать то, что вы хотите:

select r.*,
       avg(r.mon_revenue) over (partition by r.id
                                order by r.mon_min
                                rows between 3 preceding and 1 preceding row
                               ) as revenue_3mon 
--  using 3 preceding and 1 preceding row you exclude the current row
 from (select r.id, month(r.trasdate) as mon,
              min(r.trasdate) as mon_min,
              sum(r.revenue) as mon_revenue
       from revenue r
       group by r.id, year(r.trasdate), month(r.trasdate)
      ) 4
order by r.id, r.mon, r.mon_min;

Примечания:

  • Я исправил код, чтобы он распознавал как годы, так и даты.
  • Выражение [i.mon_revenue] не является допустимой ссылкой на столбец (в вашем случае).У вас нет столбца с именем «i.mon_revenue» (с . в названии).
  • Я изменил псевдоним столбца на r в соответствии с таблицей.
  • Я добавил столбец даты для каждого месяца, чтобы упростить выражение порядка.
  • внешний group by не нужен.
...