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

Olá @simon

Obrigado por compartilhar uma possível solução para gerar relatórios de tempo de resposta da equipe mensalmente. Este código ainda é viável ou existe uma versão atualizada? Além disso, há alguma maneira de gerar este relatório semanalmente em vez de mensalmente? Muito obrigado.

1 curtida

Não é. Honestamente, não tenho certeza de como já funcionou. Postarei uma versão atualizada em breve e pingarei você para avisar que está pronta.

Editar: @IreneT
Aqui está uma versão corrigida da consulta original. Eu ignoraria essa consulta e olharia as outras consultas que postei nesta resposta. Me avise se você tiver alguma dúvida sobre as consultas ou se tiver problemas para adicionar os parâmetros necessários às consultas. Com base nos testes de hoje, estou descobrindo que preciso atualizar a página depois de salvar uma consulta do Data Explorer para que os campos de entrada de parâmetros apareçam abaixo da consulta. (Isso pode ser apenas uma peculiaridade no meu site de desenvolvimento local.)

-- [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 "Tempo Médio de Primeira Resposta",
COUNT(1) AS "Tópicos Respondidos"
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

O que foi alterado foi:

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

Ficaria surpreso se eu tivesse escrito a consulta antiga sem testá-la. De qualquer forma, a versão atualizada funciona como esperado agora.

De memória, esta consulta foi escrita para um site específico e não se destinava a ser postada no Meta. Aqui estão mais algumas consultas úteis para obter informações sobre os tempos de resposta da equipe:

Tempo para a primeira resposta do grupo para tópicos criados em um determinado período:

Retorna o tempo para a primeira resposta de um membro de um determinado grupo para tópicos “regulares” (não Mensagem Pessoal) criados por um usuário que não é membro do determinado grupo. O parâmetro :group_name da consulta está definido como “staff” por padrão. Com esse valor, ele dará o tempo para a primeira resposta dos membros da equipe. Você pode alterar o valor desse parâmetro para obter tempos de resposta para diferentes grupos. Por exemplo, “customer_support”.

Observe que as datas devem ser fornecidas tecnicamente no formato aaaa-mm-dd, mas a consulta também aceitaria datas no formato dd-mm-aaaa.

-- [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

Tempo médio para a primeira resposta do grupo por categoria:

Usa a mesma lógica da consulta anterior, mas retorna o tempo médio para a primeira resposta de membros do grupo especificado por categoria para tópicos criados por usuários que não são membros do grupo especificado dentro do período de tempo definido pelos parâmetros :start_date e :end_date. Assim como na consulta anterior, se o parâmetro :group_name for deixado em seu valor padrão “staff”, ele retornará os tempos médios de primeira resposta da equipe para tópicos regulares criados por usuários não pertencentes à equipe.

-- [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, talvez substitua a consulta na OP pelas duas últimas consultas neste post. Além disso, provavelmente atualize o título para algo como “Tempo para a primeira resposta por membros do grupo”.

3 curtidas

@simon Ansioso por isso.

Muito apreciado. Obrigado

1 curtida

Não, infelizmente isso é algo conhecido. Uma atualização rápida resolve o problema. :+1:

4 curtidas

Isso está perfeito, @simon. Agradeço muito sua ajuda com isso. Apenas um pequeno ajuste da nossa parte para termos os dados de que precisamos, mas acho que estamos bem. Muito obrigado :heart:

1 curtida

Olá @simon

Nós instalamos o Data Explorer e usamos os códigos que você compartilhou. O que queremos especificamente é ter o Tempo de primeira resposta pelo nome de cada membro do grupo. No entanto, a consulta está enviando os tempos de resposta agrupados pelo ID da categoria e não pelo ID do usuário.

Agradeceríamos sua ajuda. Obrigado.

Obrigado por levantar isso, porque há um erro nas consultas que postei:

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

Essa linha está fazendo exatamente o que diz, extraindo minutos dos timestamps. Isso significa que se um tópico foi criado às 12:00, e respondido um mês depois às 12:05, a consulta estava calculando um tempo de resposta de 5 minutos.

A correção é:

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

Datas podem ser complicadas. Não posso editar o OP, então postarei as correções aqui, e depois a resposta para sua pergunta em uma postagem separada.

@JammyDodger, aqui estão as novas versões das 2 consultas no OP:

Tempo para a primeira resposta por tópico para membros de um grupo

-- [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

Tempo médio para a primeira resposta do grupo por categoria

-- [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
4 curtidas

Presumo que você queira o tempo médio de primeira resposta por membro do grupo. Se você quiser apenas o tempo que os membros do grupo levaram para responder a tópicos individuais, use a versão corrigida da primeira consulta na 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

Isso informará o tempo que os membros do grupo levaram para responder a tópicos individuais. Os resultados são organizados por categoria, mas a categoria pode ser ignorada.

Não tenho certeza de quão significativos seriam os dados para tempos médios de resposta para membros individuais do grupo. Eu teria cuidado ao usá-los para qualquer tipo de avaliação de desempenho, pois pode penalizar membros do grupo que respondem a perguntas difíceis ou tópicos que outros membros do grupo ignoraram. Com isso em mente, aqui está uma consulta que retorna os tempos médios de resposta para membros do grupo e o número de tópicos aos quais eles foram o primeiro membro do grupo a responder:

-- [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
2 curtidas

Obrigado @simon - resposta muito útil e rápida como sempre. Realmente aprecio!

Na verdade, nosso objetivo é que nossos treinadores (adicionados em um dos grupos) meçam seu tempo médio de resposta. Dessa forma, teremos uma referência de quão bem estamos nos saindo em relação ao fornecimento de suporte comunitário em discussões e à garantia de membros satisfeitos a longo prazo.

Isso faz sentido. Há muitos dados que podem ser interessantes ou úteis se interpretados da maneira certa. Com as consultas do Explorador de Dados, às vezes me preocupo que eu possa estar escrevendo consultas que são usadas para gerar relatórios de desempenho.

Eu fiz muito trabalho de suporte ao cliente. Acho que o tempo para a primeira resposta é uma métrica útil, mas a qualidade da resposta também precisa ser levada em consideração.

Uma coisa que as consultas neste tópico não estão dizendo é sobre tópicos que não receberam resposta de um membro do grupo. As consultas estão retornando apenas dados sobre tópicos que foram respondidos. Não tenho certeza de como melhor adicionar informações sobre tópicos sem resposta às consultas.

@simon faz sentido.

Obrigado por compartilhar essa informação. Talvez essa métrica (tempo para a primeira resposta) deva ser sempre apoiada pelo outro relatório sobre “Tópicos sem resposta”.

É possível que, quando um valor for clicado neste relatório, ele seja um link para mostrar um resumo desses “tópicos que não têm resposta”? Dessa forma, podemos verificar se somos capazes de responder a todos os tópicos que precisam de respostas dentro de um tempo especificado e isso excluiria os tópicos que realmente não precisam de resposta alguma.

Não é. A melhor opção para obter links para tópicos sem resposta pode ser usar uma consulta do Data Explorer. Pode haver um exemplo de consulta para isso no Meta, mas não estou encontrando um ao pesquisar por ele.

Outra opção é instalar o componente de tema Unanswered Filter: Unanswered Filter. Ele adiciona um item suspenso ao menu de navegação do site que permite filtrar listas de tópicos para exibir apenas tópicos sem resposta.

Muito obrigado @simon . Agradeço como sempre :heart:

1 curtida