Dashboard Report - Likes

This is an SQL version of the Dashboard Report for Likes.

This query provides a report of the total number of likes given to all posts on a site, on a day-to-day basis, within a specified date range.

-- [params]
-- date :start_date = 2023-12-08
-- date :end_date = 2024-01-10

WITH date_range AS (
  SELECT date_trunc('day', series) AS date
  FROM generate_series(
    :start_date::timestamp,
    :end_date::timestamp,
    '1 day'::interval
  ) series
)

SELECT
  dr.date::date,
  COALESCE(pa.likes_count, 0) AS likes_count
FROM
  date_range dr
LEFT JOIN (
  SELECT
    date_trunc('day', pa.created_at) AS action_date,
    COUNT(*) AS likes_count
  FROM post_actions pa
  WHERE pa.post_action_type_id = 2 
    AND pa.created_at >= :start_date
    AND pa.created_at <= (:end_date::date + 1)
  GROUP BY action_date
) pa ON dr.date = pa.action_date
ORDER BY dr.date

SQL Query Explanation

The main structure of the query builds upon a CTE (Common Table Expression) named date_range, which is used to generate a series of timestamps, each representing a different day within the user-defined period.

Parameters

The query accepts two parameters:

  • :start_date: The beginning of the period for which to generate the report.
  • :end_date: The end of the period for which to generate the report.

Common Table Expression: date_range

  • generate_series is a function that creates a set of timestamps from :start_date to :end_date, incremented by an interval of ‘1 day’.
  • date_trunc('day', series) truncates the timestamp to the start of the day, effectively normalizing all timestamps to 00:00:00 of their respective days.
  • The result is a set of dates, one per row, covering the entire range from :start_date to :end_date.

Subquery: Counting Likes

A subquery is used to calculate the number of likes for each day by counting rows from the post_actions table.

  • This query filters post_actions for entries where the action type signifies a like (where post_action_type_id = 2 means ‘like’).
  • It filters the actions to the date range, adding one day to the end date to include likes given on the last day.
  • It groups the results by day and counts the likes for each day.

Main Query: Merging Results

The final section of the query merges the set of all dates from the date_range CTE with the count of likes from the subquery.

  • A LEFT JOIN ensures that all dates from date_range are included in the result, even if there are no corresponding like actions for a given date (no joins found in the subquery).
  • COALESCE is used to replace NULL counts (from days without likes) with zero, ensuring the report accurately reflects days without any like activity.
  • The final result set is ordered by date to provide a chronological view of the likes given over the specified period.

Example Results

date likes_count
2023-12-08 123
2023-12-09 156
2023-12-10 278
2023-12-11 134
2023-12-12 89
2 Likes

Does this one need a AND pa.deleted_at IS NULL to filter out the Likes cast and then removed to marry up, or would that be a possible change to the dashboard query itself?

2 Likes

As it is currently, the dashboard report does include deleted likes, so adding AND pa.deleted IS NULL would change how this query is matching up with the dashboard report.

Modifying the underlying dashboard report to not include deleted likes could be a good change to consider making to the report though.

2 Likes

My forum isn’t very big, and most of our like reactions come from “staff” (admins, moderators, TL=4). I wanted to see how likes from regular users vs “staff” compared, and list the number of posts/day for a better sense of what’s going on and where we need to concentrate efforts to improve use of reactions.

Me and my pal ChatGPT came up with this:

-- [params]
-- date :start_date = 2024-01-01
-- date :end_date = 2024-12-31

WITH date_range AS (
  SELECT date_trunc('day', series) AS date
  FROM generate_series(
    :start_date::timestamp,
    :end_date::timestamp,
    '1 day'::interval
  ) series
),
staff_users AS (
  SELECT id
  FROM users
  WHERE admin = true OR moderator = true OR trust_level = 4
)

SELECT
  dr.date::date,
  COALESCE(pa_non_staff.regular_likes_count, 0) AS regular_likes_count,
  COALESCE(pa_staff.staff_likes_count, 0) AS staff_likes_count,
  COALESCE(pa_non_staff.regular_likes_count, 0) + COALESCE(pa_staff.staff_likes_count, 0) AS total_likes,
  COALESCE(posts_count.posts_per_day, 0) AS posts_per_day
FROM
  date_range dr
LEFT JOIN (
  SELECT
    date_trunc('day', pa.created_at) AS action_date,
    COUNT(*) AS regular_likes_count
  FROM post_actions pa
  WHERE pa.post_action_type_id = 2 
    AND pa.user_id NOT IN (SELECT id FROM staff_users)
    AND pa.created_at >= :start_date
    AND pa.created_at <= (:end_date::date + 1)
  GROUP BY action_date
) pa_non_staff ON dr.date = pa_non_staff.action_date
LEFT JOIN (
  SELECT
    date_trunc('day', pa.created_at) AS action_date,
    COUNT(*) AS staff_likes_count
  FROM post_actions pa
  WHERE pa.post_action_type_id = 2
    AND pa.user_id IN (SELECT id FROM staff_users)
    AND pa.created_at >= :start_date
    AND pa.created_at <= (:end_date::date + 1)
  GROUP BY action_date
) pa_staff ON dr.date = pa_staff.action_date
LEFT JOIN (
  SELECT
    date_trunc('day', p.created_at) AS post_date,
    COUNT(*) AS posts_per_day
  FROM posts p
  WHERE p.created_at >= :start_date
    AND p.created_at <= (:end_date::date + 1)
  GROUP BY post_date
) posts_count ON dr.date = posts_count.post_date
ORDER BY dr.date

