PostgreSQL получает относительное среднее с группой по - PullRequest
1 голос
/ 27 апреля 2019

У меня есть таблица следующим образом.Строки расположены в определенном порядке.

id    |      value
------+---------------------
 1    |        2
 1    |        4     
 1    |        3
 2    |        2
 2    |        2
 2    |        5

Я бы хотел сгруппировать строки по столбцу 'id' и получить среднее значение, отображаемое в каждом столбце в терминах предыдущих значений столбца (Как объясняется в следующем примере в квадратных скобках)

id    |      value  |    RelativeAverage    
------+-------------+--------------------
 1    |        2    |        (2/1) = 2
 1    |        4    |        (2+4 /2) = 3
 1    |        3    |        (2+4+3 / 3) = 3
 2    |        2    |        (2/1) = 2
 2    |        2    |        (2+2 / 2) = 2
 2    |        5    |        (2+2+5 / 3) = 9

Есть ли подход, с помощью которого я могу достичь этого?

Заранее спасибо

Ответы [ 3 ]

2 голосов
/ 27 апреля 2019

Неправильный запрос:

select 
  id, value, 

  sum(value) over(arrangement), rank() over(arrangement),

  sum(value) over(arrangement)::numeric / rank() over(arrangement) 
  as relative_average
from tbl
window arrangement as (partition by id order by id);

Вывод (неверный):

| id | value | sum | rank | relative_average |
|----|-------|-----|------|------------------|
|  1 |     2 |   9 |    1 |                9 |
|  1 |     4 |   9 |    1 |                9 |
|  1 |     3 |   9 |    1 |                9 |
|  2 |     1 |   8 |    1 |                8 |
|  2 |     2 |   8 |    1 |                8 |
|  2 |     5 |   8 |    1 |                8 |

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

Правильный запрос:

select 
    id, value, 

    sum(value) over(arrangement), rank() over(arrangement),

    sum(value) over(arrangement)::numeric / rank() over(arrangement) 
    as relative_average
from tbl
window arrangement as (partition by id order by tbl.ctid);

Вывод (правильный):

| id | value | sum | rank |   relative_average |
|----|-------|-----|------|--------------------|
|  1 |     2 |   2 |    1 |                  2 |
|  1 |     4 |   6 |    2 |                  3 |
|  1 |     3 |   9 |    3 |                  3 |
|  2 |     1 |   1 |    1 |                  1 |
|  2 |     2 |   3 |    2 |                1.5 |
|  2 |     5 |   8 |    3 | 2.6666666666666665 |

Лучшийспособ состоит в том, чтобы ввести серийный первичный ключ, поэтому можно получить промежуточный итог (sum over()) на основе фактического расположения ваших данных.

CREATE TABLE tbl
    (ordered_pk serial primary key, "id" int, "value" int)
;

INSERT INTO tbl
    ("id", "value")
VALUES
    (1, 2),
    (1, 4),
    (1, 3),
    (2, 1),
    (2, 2),
    (2, 5)
;

Правильный запрос:

select 
    id, value, 

    sum(value) over(arrangement), rank() over(arrangement),

    sum(value) over(arrangement)::numeric / rank() over(arrangement) 
    as relative_average
from tbl
window arrangement as (partition by id order by ordered_pk);

Вывод (правильный):

| id | value | sum | rank |   relative_average |
|----|-------|-----|------|--------------------|
|  1 |     2 |   2 |    1 |                  2 |
|  1 |     4 |   6 |    2 |                  3 |
|  1 |     3 |   9 |    3 |                  3 |
|  2 |     1 |   1 |    1 |                  1 |
|  2 |     2 |   3 |    2 |                1.5 |
|  2 |     5 |   8 |    3 | 2.6666666666666665 |

Живой тест: http://sqlfiddle.com/#!17/f18276/1

Вы можете order by value, но это даст другой результат, не обязательно неправильный вывод, но другой из-заразличное расположение ценностей.И тогда вам также необходимо использовать row_number вместо rank / dense_rank из-за возможного дублирования значений.Здесь я сделал пример дублирующихся значений.

Правильный запрос:

select 
    id, value, 

    sum(value) over(arrangement),

    row_number() over(arrangement),
    rank() over(arrangement),  
    dense_rank() over(arrangement),    

    sum(value) over(arrangement)::numeric / row_number() over(arrangement) 
    as relative_average
from tbl
window arrangement as (partition by id order by value)

