Time to first response by group members

Time to first group response for topics created within a given time period

Returns the time to first response by a member of a given group to “regular” (not Personal Message) topics created by a user who is not a member of the given group. The query’s :group_name parameter is set to “staff” by default. With that value, it will give the time to first response by staff members. You can change the value of that parameter to get response times for different groups. For example “customer_support”.

Note that dates should technically be supplied in the form yyyy-mm-dd, but the query would also accept dates in the form dd-mm-yyyy.

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at)) / 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    topic_id,
    staff_user_id,
    ROUND(response_time_minutes::numeric, 2) AS response_time_minutes
FROM group_response_times
WHERE row_num = 1
ORDER BY category_id, response_time_minutes

Average time to first group response per category:

Uses the same logic as the previous query, but returns the average time to the first response by members of the given group per category for topics created by users who are not members of the given group within the time period set by the :start_date and :end_date parameters. As with the previous query, if the :group_name parameter is left at its default value of “staff”, it will return the average staff first response times for regular topics created by non-staff users.

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at)) / 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    ROUND(AVG (response_time_minutes)::numeric, 2) AS average_response_time_minutes,
    COUNT(*) AS num_topics_with_staff_responses
FROM group_response_times
WHERE row_num = 1
GROUP BY category_id

Привет, @simon

Спасибо за предложение решения для формирования ежемесячных отчетов по времени ответа сотрудников. Этот код всё ещё актуален или есть обновлённая версия? Также можно ли настроить этот отчёт на еженедельной основе вместо ежемесячной? Большое спасибо.

Нет, не актуален. Честно говоря, я не уверен, как он вообще когда-то работал. Скоро я опубликую обновлённую версию и отмечу вас, чтобы сообщить, что она готова.

Редактирование: @IreneT
Вот исправленная версия исходного запроса. Я бы проигнорировал тот запрос и вместо него посмотрел на другие запросы, которые я опубликовал в этом ответе. Дайте знать, если у вас возникнут вопросы по запросам или если вы столкнётесь с проблемами при добавлении необходимых параметров в запросы. По результатам тестирования сегодня я обнаружил, что мне приходится обновлять страницу после сохранения запроса в Data Explorer, чтобы поля ввода параметров появились под запросом. (Это может быть просто особенностью моего локального сайта разработки.)

-- [params]
-- int :months_ago = 1

WITH query_period AS (
SELECT
date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' as period_start,
date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' + INTERVAL '1 month' - INTERVAL '1 second' as period_end
),
staff_responses AS (
SELECT
DISTINCT ON (p.topic_id)
p.topic_id,
p.created_at,
t.category_id,
EXTRACT(MINUTE FROM (p.created_at - t.created_at)) AS response_time
FROM posts p
JOIN topics t
ON t.id = p.topic_id
AND t.category_id = ANY ('{46,25,43,40,44,35,22,7,20,17,6,12}'::int[])
JOIN users u
ON u.id = p.user_id
WHERE p.post_number > 1
AND u.admin = 't' OR u.moderator = 't'
ORDER BY p.topic_id, p.created_at
),
user_topics AS (
SELECT
t.id
FROM topics t
JOIN users u
ON u.id = t.user_id
WHERE u.admin = 'f' AND u.moderator = 'f'
)

SELECT
sr.category_id,
AVG(sr.response_time) AS "Average First Response Time",
COUNT(1) AS "Topics Responded to"
FROM staff_responses sr
JOIN query_period qp
ON sr.created_at >= qp.period_start
AND sr.created_at <= qp.period_end
JOIN user_topics t
ON t.id = sr.topic_id
GROUP BY sr.category_id

Что было изменено:

--DATE_TRUNC('minute', p.created_at - t.created_at) AS response_time
EXTRACT(MINUTE FROM (p.created_at - t.created_at)) AS response_time

Я был бы удивлён, если бы написал старый запрос без его тестирования. В любом случае, обновлённая версия теперь работает как ожидалось.