Changes to @SaraDev’s original query (thanks, Sara!):
SQL Changes Summary

  1. Generate Staff Group:
    Added a staff_users CTE to identify staff users from the users table. A staff user is defined as any of:
  • admin = true
  • moderator = true
  • trust_level = 4
  1. Separate Staff Likes:
    Added a subquery to calculate the likes count from staff users (staff_likes_count) by filtering post_actions for user_id in the staff_users group.
  2. Rename Non-Staff Likes Column:
    Changed the output label for non-staff likes from likes_count to regular_likes_count.
  3. Add Total Likes:
    Introduced a total_likes column to sum regular_likes_count and staff_likes_count.
  4. Add Posts Per Day:
    Added a subquery to calculate the number of posts per day (posts_per_day) and joined it to the date range.
    (Yeah ChatGPT made this list of changes for me too.)

Example results:

date regular_likes_count staff_likes_count posts_per_day
1/1/24 0 6 7
1/2/24 0 5 3
1/3/24 1 0 4
1/4/24 1 2 5
1/5/24 9 9 30
1/6/24 0 1 11
1/7/24 2 4 11
1/8/24 0 5 18
1/9/24 0 0 2
1/10/24 0 0 7
1/11/24 0 4 5
1/12/24 4 0 4
1/13/24 6 0 10
1/14/24 1 7 18
1/15/24 2 4 7
Same query reported by weeks to smooth
-- [params]
-- integer :weeks_ago = 52

WITH date_range AS (
  SELECT date_trunc('week', series) AS week_start
  FROM generate_series(
    date_trunc('week', now()) - (:weeks_ago || ' weeks')::interval,
    date_trunc('week', now()),
    '1 week'::interval
  ) series
),
staff_users AS (
  SELECT id
  FROM users
  WHERE admin = true OR moderator = true OR trust_level = 4
)

SELECT
  dr.week_start::date AS week_start,
  COALESCE(pa_non_staff.regular_likes_count, 0) AS regular_likes_count,
  COALESCE(pa_staff.staff_likes_count, 0) AS staff_likes_count,
  COALESCE(pa_non_staff.regular_likes_count, 0) + COALESCE(pa_staff.staff_likes_count, 0) AS total_likes,
  COALESCE(posts_count.posts_per_week, 0) AS posts_per_week
FROM
  date_range dr
LEFT JOIN (
  SELECT
    date_trunc('week', pa.created_at) AS action_week,
    COUNT(*) AS regular_likes_count
  FROM post_actions pa
  WHERE pa.post_action_type_id = 2
    AND pa.user_id NOT IN (SELECT id FROM staff_users)
    AND pa.created_at >= date_trunc('week', now()) - (:weeks_ago || ' weeks')::interval
    AND pa.created_at <= date_trunc('week', now())
  GROUP BY action_week
) pa_non_staff ON dr.week_start = pa_non_staff.action_week
LEFT JOIN (
  SELECT
    date_trunc('week', pa.created_at) AS action_week,
    COUNT(*) AS staff_likes_count
  FROM post_actions pa
  WHERE pa.post_action_type_id = 2
    AND pa.user_id IN (SELECT id FROM staff_users)
    AND pa.created_at >= date_trunc('week', now()) - (:weeks_ago || ' weeks')::interval
    AND pa.created_at <= date_trunc('week', now())
  GROUP BY action_week
) pa_staff ON dr.week_start = pa_staff.action_week
LEFT JOIN (
  SELECT
    date_trunc('week', p.created_at) AS post_week,
    COUNT(*) AS posts_per_week
  FROM posts p
  WHERE p.created_at >= date_trunc('week', now()) - (:weeks_ago || ' weeks')::interval
    AND p.created_at <= date_trunc('week', now())
  GROUP BY post_week
) posts_count ON dr.week_start = posts_count.post_week
ORDER BY dr.week_start

In case it's interesting, here's the final prompts that modified Sara's query:

I have an SQL query that reports the daily likes count (likes_count) between two dates, but I need to make the following enhancements to produce a final output that aggregates data by weeks and includes additional details:

  1. Define a Staff Group:
  • Create a staff_users group from the users table. A user should be considered staff if they meet any of the following criteria:
    • admin = true
    • moderator = true
    • trust_level = 4
  1. Separate Likes by Staff and Non-Staff:
  • Add two separate columns:
    • regular_likes_count: Count likes from non-staff users.
    • staff_likes_count: Count likes from staff users.
  • Ensure the regular_likes_count column excludes likes generated by staff users.
  1. Add Total Likes:
  • Include a total_likes column that sums regular_likes_count and staff_likes_count.
  1. Add Posts Per Period:
  • Add a posts_per_week column that counts the number of posts created during each week.
  1. Aggregate by Weeks:
  • Modify the query to group all data by weekly intervals instead of daily.
  • Include a week_start column that represents the start date of each week.
  1. Limit by Weeks Ago:
  • Introduce a parameter :weeks_ago to limit the results to the past N weeks. The default value should be 52 weeks (1 year).
  1. Order and Final Columns:
  • Ensure the output is ordered by week_start and includes the following columns in this order:
    1. week_start: The start date of the week.
    2. regular_likes_count: The count of likes from non-staff users.
    3. staff_likes_count: The count of likes from staff users.
    4. total_likes: The sum of regular_likes_count and staff_likes_count.
    5. posts_per_week: The number of posts created during the week.
2 Likes