Вывод:

| id | value | sum | row_number | rank | dense_rank |   relative_average |
|----|-------|-----|------------|------|------------|--------------------|
|  1 |     2 |   2 |          1 |    1 |          1 |                  2 |
|  1 |     3 |   5 |          2 |    2 |          2 |                2.5 |
|  1 |     4 |   9 |          3 |    3 |          3 |                  3 |
|  2 |     1 |   1 |          1 |    1 |          1 |                  1 |
|  2 |     2 |   5 |          2 |    2 |          2 |                2.5 |
|  2 |     2 |   5 |          3 |    2 |          2 | 1.6666666666666667 |
|  2 |     5 |  10 |          4 |    4 |          3 |                2.5 |

Живой тест: http://sqlfiddle.com/#!17/2b5aac/1

1 голос
/ 27 апреля 2019

Не так горжусь моим другим ответом

Просто используйте avg.

Сегодня я узнал rows between unbounded preceding and current row.И это работает с фактическим расположением данных даже при отсутствии хорошего поля кандидата на order by.Похоже, что, по крайней мере, вы можете избежать использования скрытого поля Postgres ctid, или вы можете даже избежать использования первичного последовательного интерфейса.Тем не менее, рекомендуется использовать серийный первичный ключ или поле для создания даты до order by после.

Вот лучший запрос.Нет необходимости делить, просто используйте avg

select 
    id, value, 

    avg(value) over(arrangement rows between unbounded preceding and current row)
from tbl
window arrangement as (partition by id);

Выход

| id | value |                avg |
|----|-------|--------------------|
|  1 |     2 |                  2 |
|  1 |     4 |                  3 |
|  1 |     3 |                  3 |
|  2 |     1 |                  1 |
|  2 |     2 |                1.5 |
|  2 |     5 | 2.6666666666666665 |
select 
    id, value, 

    sum(value) over(arrangement), rank() over(arrangement),

    sum(value) over(arrangement)::numeric / rank() over(arrangement) 
    as relative_average,    

    avg(value) over(arrangement rows between unbounded preceding and current row)
from tbl
window arrangement as (partition by id order by id);

Выход:

| id | value | sum | rank | relative_average |                avg |
|----|-------|-----|------|------------------|--------------------|
|  1 |     2 |   9 |    1 |                9 |                  2 |
|  1 |     4 |   9 |    1 |                9 |                  3 |
|  1 |     3 |   9 |    1 |                9 |                  3 |
|  2 |     1 |   8 |    1 |                8 |                  1 |
|  2 |     2 |   8 |    1 |                8 |                1.5 |
|  2 |     5 |   8 |    1 |                8 | 2.6666666666666665 |
select 
    id, value, 

    sum(value) over(arrangement), rank() over(arrangement),

    sum(value) over(arrangement)::numeric / rank() over(arrangement) 
    as relative_average,

    avg(value) over(arrangement rows between unbounded preceding and current row)    
from tbl
window arrangement as (partition by id order by tbl.ctid);

Выход:

| id | value | sum | rank |   relative_average |                avg |
|----|-------|-----|------|--------------------|--------------------|
|  1 |     2 |   2 |    1 |                  2 |                  2 |
|  1 |     4 |   6 |    2 |                  3 |                  3 |
|  1 |     3 |   9 |    3 |                  3 |                  3 |
|  2 |     1 |   1 |    1 |                  1 |                  1 |
|  2 |     2 |   3 |    2 |                1.5 |                1.5 |
|  2 |     5 |   8 |    3 | 2.6666666666666665 | 2.6666666666666665 |
select 
    id, value, 

    sum(value) over(arrangement), rank() over(arrangement),

    sum(value) over(arrangement)::numeric / rank() over(arrangement) 
    as relative_average,

    avg(value) over(arrangement rows between unbounded preceding and current row)    
from tbl
window arrangement as (partition by id order by ordered_pk);

Вывод:

| id | value | sum | rank |   relative_average |                avg |
|----|-------|-----|------|--------------------|--------------------|
|  1 |     2 |   2 |    1 |                  2 |                  2 |
|  1 |     4 |   6 |    2 |                  3 |                  3 |
|  1 |     3 |   9 |    3 |                  3 |                  3 |
|  2 |     1 |   1 |    1 |                  1 |                  1 |
|  2 |     2 |   3 |    2 |                1.5 |                1.5 |
|  2 |     5 |   8 |    3 | 2.6666666666666665 | 2.6666666666666665 |

