Я подготовил простой 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;
Желаемый результат - 7 минут (1 + 10 + 10) / 3 для обоих плееров. На своей веб-странице я хотел бы указать, как медленно или быстро каждый игрок делает ходы.
10 будет ли среднее время хода для 2-й и 3-й партии?
Да, но я хотел бы вывести только одно значение: среднее время обдумывания всех ходов, выполненных игроком, то есть 7 минут.
Вам следует удалить обновления. Это относится к другому вопросу и похоже, что вы уже решили?
Я не думаю, что m.uid будет сильно фильтровать. ПРАВИЛЬНО, теперь более крупный фильтр - это m.played > now() ... я думаю, вы хотите, чтобы эта версия sqlfiddle.com/#!17/73a57/32 просто присоединялась к играм, где uid =1 - player1 или player2


Вам нужны подзапросы для вложения оконных функций. Я думаю, это то, что вы хотите:
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
;
Спасибо, но на самом деле предложение Гордона делает то, что мне нужно.
Желаемые результаты помогут.