Как вычислить значения агрегата по условию?

#sql #postgresql #average

#sql #postgresql #среднее

Вопрос:

вот мой базовый пример

  ---- ------- --------------------- 
| id | value | Get avg data here ↓ |
 ---- ------- --------------------- 
|  1 |    22 |                     |
|  2 |    23 |                     |
|  3 |     4 |                     |
|  3 |    33 |                     |
|  4 |   222 |                     |
|  5 |    75 |                     |
|  6 |    92 |                     |
|  7 |   202 |                     |
 ---- ------- --------------------- 
  

Попробуйте что-нибудь подобное, но ничего

 AVG (value) OVER (ORDER BY id 
        (CASE WHEN id 5 THEN ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING):: decimal(8,2) AS 'hello'),
        (CASE WHEN id 1 THEN ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING):: decimal(8,2) AS 'hello'),
        (ELSE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING):: decimal(8,2) AS 'hello' END)
FROM initial_table)
  

Не могли бы вы мне помочь?

Комментарии:

1. По какому столбцу вы упорядочиваете свои строки? т.е. Что определяет, что строка с id=7, value=211 является предыдущей строкой, id=1, value=155 а не предыдущей строкой id=1, value=210 ?

Ответ №1:

ОБНОВЛЕНИЕ: для них я добавил новый столбец, позволяющий упорядочивать строки. Поскольку OP ссылается на day_name как число в коде, я предположил, что они относятся к дням — и, таким образом, изменил столбец ‘id’ на ‘daynum’ и, соответственно, добавил столбец ‘weeknum’.

Сортировка по числу недель, а затем по числу дней эквивалентна сортировке по дате. Если вместо этого в OP в качестве ключевого столбца для сортировки используется поле datefield, измените мои ссылки на ORDER BY weeknum, daynum to ORDER BY datefield .

Вот настройка данных, которую я использовал (только первые две недели):

 CREATE TABLE initial_table 
    (weeknum int, daynum int, value int, PRIMARY KEY (weeknum, daynum));

INSERT INTO initial_table (weeknum, daynum, value) VALUES
(1, 1,  22),
(1, 2,  23),
(1, 3,   4),
(1, 4,  23),
(1, 5,  14),
(1, 6, 132),
(1, 7, 211),
(2, 1, 155),
(2, 2, 190),
(2, 3,  33),
(2, 4, 222),
(2, 5,  75),
(2, 6,  92),
(2, 7, 107);
  

Одной из относительно понятных версий было бы иметь подзапрос или CTE, который находит все типы средних, а затем внешний запрос, который выбирает соответствующий, например,

 WITH AvgList AS
(SELECT weeknum,
        daynum, 
        value, 
    SUM (value) OVER (ORDER BY weeknum, daynum) AS CumulativeSum,
        AVG (value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) :: decimal(8,2) AS Avg5,
        AVG (value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING) :: decimal(8,2) AS Avg1,
        AVG (value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) :: decimal(8,2) AS AvgOther
    FROM initial_table
)
SELECT weeknum,
       daynum, 
       value,
       CASE WHEN daynum = 5 THEN Avg5
            WHEN daynum = 1 THEN Avg1
            ELSE AvgOther END AS hello
FROM AvgList;
  

Более короткий способ — просто включить их в выражение с одним регистром

 SELECT weeknum,
       daynum, 
       value,
       CASE WHEN daynum = 5 THEN AVG (value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) :: decimal(8,2)
            WHEN daynum = 1 THEN AVG (value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING) :: decimal(8,2)
            ELSE  AVG(value) OVER (ORDER BY weeknum, daynum ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) :: decimal(8,2) END AS hello
FROM initial_table;
  

Вот db<> скрипка с данными и оба варианта.

К вашему сведению, вот скрипка db<> в SQL Server, поскольку это то, что я написал в первую очередь.