SQL перемещение совокупной суммы без частичных результатов

#sql #database #postgresql

#sql #База данных #postgresql

Вопрос:

Предположим, у меня есть эта схема (протестирована на postgresql), где отношение «Scorelines» содержит результаты спортивных матчей. (kickoff — это ВРЕМЕННАЯ МЕТКА, но заменена на INT для удобства чтения)

SQLFiddle здесь: http://sqlfiddle.com /#!12/52475/3

 CREATE TABLE Scorelines (
   team TEXT, 
   kickoff INT,
   scored INT,
   conceded INT
);
  

Теперь я хочу создать еще один столбец ‘three_matches_scored’, который содержит сумму очков, набранных
за 3 предыдущих игры (определяется по началу) той же команды. У меня есть это:

 SELECT team, kickoff, scored, conceded, SUM(scored) OVER three_matches AS three_matches_scored 
FROM Scorelines
WINDOW three_matches AS
      (PARTITION BY team ORDER BY kickoff
       ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING)
ORDER BY kickoff;
  

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

 | TEAM | KICKOFF | SCORED | CONCEDED | THREE_MATCHES_SCORED |
|------|---------|--------|----------|----------------------|
|    A |       1 |      1 |        0 |               (null) |
|    B |       2 |      1 |        1 |               (null) |
|    A |       3 |      1 |        1 |                    1 |
|    A |       4 |      3 |        0 |                    2 |
|    B |       4 |      1 |        4 |                    1 |
|    A |       6 |      0 |        2 |                    5 |
|    B |       6 |      4 |        2 |                    2 |
|    B |       8 |      1 |        2 |                    6 |
|    B |      10 |      1 |        1 |                    6 |
|    A |      11 |      2 |        1 |                    4 |
  

Я хочу, чтобы столбец ‘three_matches_scored’ был (нулевым) для первых 3 игр, потому что нет 3 результатов для суммирования. Как я могу этого добиться?

Я бы предпочел простые понятные решения, производительность не критична для данного конкретного случая.

Моя единственная идея прямо сейчас — определить хранимую функцию SUM3, которая приводит к (null) с суммированием менее 3 значений. Но я никогда не определял функцию в SQL и, похоже, не могу ее понять.

Ответ №1:

Вы можете использовать оператор case для обнуления строк, в которых меньше 3 игр:

 SELECT team, kickoff, scored, conceded,
    CASE WHEN COUNT(scored) OVER three_matches = 3 
    THEN SUM(scored) OVER three_matches 
    ELSE NULL 
    END AS three_matches_scored
FROM Scorelines
WINDOW three_matches AS
(PARTITION BY team ORDER BY kickoff
ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING)
ORDER BY kickoff;
  

Вывод:

  team | kickoff | scored | conceded | three_matches_scored
------ --------- -------- ---------- ----------------------
 A    |       1 |      1 |        0 |
 B    |       2 |      1 |        1 |
 A    |       3 |      1 |        1 |
 A    |       4 |      3 |        0 |
 B    |       4 |      1 |        4 |
 A    |       6 |      0 |        2 |                    5
 B    |       6 |      4 |        2 |
 B    |       8 |      1 |        2 |                    6
 B    |      10 |      1 |        1 |                    6
 A    |      11 |      2 |        1 |                    4
(10 rows)
  

Ответ №2:

См. Ответ harmics выше.

(мое первое решение, просто для справки)

Решение с определяемым пользователем агрегатом:

 CREATE TYPE intermediate_sum AS (
   sum INT,
   count INT
);


CREATE FUNCTION sum_sfunc(intermediate_sum, INTEGER) RETURNS intermediate_sum AS
$$ SELECT $2   $1.sum AS sum, $1.count - 1 AS count $$ LANGUAGE SQL;

CREATE FUNCTION sum_ffunc(intermediate_sum) RETURNS INTEGER AS
$$ SELECT (CASE WHEN $1.count > 1 THEN null
                WHEN $1.count = 0 THEN $1.sum
           END)
$$ LANGUAGE SQL;

CREATE AGGREGATE sum3(INTEGER) (
    sfunc = sum_sfunc,
    finalfunc = sum_ffunc,
    stype = intermediate_sum,
    initcond = '(0,3)'
);
  

Совокупная СУММА3 требует не менее 3 значений, в противном случае она возвращает (null). Можно определить другие агрегаты, такие как SUM4, изменив initcond, например, на ‘(0,4)’.