시간 간격별 대시보드 리포트 데이터 집계

최근 저는 Discourse 대시보드 보고서에서 볼 수 있는 것과 유사한 데이터를 반환하지만, 시간 기간별로 데이터를 집계할 수 있도록 하는 Data Explorer 쿼리를 작성했습니다. 예를 들어, 주어진 시작일과 종료일 사이에 생성된 주제의 수를 표시하되, 일별이 아닌 주별 기간으로 합계를 계산하는 것입니다.

쿼리 매개변수는 다음 규칙에 따라 설정됩니다:

쿼리 매개변수: query_interval (Postgres interval, 예: ‘1 day’, ‘7 days’, ‘1 week’, ‘1 month’), start_date (‘yyyy-mm-dd’), end_date (‘yyyy-mm-dd’), category_ids (쉼표로 구분된 카테고리 ID 목록, 기본값 -1), include_subcategories (boolean, 기본값 true). 제공된 시작일과 종료일 사이에 생성된 게시물의 수를 반환합니다. 결과는 쿼리 간격별로 그룹화됩니다. category_ids 목록에 -1 값이 포함되어 있으면 모든 카테고리에 대한 결과가 반환됩니다.

기간별 첫 응답까지의 평균 소요 시간

--[params]
-- string :query_interval = 7 days
-- date :start_date
-- date :end_date
-- int_list :category_ids = -1
-- boolean :include_subcategories = true

WITH query_periods AS (
  SELECT generate_series(:start_date, :end_date, :query_interval::interval)::date as period_start
),
subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (:category_ids)
),
sub_subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (SELECT id FROM subcategory_ids)
),

topics_and_replies AS (
    SELECT
    t.created_at AS topic_created_at,
    p.topic_id AS reply_topic_id,
    p.created_at AS reply_created_at,
    period_start
    FROM topics t
    JOIN query_periods
    ON t.created_at::date >= period_start AND t.created_at::date < period_start + interval :query_interval
    JOIN posts p
    ON p.topic_id = t.id
    WHERE t.posts_count > 1
    AND t.archetype = 'regular'
    AND t.deleted_at IS NULL
    AND CASE
        WHEN -1 IN (:category_ids)
            THEN true
        WHEN :include_subcategories = false
            THEN t.category_id IN (:category_ids)
        ELSE t.category_id IN (:category_ids) OR t.category_id IN (SELECT id FROM subcategory_ids) OR t.category_id IN (SELECT id FROM sub_subcategory_ids)
    END
    AND p.post_number > 1
    AND p.post_type = 1
    AND p.deleted_at IS NULL
)

SELECT period_start, ROUND(AVG(reply_time_hours)::numeric, 2) AS response_time_hours FROM(
    SELECT
    qp.period_start,
    EXTRACT(EPOCH FROM MIN(reply_created_at) - topic_created_at):: float / 3600 AS reply_time_hours
    FROM query_periods qp
    JOIN topics_and_replies tar
    ON tar.period_start = qp.period_start
    GROUP BY reply_topic_id, topic_created_at, qp.period_start
) replies_for_period
GROUP BY period_start
ORDER BY period_start

기간별 총 해결된 수

--[params]
-- string :query_interval = 7 days
-- date :start_date
-- date :end_date
-- int_list :category_ids = -1
-- boolean :include_subcategories = true

WITH query_periods AS (
  SELECT generate_series(:start_date, :end_date, :query_interval::interval)::date as period_start
),
subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (:category_ids)
),
sub_subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (SELECT id FROM subcategory_ids)
)

SELECT
period_start,
COUNT(1) AS solved_count
FROM user_actions ua
JOIN query_periods
ON ua.created_at::date >= period_start AND ua.created_at::date < period_start + interval :query_interval
JOIN topics t
ON t.id = ua.target_topic_id
JOIN posts p 
ON p.id = ua.target_post_id
WHERE ua.action_type = 15
AND t.deleted_at IS NULL
AND p.deleted_at IS NULL
AND CASE
    WHEN -1 IN (:category_ids)
        THEN true
    WHEN :include_subcategories = false
        THEN t.category_id IN (:category_ids)
    ELSE t.category_id IN (:category_ids) OR t.category_id IN (SELECT id FROM subcategory_ids) OR t.category_id IN (SELECT id FROM sub_subcategory_ids)
END
GROUP BY period_start
ORDER BY period_start

기간별 주제 수

--[params]
-- string :query_interval = 7 days
-- date :start_date
-- date :end_date
-- int_list :category_ids = -1
-- boolean :include_subcategories = true

WITH query_periods AS (
  SELECT generate_series(:start_date, :end_date, :query_interval::interval)::date as period_start
),
subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (:category_ids)
),
sub_subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (SELECT id FROM subcategory_ids)
)

SELECT qp.period_start,
COUNT(t.id)
FROM query_periods qp
JOIN topics t
ON t.created_at::date >= qp.period_start AND t.created_at::date < qp.period_start + interval :query_interval
WHERE t.deleted_at IS NULL
AND t.archetype = 'regular'
AND CASE
    WHEN -1 IN (:category_ids)
        THEN true
    WHEN :include_subcategories = false
        THEN t.category_id IN (:category_ids)
    ELSE t.category_id IN (:category_ids) OR t.category_id IN (SELECT id FROM subcategory_ids) OR t.category_id IN (SELECT id FROM sub_subcategory_ids)
