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
- 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
- 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.
- Rename Non-Staff Likes Column:
Changed the output label for non-staff likes from likes_count
to regular_likes_count
.
- Add Total Likes:
Introduced a total_likes
column to sum regular_likes_count
and staff_likes_count
.
- 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:
- 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
- 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.
- Add Total Likes:
- Include a
total_likes
column that sums regular_likes_count
and staff_likes_count
.
- Add Posts Per Period:
- Add a
posts_per_week
column that counts the number of posts created during each week.
- 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.
- 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).
- Order and Final Columns:
- Ensure the output is ordered by
week_start
and includes the following columns in this order:
week_start
: The start date of the week.
regular_likes_count
: The count of likes from non-staff users.
staff_likes_count
: The count of likes from staff users.
total_likes
: The sum of regular_likes_count
and staff_likes_count
.
posts_per_week
: The number of posts created during the week.