Gelöste und ungelöste Themenstatistiken mit Datums- und Tag-Parametern

Dieser Data Explorer-Bericht bietet eine umfassende Analyse von gelösten und ungelösten Themen auf einer Website innerhalb eines bestimmten Datumsbereichs und optional gefiltert nach einem bestimmten Tag.

:discourse: Dieser Bericht erfordert, dass das Plugin Discourse Solved aktiviert ist.

Dieser Bericht ist besonders nützlich für Administratoren und Moderatoren, die die Reaktionsfähigkeit der Community verstehen und Verbesserungsmöglichkeiten in der Benutzerunterstützung und im Engagement identifizieren möchten.

Statistiken zu gelösten und ungelösten Themen mit Datums- und Tag-Parametern

--[params]
-- date :start_date = 2022-01-01
-- date :end_date = 2024-01-01
-- text :tag_name = all

WITH valid_topics AS (
    SELECT
        t.id,
        t.user_id,
        t.title,
        t.views,
        (SELECT COUNT(*) FROM posts WHERE topic_id = t.id AND deleted_at IS NULL AND post_type = 1) - 1 AS "posts_count",
        t.created_at,
        (CURRENT_DATE::date - t.created_at::date) AS "total_days",
        STRING_AGG(tags.name, ', ') AS tag_names, -- Tags für jedes Thema aggregieren
        c.name AS category_name
    FROM topics t
    LEFT JOIN topic_tags tt ON tt.topic_id = t.id
    LEFT JOIN tags ON tags.id = tt.tag_id
    LEFT JOIN categories c ON c.id = t.category_id
    WHERE t.deleted_at IS NULL
        AND t.created_at::date BETWEEN :start_date AND :end_date
        AND t.archetype = 'regular'
    GROUP BY t.id, c.name
),

solved_topics AS (
    SELECT
        vt.id,
        dsst.created_at
    FROM discourse_solved_solved_topics dsst
    INNER JOIN valid_topics vt ON vt.id = dsst.topic_id
),