По памяти, этот запрос был написан для конкретного сайта и не предназначался для публикации на Meta. Вот ещё несколько полезных запросов для получения информации о времени ответа сотрудников:

Время до первого группового ответа для тем, созданных в определённый период времени

Возвращает время до первого ответа члена определённой группы на «обычные» (не личные сообщения) темы, созданные пользователем, который не является членом этой группы. Параметр :group_name запроса по умолчанию установлен в значение «staff». При этом значении он покажет время до первого ответа сотрудниками. Вы можете изменить значение этого параметра, чтобы получить время ответа для разных групп. Например, «customer_support».

Обратите внимание, что даты технически должны предоставляться в формате yyyy-mm-dd, но запрос также принимает даты в формате dd-mm-yyyy.

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(MINUTE FROM (p.created_at - t.created_at)) AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    topic_id,
    staff_user_id,
    response_time_minutes
FROM group_response_times
WHERE row_num = 1
ORDER BY category_id, response_time_minutes

Среднее время до первого группового ответа по категориям:

Использует ту же логику, что и предыдущий запрос, но возвращает среднее время до первого ответа членами указанной группы по каждой категории для тем, созданных пользователями, не являющимися членами этой группы, в период времени, заданный параметрами :start_date и :end_date. Как и в предыдущем запросе, если параметр :group_name оставить со значением по умолчанию «staff», он вернёт среднее время первого ответа сотрудниками для обычных тем, созданных пользователями, не являющимися сотрудниками.

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(MINUTE FROM (p.created_at - t.created_at)) AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    AVG (response_time_minutes) AS average_response_time_minutes,
    COUNT(*) AS num_topics_with_staff_responses
FROM group_response_times
WHERE row_num = 1
GROUP BY category_id

@JammyDodger, возможно, стоит заменить запрос в исходном сообщении на последние два запроса в этом посте. Также, вероятно, стоит обновить заголовок на что-то вроде «Время до первого ответа членами группы».

@simon С нетерпением жду этого.

Очень признателен. Спасибо.

Нет, к сожалению, это известная проблема. Однако простое обновление страницы решает её. :+1:

Это отлично, @simon. Огромное спасибо за помощь в этом. С нашей стороны потребовалась лишь небольшая корректировка, чтобы получить нужные данные, но в целом всё в порядке. Большое спасибо :heart:

Привет, @simon

Мы установили Data Explorer и использовали предоставленные вами коды. Наша конкретная цель — получить время до первого ответа для каждого участника группы по имени. Однако запрос возвращает время отклика, сгруппированное по ID категории, а не по ID пользователя.

Будем признательны за вашу помощь. Спасибо.

Спасибо, что подняли этот вопрос, потому что в опубликованных мной запросах есть ошибка:

EXTRACT(MINUTE FROM (p.created_at - t.created_at)) AS response_time_minutes

Эта строка делает именно то, что в ней написано: извлекает минуты из временных меток. Это означает, что если тема была создана в 12:00, а ответ получен через месяц в 12:05, запрос рассчитывал время ответа как 5 минут.

Исправление:

EXTRACT(EPOCH FROM (p.created_at - t.created_at))/ 60 AS response_time_minutes

Работа с датами может быть запутанной. Я не могу отредактировать первый пост, поэтому опубликую исправления здесь, а ответ на ваш вопрос — в отдельном сообщении.

@JammyDodger, вот новые версии двух запросов из первого поста:

Время до первого ответа по каждой теме для участников группы

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at)) / 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    topic_id,
    staff_user_id,
    ROUND(response_time_minutes::numeric, 2) AS response_time_minutes
FROM group_response_times
WHERE row_num = 1
ORDER BY category_id, response_time_minutes

Среднее время до первого ответа группы по каждой категории

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at)) / 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    ROUND(AVG (response_time_minutes)::numeric, 2) AS average_response_time_minutes,
    COUNT(*) AS num_topics_with_staff_responses