Живой тест: http://sqlfiddle.com/#!17/f18276/9

rows between unbounded preceding and current row также можно записать как rows unbounded preceding http://sqlfiddle.com/#!17/f18276/11

И вот результат с order by value, когда значение имеет дубликаты.

select 
    id, value, 

    sum(value) over(arrangement),

    row_number() over(arrangement) as rn,
    rank() over(arrangement) as rank,  
    dense_rank() over(arrangement) drank,    

    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__rn,
    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__rank,    
    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__drank,        

    trunc( avg(value) over(arrangement 
    rows between unbounded preceding and current row), 2) as ra
from tbl
window arrangement as (partition by id order by value)

Вывод:

| id | value | sum | rn | rank | drank | ra__rn | ra__rank | ra__drank |   ra |
|----|-------|-----|----|------|-------|--------|----------|-----------|------|
|  1 |     2 |   2 |  1 |    1 |     1 |      2 |        2 |         2 |    2 |
|  1 |     3 |   5 |  2 |    2 |     2 |    2.5 |      2.5 |       2.5 |  2.5 |
|  1 |     4 |   9 |  3 |    3 |     3 |      3 |        3 |         3 |    3 |
|  2 |     1 |   1 |  1 |    1 |     1 |      1 |        1 |         1 |    1 |
|  2 |     2 |   5 |  2 |    2 |     2 |    2.5 |      2.5 |       2.5 |  1.5 |
|  2 |     2 |   5 |  3 |    2 |     2 |   1.66 |     1.66 |      1.66 | 1.66 |
|  2 |     5 |  10 |  4 |    4 |     3 |    2.5 |      2.5 |       2.5 |  2.5 |

Живой тест: http://sqlfiddle.com/#!17/2b5aac/16

И вотрезультат с order by ordered_pk, когда значение имеет дубликаты.

select 
    id, value,

    sum(value) over(arrangement),

    row_number() over(arrangement) as rn,
    rank() over(arrangement) as rank,  
    dense_rank() over(arrangement) drank,    

    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__rn,
    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__rank,    
    trunc( sum(value) over(arrangement)::numeric 
        / row_number() over(arrangement), 2) as ra__drank,        

    trunc( avg(value) over(arrangement 
    rows between unbounded preceding and current row), 2) as ra
from tbl
window arrangement as (partition by id order by ordered_pk)
| id | value | sum | rn | rank | drank | ra__rn | ra__rank | ra__drank |   ra |
|----|-------|-----|----|------|-------|--------|----------|-----------|------|
|  1 |     2 |   2 |  1 |    1 |     1 |      2 |        2 |         2 |    2 |
|  1 |     4 |   6 |  2 |    2 |     2 |      3 |        3 |         3 |    3 |
|  1 |     3 |   9 |  3 |    3 |     3 |      3 |        3 |         3 |    3 |
|  2 |     1 |   1 |  1 |    1 |     1 |      1 |        1 |         1 |    1 |
|  2 |     2 |   3 |  2 |    2 |     2 |    1.5 |      1.5 |       1.5 |  1.5 |
|  2 |     2 |   5 |  3 |    3 |     3 |   1.66 |     1.66 |      1.66 | 1.66 |
|  2 |     5 |  10 |  4 |    4 |     4 |    2.5 |      2.5 |       2.5 |  2.5 |

Текущий тест: http://sqlfiddle.com/#!17/baaf9/2

0 голосов
/ 27 апреля 2019

Если я предполагаю, что у вас есть столбец заказа в таблице, то вы хотите:

select t.*,
       avg(value) over (partition by id
                        order by ?
                        rows between unbounded preceding and current row
                       ) as running_avg
from t;

? - это столбец заказа.

Другими словами, Postgres имеет единственную встроенную функцию, которая делает именно то, что вы хотите - и эта функция оказывается стандартным SQL.

Требуется рамка окна, использующая rows, поскольку по умолчанию range.

Если у вас нет столбца заказа, вам следует добавить его. Я настоятельно советую вам НЕ использовать ctid для этой цели. Может показаться, что он работает с небольшими наборами данных, но он не стабилен во времени и может не работать с большими наборами данных.

Если вы ожидаете, что ваши данные будут упорядочены по вставкам, используйте столбец serial, чтобы зафиксировать порядок вставок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...