last_reply AS (
    SELECT p.topic_id, p.user_id FROM posts p
    INNER JOIN (SELECT topic_id, MAX(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                GROUP BY topic_id) x ON x.post = p.id
),

first_reply AS (
    SELECT p.topic_id, p.user_id, p.created_at FROM posts p
    INNER JOIN (SELECT topic_id, MIN(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                AND post_number > 1
                GROUP BY topic_id) x ON x.post = p.id
)

SELECT
    CASE
        WHEN st.id IS NOT NULL THEN 'solved'
        ELSE 'unsolved'
    END AS status,
    vt.tag_names,
    vt.category_name,
    vt.id AS topic_id,
    vt.user_id AS topic_user_id,
    ue.email,
    vt.title,
    vt.views,
    lr.user_id AS last_reply_user_id,
    ue2.email AS last_reply_user_email,
    vt.created_at::date AS topic_create,
    COALESCE(TO_CHAR(fr.created_at, 'YYYY-MM-DD'), '') AS first_reply_create,
    COALESCE(TO_CHAR(st.created_at, 'YYYY-MM-DD'), '') AS solution_create,
    COALESCE(fr.created_at::date - vt.created_at::date, 0) AS "time_first_reply(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (fr.created_at - vt.created_at)) / 3600.00), 0) AS "time_first_reply(hours)",
    COALESCE(st.created_at::date - vt.created_at::date, 0) AS "time_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.created_at - vt.created_at)) / 3600.00), 0) AS "time_solution(hours)",
    vt.created_at::date,
    vt.posts_count AS number_of_replies,
    vt.total_days AS total_days_without_solution
FROM valid_topics vt
LEFT JOIN last_reply lr ON lr.topic_id = vt.id
LEFT JOIN first_reply fr ON fr.topic_id = vt.id
LEFT JOIN solved_topics st ON st.id = vt.id
INNER JOIN user_emails ue ON vt.user_id = ue.user_id AND ue."primary" = true
LEFT JOIN user_emails ue2 ON lr.user_id = ue2.user_id AND ue2."primary" = true
WHERE (:tag_name = 'all' OR vt.tag_names ILIKE '%' || :tag_name || '%')
GROUP BY st.id, vt.tag_names, vt.category_name, vt.id, vt.user_id, ue.email, vt.title, vt.views, lr.user_id, ue2.email, vt.created_at, fr.created_at, st.created_at, vt.posts_count, vt.total_days
ORDER BY topic_create, vt.total_days DESC

SQL-Abfrageerklärung

Die Berichterstellung erfolgt über eine komplexe SQL-Abfrage, die Common Table Expressions (CTEs) verwendet, um die Daten effizient zu organisieren und zu verarbeiten. Die Abfrage ist wie folgt strukturiert:

  • valid_topics: Diese CTE filtert Themen nach dem angegebenen Datumsbereich und Archetyp (‘regular’) und schließt gelöschte Themen aus. Sie aggregiert auch die mit jedem Thema verbundenen Tags für eine spätere Filterung nach Tag-Namen, falls angegeben.
  • solved_topics: Identifiziert Themen, die als gelöst markiert wurden.
  • last_reply: Ermittelt den Benutzer, der die letzte Antwort in jedem Thema gegeben hat, indem die maximale Post-ID (die die aktuellste Antwort angibt) ermittelt wird, die nicht gelöscht ist und vom Post-Typ 1 (eine reguläre Antwort) ist.
  • first_reply: Ähnlich wie last_reply, identifiziert jedoch den ersten Benutzer, der auf das Thema nach dem ursprünglichen Beitrag geantwortet hat.

Die Hauptabfrage kombiniert dann diese CTEs, um einen detaillierten Bericht für jedes Thema zu erstellen, einschließlich, ob es gelöst oder ungelöst ist, Tag-Namen, Kategorie-Namen, Themen- und Benutzer-IDs, E-Mails, Aufrufe, Antwortanzahlen und Zeitangaben für die erste Antwort und die Lösung.

Parameter

  • start_date: Der Beginn des Datumsbereichs, für den der Bericht generiert werden soll.
  • end_date: Das Ende des Datumsbereichs, für den der Bericht generiert werden soll.
  • tag_name: Der spezifische Tag, nach dem Themen gefiltert werden sollen. Verwenden Sie ‘all’, um Themen mit beliebigen Tags einzuschließen.

Ergebnisse

Der Bericht liefert die folgenden Informationen für jedes Thema innerhalb der angegebenen Parameter:

  • status: Gibt an, ob das Thema gelöst wurde oder ungelöst bleibt.
  • tag_names: Zeigt die mit dem Thema verbundenen Tags an.
  • category_name: Zeigt die mit dem Thema verbundene Kategorie an.
  • topic_id: Die eindeutige Kennung für das Thema.
  • topic_user_id: Die ID des Benutzers, der das Thema erstellt hat.
  • user_email: Die E-Mail-Adresse des Themaerstellers.
  • title: Der Titel des Themas.
  • views: Die Anzahl der Aufrufe, die das Thema erhalten hat.
  • last_reply_user_id: Die ID des Benutzers, der die letzte Antwort auf das Thema gegeben hat.
  • last_reply_user_email: Die E-Mail-Adresse des Benutzers, der die letzte Antwort gegeben hat.
  • topic_create: Das Datum, an dem das Thema erstellt wurde.
  • first_reply_create: Das Datum der ersten Antwort auf das Thema.
  • solution_create: Das Datum, an dem eine Lösung für das Thema markiert wurde (falls zutreffend).
  • time_first_reply(days/hours): Die Zeit, die benötigt wurde, um die erste Antwort zu erhalten, in Tagen und Stunden.
  • time_solution(days/hours): Die Zeit, die benötigt wurde, um das Thema zu lösen, in Tagen und Stunden.
  • created_at: Das Erstellungsdatum des Themas.
  • number_of_replies: Die Gesamtzahl der Antworten auf das Thema.
  • total_days_without_solution: Die Gesamtzahl der Tage, an denen das Thema ohne Lösung aktiv war.

Beispielergebnisse

status tag_names category_name topic_id topic_user_id user_email title views last_reply_user_id last_reply_user_email topic_create first_reply_create solution_create time_first_reply(days) time_first_reply(hours) time_solution(days) time_solution(hours) created_at number_of_replies total_days_without_solution
solved support, password category1 101 1 user1@example.com How to reset my password? 150 3 user3@example.com 2022-01-05 2022-01-06 2022-01-07 1 24 2 48 2022-01-05 5 2
unsolved support, account category2 102 2 user2@example.com Issue with account activation 75 4 user4@example.com 2022-02-10 2022-02-12 2 48 0 0 2022-02-10 3 412
solved support category3 103 5 user5@example.com Can’t upload profile picture 200 6 user6@example.com 2022-03-15 2022-03-16 2022-03-18 1 24 3 72 2022-03-15 8 3
unsolved NULL category4 104 7 user7@example.com Error when posting 50 8 user8@example.com 2022-04-20 0 0 0 0 2022-04-20 0 373
3 „Gefällt mir“

Eine weitere tolle Anfrage und eine weitere Bitte von mir. :slight_smile:

Können Sie ein Auswahlfeld erstellen, um Kategorie/Unterkategorie einzugrenzen?
Ich würde diesen Bericht gerne nur für meine Ticketkategorie ausführen können.

Außerdem habe ich einen seltsamen Sonderfall gefunden. Sie können ihn vielleicht oder vielleicht auch nicht berücksichtigen, aber schaden kann die Frage nicht.

Ich habe ein Thema, auf das ich geantwortet und das ich am Tag nach der Veröffentlichung als Lösung markiert habe. Dann gab ein anderer Techniker eine andere Antwort und markierte diese etwa 10 Tage später als Lösung.

Der Bericht zeigt die Lösungszeit als 1 Tag, aber die Gesamtzeit ohne Lösung als 10 Tage an.

PNG image

Hallo @tknospdr,

Um beide Ihrer Fragen hier zu beantworten:

Sie können die folgende Abfrage verwenden, um dies zu adressieren:

--[params]
-- date :start_date = 2022-01-01
-- date :end_date = 2024-01-01
-- text :tag_name = all
-- null category_id :category_id

WITH valid_topics AS (
    SELECT
        t.id,
        t.user_id,
        t.title,
        t.views,
        (SELECT COUNT(*) FROM posts WHERE topic_id = t.id AND deleted_at IS NULL AND post_type = 1) - 1 AS "posts_count",
        t.created_at,
        (CURRENT_DATE::date - t.created_at::date) AS "total_days",
        STRING_AGG(tags.name, ', ') AS tag_names,
        c.name AS category_name,
        t.category_id
    FROM topics t
    LEFT JOIN topic_tags tt ON tt.topic_id = t.id
    LEFT JOIN tags ON tags.id = tt.tag_id
    LEFT JOIN categories c ON c.id = t.category_id
    WHERE t.deleted_at IS NULL
        AND t.created_at::date BETWEEN :start_date AND :end_date
        AND t.archetype = 'regular'
    GROUP BY t.id, c.name, t.category_id
),

solved_topics AS (
    SELECT
        dsst.topic_id,
        MIN(dsst.created_at) AS first_solution_at, -- Get earliest solution
        MAX(dsst.created_at) AS latest_solution_at -- Get latest solution
    FROM discourse_solved_solved_topics dsst
    GROUP BY dsst.topic_id
),

last_reply AS (
    SELECT p.topic_id, p.user_id FROM posts p
    INNER JOIN (SELECT topic_id, MAX(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                GROUP BY topic_id) x ON x.post = p.id
),

first_reply AS (
    SELECT p.topic_id, p.user_id, p.created_at FROM posts p
    INNER JOIN (SELECT topic_id, MIN(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                AND post_number > 1
                GROUP BY topic_id) x ON x.post = p.id
)

SELECT
    CASE
        WHEN st.topic_id IS NOT NULL THEN 'solved'
        ELSE 'unsolved'
    END AS status,
    vt.tag_names,
    vt.category_name,
    vt.id AS topic_id,
    vt.user_id AS topic_user_id,
    ue.email,
    vt.title,
    vt.views,
    lr.user_id AS last_reply_user_id,
    ue2.email AS last_reply_user_email,
    vt.created_at::date AS topic_create,
    COALESCE(TO_CHAR(fr.created_at, 'YYYY-MM-DD'), '') AS first_reply_create,
    COALESCE(TO_CHAR(st.first_solution_at, 'YYYY-MM-DD'), '') AS first_solution_create,
    COALESCE(TO_CHAR(st.latest_solution_at, 'YYYY-MM-DD'), '') AS latest_solution_create,
    COALESCE(fr.created_at::date - vt.created_at::date, 0) AS "time_first_reply(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (fr.created_at - vt.created_at)) / 3600.00), 0) AS "time_first_reply(hours)",
    COALESCE(st.first_solution_at::date - vt.created_at::date, 0) AS "time_to_first_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.first_solution_at - vt.created_at)) / 3600.00), 0) AS "time_to_first_solution(hours)",
    COALESCE(st.latest_solution_at::date - vt.created_at::date, 0) AS "time_to_latest_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.latest_solution_at - vt.created_at)) / 3600.00), 0) AS "time_to_latest_solution(hours)",
    vt.created_at::date,
    vt.posts_count AS number_of_replies,
    CASE
        WHEN st.topic_id IS NULL THEN vt.total_days
        ELSE COALESCE(st.latest_solution_at::date - vt.created_at::date, 0)
    END AS total_days_without_solution
FROM valid_topics vt
LEFT JOIN last_reply lr ON lr.topic_id = vt.id
LEFT JOIN first_reply fr ON fr.topic_id = vt.id
LEFT JOIN solved_topics st ON st.topic_id = vt.id
INNER JOIN user_emails ue ON vt.user_id = ue.user_id AND ue."primary" = true
LEFT JOIN user_emails ue2 ON lr.user_id = ue2.user_id AND ue2."primary" = true
WHERE (:tag_name = 'all' OR vt.tag_names ILIKE '%' || :tag_name || '%')
  AND (:category_id ISNULL OR vt.category_id = :category_id)
GROUP BY st.topic_id, st.first_solution_at, st.latest_solution_at, vt.tag_names, vt.category_name, vt.id, vt.user_id, ue.email, vt.title, vt.views, lr.user_id, ue2.email, vt.created_at, fr.created_at, vt.posts_count, vt.total_days
ORDER BY topic_create, vt.total_days DESC

Wobei der Parameter -- null category_id :category_id verwendet werden kann, um optional eine Kategorie für den Bericht auszuwählen, und die Ergebnisse sowohl die erste als auch die letzte Lösung verfolgen.

Zusätzlich wird das Ergebnis total_days_without_solution nun das Datum der letzten Lösung anstelle des ersten verwenden.

1 „Gefällt mir“

Super, danke! Sieht großartig aus.

1 „Gefällt mir“