FROM group_response_times
WHERE row_num = 1
GROUP BY category_id

Полагаю, вы хотите получить среднее время до первого ответа для каждого участника группы. Если же вам нужно просто время, за которое участники группы ответили на отдельные темы, используйте фиксированную версию первого запроса из первого поста темы (OP):

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at))/ 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    category_id,
    topic_id,
    staff_user_id,
    ROUND(response_time_minutes::numeric, 2) AS response_time_minutes
FROM group_response_times
WHERE row_num = 1
ORDER BY category_id, response_time_minutes

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

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

-- [params]
-- date :start_date
-- date :end_date
-- string :group_name = staff

WITH group_response_times AS (
    SELECT
        t.category_id,
        t.id AS topic_id,
        EXTRACT(EPOCH FROM (p.created_at - t.created_at))/ 60 AS response_time_minutes,
        p.user_id AS staff_user_id,
        ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY p.created_at) AS row_num
    FROM posts p
    JOIN topics t ON t.id = p.topic_id
    WHERE t.user_id NOT IN (SELECT user_id
                                FROM group_users gu JOIN groups g ON g.id = gu.group_id
                                WHERE gu.user_id > 0 AND g.name = :group_name)
        AND p.user_id IN (SELECT user_id
                              FROM group_users gu JOIN groups g ON g.id = gu.group_id
                              WHERE gu.user_id > 0 AND g.name = :group_name)
        AND t.archetype = 'regular'
        AND t.deleted_at IS NULL
        AND p.post_type = 1
        AND p.deleted_at IS NULL
        AND t.created_at BETWEEN :start_date AND :end_date
)

SELECT
    staff_user_id,
    ROUND(AVG(response_time_minutes)::numeric, 2) AS average_response_time_minutes,
    COUNT(*) AS number_of_topics_responded_to
FROM group_response_times
WHERE row_num = 1
GROUP BY staff_user_id
ORDER BY average_response_time_minutes

Спасибо, @simon — как всегда, очень полезный и быстрый ответ. Очень ценим это!

На самом деле наша цель — чтобы наши тренеры (добавленные в одну из групп) измеряли свое среднее время ответа. Таким образом, у нас будет ориентир того, насколько хорошо мы справляемся с предоставлением поддержки сообществу в Discourse и обеспечиваем удовлетворенность участников в долгосрочной перспективе.

Это имеет смысл. Существует множество данных, которые могут оказаться интересными или полезными, если их правильно интерпретировать. Используя запросы Data Explorer, я иногда беспокоюсь, что пишу запросы, которые применяются для формирования отчётов о производительности.

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

Один из моментов, который не раскрывают запросы в этой теме, — это информация о темах, которые не получили ответа от участника группы. Запросы возвращают данные только о темах, на которые были даны ответы. Я не уверен, как лучше всего добавить информацию о темах без ответов в эти запросы.

@simon это имеет смысл.

Спасибо за предоставленную информацию. Возможно, эту метрику (время до первого ответа) всегда следует подкреплять другим отчетом по теме «Темы без ответа».

Возможно ли, что при клике на значение в этом отчете будет отображаться ссылка, ведущая к сводке тех «тем, на которые нет ответа»? Это позволит нам проверить, успеваем ли мы отвечать на все темы, требующие реакции, в установленные сроки, исключая при этом темы, которые вообще не требуют ответа.

Нет, это невозможно. Лучший вариант для получения ссылок на темы без ответов — использовать запрос в Data Explorer. На Meta, возможно, есть пример такого запроса, но я не могу его найти при поиске.

Другой вариант — установить компонент темы «Фильтр безответных тем»: Unanswered Filter. Он добавляет элемент выпадающего меню в навигацию сайта, позволяющий фильтровать списки тем и отображать только те, на которые нет ответов.

Огромное спасибо, @simon. Как всегда, очень ценим :heart: