Расчет средней длины временного интервала

Я подготовил простой SQL Fiddle, демонстрирующий мою проблему -

В PostgreSQL 10.3 я храню информацию о пользователях, играх для двух игроков и ходах в следующих трех таблицах:

CREATE TABLE players (
    uid SERIAL PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE games (
    gid SERIAL PRIMARY KEY,
    player1 integer NOT NULL REFERENCES players ON DELETE CASCADE,
    player2 integer NOT NULL REFERENCES players ON DELETE CASCADE
);

CREATE TABLE moves (
    mid BIGSERIAL PRIMARY KEY,
    uid integer NOT NULL REFERENCES players ON DELETE CASCADE,
    gid integer NOT NULL REFERENCES games ON DELETE CASCADE,
    played timestamptz NOT NULL
);

Предположим, что 2 игрока, Алиса и Боб сыграли друг с другом в 3 игры:

INSERT INTO players (name) VALUES ('Alice'), ('Bob');
INSERT INTO games (player1, player2) VALUES (1, 2);
INSERT INTO games (player1, player2) VALUES (1, 2);
INSERT INTO games (player1, player2) VALUES (1, 2);

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

Но потом они остыли :-) и сыграли 2 медленные партии с ходами каждые 10 минут:

INSERT INTO moves (uid, gid, played) VALUES
(1, 1, now() + interval '1 min'),
(2, 1, now() + interval '2 min'),
(1, 1, now() + interval '3 min'),
(2, 1, now() + interval '4 min'),
(1, 1, now() + interval '5 min'),
(2, 1, now() + interval '6 min'),

(1, 2, now() + interval '10 min'),
(2, 2, now() + interval '20 min'),
(1, 2, now() + interval '30 min'),
(2, 2, now() + interval '40 min'),
(1, 2, now() + interval '50 min'),
(2, 2, now() + interval '60 min'),

(1, 3, now() + interval '110 min'),
(2, 3, now() + interval '120 min'),
(1, 3, now() + interval '130 min'),
(2, 3, now() + interval '140 min'),
(1, 3, now() + interval '150 min'),
(2, 3, now() + interval '160 min');

На веб-странице с игровой статистикой я хотел бы отображать среднее время, проходящее между ходами для каждого игрока.

Поэтому я полагаю, что мне нужно использовать Функция окна LAG PostgreSQL.

Поскольку одновременно можно играть в несколько игр, я пытаюсь использовать PARTITION BY gid (т.е. по «идентификатору игры»).

К сожалению, я получаю синтаксическую ошибку вызовы оконных функций не могут быть вложенными с моим SQL-запросом:

SELECT AVG(played - LAG(played) OVER (PARTITION BY gid order by played))
OVER (PARTITION BY gid order by played)
FROM moves
-- trying to calculate average thinking time for player Alice
WHERE uid = 1;

Обновлено:

Поскольку количество игр в моей базе данных велико и растет день ото дня, я попытался (здесь новый SQL Fiddle) добавить условие во внутренний запрос выбора:

SELECT AVG(played - prev_played)
FROM (SELECT m.*,
      LAG(m.played) OVER (PARTITION BY m.gid ORDER BY played) AS prev_played
      FROM moves m
      JOIN games g ON (m.uid in (g.player1, g.player2))
      WHERE m.played > now() - interval '1 month'
     ) m
WHERE uid = 1;

Однако по какой-то причине это радикально меняет возвращаемое значение до 1 мин 45 сек.

И мне интересно, почему внутренний запрос SELECT внезапно возвращает гораздо больше строк, может быть, в моем JOIN отсутствует какое-то условие?

ОБНОВЛЕНИЕ 2:

Хорошо, я понимаю, почему среднее значение уменьшается: через несколько строк с одинаковыми отметками времени (например, played - prev_played = 0), но как исправить JOIN?

ОБНОВЛЕНИЕ 3:

Неважно, мне не хватало условия m.gid = g.gid AND в моем SQL JOIN, теперь это работает:

SELECT AVG(played - prev_played)
FROM (SELECT m.*,
      LAG(m.played) OVER (PARTITION BY m.gid ORDER BY played) AS prev_played
      FROM moves m
      JOIN games g ON (m.gid = g.gid AND m.uid in (g.player1, g.player2))
      WHERE m.played > now() - interval '1 month'
     ) m
WHERE uid = 1;

Желаемые результаты помогут.

Gordon Linoff 23.04.2018 16:51

Желаемый результат - 7 минут (1 + 10 + 10) / 3 для обоих плееров. На своей веб-странице я хотел бы указать, как медленно или быстро каждый игрок делает ходы.

Alexander Farber 23.04.2018 16:53

10 будет ли среднее время хода для 2-й и 3-й партии?

Juan Carlos Oropeza 23.04.2018 16:54

Да, но я хотел бы вывести только одно значение: среднее время обдумывания всех ходов, выполненных игроком, то есть 7 минут.

Alexander Farber 23.04.2018 16:56

Вам следует удалить обновления. Это относится к другому вопросу и похоже, что вы уже решили?

Juan Carlos Oropeza 23.04.2018 21:15

Я не думаю, что m.uid будет сильно фильтровать. ПРАВИЛЬНО, теперь более крупный фильтр - это m.played > now() ... я думаю, вы хотите, чтобы эта версия sqlfiddle.com/#!17/73a57/32 просто присоединялась к играм, где uid =1 - player1 или player2

Juan Carlos Oropeza 23.04.2018 21:54
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
3
6
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вам нужны подзапросы для вложения оконных функций. Я думаю, это то, что вы хотите:

select avg(played - prev_played)
from (select m.*,
             lag(m.played) over (partition by gid order by played) as prev_played
      from moves m
     ) m
where uid = 1;

Примечание: where должен входить во внешний запрос, поэтому он не влияет на lag().

Наверное, ответа @gordon достаточно. Но это не тот результат, о котором вы спрашиваете в своем комментарии. Работает только потому, что данные имеют одинаковое количество строк для каждой игры, поэтому среднее значение игр совпадает с полным средним. Но если вы хотите среднее из игр, вам понадобится один дополнительный уровень.

With cte as (
    SELECT gid, AVG(played - prev_played) as play_avg
    FROM (select m.*,
                 lag(m.played) over (partition by gid order by played) as prev_played
          from moves m      
         ) m
    WHERE uid = 1
    GROUP BY gid
)
   SELECT AVG(play_avg)
   FROM cte
;

Спасибо, но на самом деле предложение Гордона делает то, что мне нужно.

Alexander Farber 23.04.2018 19:29

Другие вопросы по теме