Функция окна Postgres и извлеченная/усредненная длительность между временными метками

#sql #postgresql #window-functions

Вопрос:

Я читаю ЭТО уже много лет, но это мой первый пост. Надеюсь, кто-нибудь сможет помочь мне решить эту проблему.

Я новичок в оконных функциях, но, насколько я понимаю, это то, что я ищу. У меня есть 3 таблицы пользователей, задач и пользователей задач. Одному или нескольким пользователям может быть назначена задача (через task_users). То, что я хотел бы видеть, — это таблица, показывающая следующее:

идентификатор
пользователя полное имя
пользователя сколько задач было выдано этому пользователю (вхождений)
средняя продолжительность всех задач, выданных этому пользователю (average_duration)

Текущий метод, который я использую для извлечения продолжительности для одной задачи, таков:

ИЗВЛЕЧЕНИЕ(ЭПОХА из closed_at) — ИЗВЛЕЧЕНИЕ(ЭПОХА из started_at)/3600 КАК продолжительность

Вот столбцы, представляющие интерес в каждой таблице:

Пользователи
идентификатор
фамилия имя
отчество

задачи
идентификатор
started_at (метка времени без tz)
closed_at (метка времени без tz)

пользователи задач
идентификатор задачи (ссылки tasks.id)
идентификатор пользователя (ссылки users.id)

Используя приведенный ниже sql, я могу сгенерировать таблицу, показывающую каждого пользователя, его идентификатор и количество задач, отправленных этому пользователю:

 SELECT 
    users.id AS u_id,
    concat(users.last_name, ', ', users.first_name) AS u_name, 
    COUNT(*) AS occurrences
FROM tasks
INNER JOIN task_users ON task_users.task_id = tasks.id
INNER JOIN users ON users.id = task_users.user_id
WHERE tasks.closed_at IS NOT NULL 
GROUP BY u_id
ORDER BY occurrences DESC
 

Этот запрос показывает:

 ----------------------------------
id    u_name           occurrences
----------------------------------
1  |  Mike Smith     | 10
2  |  Dave Johnson   | 5
3  |  George Wilson  | 3
etc...
 

Я хотел бы создать ту же таблицу, что и выше, но со средней продолжительностью (часы, которые потребовались для выполнения каждой задачи) для всех задач, отправленных каждому пользователю. Что-то вроде следующего:

 ------------------------------------------------------
id    u_name           occurrences    average_duration
------------------------------------------------------
1  |  Mike Smith     | 10           | 32.7
2  |  Dave Johnson   | 5            | 15.2
3  |  George Wilson  | 3            | 10.0
etc...
 

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

 SELECT 
    users.id AS u_id,
    concat(users.last_name, ', ', users.first_name) AS u_name, 
    COUNT(*) AS occurrences,
    AVG(tsk.duration) OVER(PARTITION BY users.id) AS average_duration
FROM 
    (SELECT id, (EXTRACT(EPOCH from closed_at) - EXTRACT(EPOCH from started_at)/3600) AS duration FROM tasks) tsk
INNER JOIN task_users ON tsk.id = task_users.task_id
INNER JOIN users ON users.id = task_users.user_id
 

Я новичок в оконных функциях и не гуру SQL, но мне кажется, что оконная функция-лучшее решение?

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

Спасибо!

Ответ №1:

Функция окна предоставит значение для каждой строки. В вашем сценарии у пользователя несколько задач, и поэтому объединение приведет к созданию нескольких строк для каждого пользователя.

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

 SELECT 
    users.id AS u_id,
    concat(users.last_name, ', ', users.first_name) AS u_name, 
    COUNT(*) AS occurrences,
    SUM(
        EXTRACT(EPOCH from closed_at) - EXTRACT(EPOCH from started_at)/3600
    ) / COUNT(*) as average_duration
FROM tasks
INNER JOIN task_users ON task_users.task_id = tasks.id
INNER JOIN users ON users.id = task_users.user_id
WHERE tasks.closed_at IS NOT NULL 
GROUP BY u_id
ORDER BY occurrences DESC
 

или

 SELECT 
    users.id AS u_id,
    concat(users.last_name, ', ', users.first_name) AS u_name, 
    COUNT(*) AS occurrences,
    AVG(
        EXTRACT(EPOCH from closed_at) - EXTRACT(EPOCH from started_at)/3600
    ) as average_duration
FROM tasks
INNER JOIN task_users ON task_users.task_id = tasks.id
INNER JOIN users ON users.id = task_users.user_id
WHERE tasks.closed_at IS NOT NULL 
GROUP BY u_id
ORDER BY occurrences DESC
 

Дайте мне знать, если это сработает для вас.

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

1. Спасибо! Именно то, что мне было нужно. Вопрос: работает ли это, потому что ИЗВЛЕЧЕНИЕ находится в агрегатной функции?

2. @cndev да. Сначала будет вычислено выражение в агрегатной функции.