END
GROUP BY qp.period_start
ORDER BY qp.period_start

기간별 게시물 수

--[params]
-- string :query_interval = 7 days
-- date :start_date
-- date :end_date
-- int_list :category_ids = -1
-- boolean :include_subcategories = true

WITH query_periods AS (
  SELECT generate_series(:start_date, :end_date, :query_interval::interval)::date as period_start
),
subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (:category_ids)
),
sub_subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (SELECT id FROM subcategory_ids)
)

SELECT
period_start,
COUNT(p.id)
FROM query_periods qp
JOIN posts p
ON p.created_at::date >= qp.period_start AND p.created_at::date < qp.period_start + interval :query_interval
JOIN topics t
ON t.id = p.topic_id
WHERE t.archetype = 'regular'
AND t.deleted_at IS NULL
AND CASE
    WHEN -1 IN (:category_ids)
        THEN true
    WHEN :include_subcategories = false
        THEN t.category_id IN (:category_ids)
    ELSE t.category_id IN (:category_ids) OR t.category_id IN (SELECT id FROM subcategory_ids) OR t.category_id IN (SELECT id FROM sub_subcategory_ids)
END
AND p.deleted_at IS NULL
AND p.post_type = 1
GROUP BY period_start
ORDER BY period_start

@simon님 감사합니다 - 정말 훌륭합니다!

처음에는 기간(interval)을 선택할 때 start_date와 end_date 매개변수가 여전히 필수인 것이, 그리고 그 반대도 마찬가지인 것이 혼란스러웠습니다. 이제 보니 날짜 범위 내에서 X 기간 단위로 결과를 반환하는 방식이군요. 한 해 동안의 월별 변화를 빠르게 확인하거나 비슷한 시나리오에서 정말 유용합니다.

카테고리와 하위 카테고리 포함 기능도 좋습니다. 커뮤니티의 다양한 부분에서 활동을 추적하고 있기 때문에, 특정 카테고리 전체와 하위 카테고리의 성과를 빠르게 확인할 수 있어 매우 유용합니다.

이 쿼리를 수정하여 하위 카테고리 결과를 쉼표로 구분된 목록으로 표시할 수 있는 간단한 방법이 있을까요?

예: 기간 동안 카테고리 1에 작성된 게시물 (10개), 2 (20개) & 3 (30개).

쿼리에 1,2,3이라는 category_ids를 추가하면 합계(60개)가 반환됩니다. 10,20,30을 반환하는 방법을 찾고 싶습니다. 그러면 카테고리 간에 나란히 비교할 수 있을 것입니다.

가능합니다. 더 쉬운 접근법은 쿼리를 수정하여 각 카테고리에 대해 한 줄씩 반환하도록 하는 것입니다. 이는 마지막 GROUP BY 절에 카테고리 ID를 포함하도록 변경하면 됩니다. 제가 게시한 모든 예제에서 이를 시도해 보지는 않았지만, 아래는 이를 수행하는 “구간별 게시글 수” 쿼리의 수정 예시입니다:

--[params]
-- string :query_interval = 7 days
-- date :start_date
-- date :end_date
-- int_list :category_ids = -1
-- boolean :include_subcategories = true

WITH query_periods AS (
  SELECT generate_series(:start_date, :end_date, :query_interval::interval)::date as period_start
),
subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (:category_ids)
),
sub_subcategory_ids AS (
    SELECT id FROM categories
    WHERE parent_category_id IN (SELECT id FROM subcategory_ids)
)

SELECT
period_start,
t.category_id,
COUNT(p.id)
FROM query_periods qp
JOIN posts p
ON p.created_at::date >= qp.period_start AND p.created_at::date < qp.period_start + interval :query_interval
JOIN topics t
ON t.id = p.topic_id
WHERE t.archetype = 'regular'
AND t.deleted_at IS NULL
AND CASE
    WHEN -1 IN (:category_ids)
        THEN true
    WHEN :include_subcategories = false
        THEN t.category_id IN (:category_ids)
    ELSE t.category_id IN (:category_ids) OR t.category_id IN (SELECT id FROM subcategory_ids) OR t.category_id IN (SELECT id FROM sub_subcategory_ids)
END
AND p.deleted_at IS NULL
AND p.post_type = 1
GROUP BY period_start, t.category_id
ORDER BY period_start

제 개발 사이트에서 결과가 어떻게 보이는지 아래에 첨부했습니다:

정말 좋아요 - 다시 한번 감사드립니다! 제가 원하는 대로 작동할 것 같아요 :slight_smile:

이것은 정말 좋습니다. @simon 감사합니다.

간단한 질문을 해서 죄송하지만, 다음이 가능한가요?

  1. 대시보드의 ‘보고서(Reports)’ 섹션에 직접 작성한 보고서를 포함할 수 있으며, 어떻게 해야 하나요?
  2. DataExplorer에서 실행한 쿼리의 결과에 따라 특정 작업을 트리거할 수 있나요? 예를 들어 관리자에게 메시지를 보내는 것 같은 경우요.

감사합니다