The discussion revolves around creating a SQL query in the Data Explorer to identify active users—those who have either posted or read content in the last year. nathank initially sought help combining data from posts and user visits, struggling with the UNION operator. Heddson provided a working query using UNION to merge users who posted or read, which nathank acknowledged as helpful. However, nathank later refined the approach, using subqueries and LEFT JOINs to include group-specific data and detailed user stats, avoiding UNION’s limitations. The query now returns user IDs, usernames, last seen, posts created, and posts read, filtering for active users in a specified group. satonotdead asked about adapting the query for badge systems, which require a granted_at column. nathank explained the need to modify the query to return only user_id and granted_at, noting the query’s potential performance impact if run too frequently.
This should give you every user that have either read whilst logged in or posted over the last year. The data explorer will also print out how many they are.
SELECT p.user_id
FROM posts p
LEFT JOIN topics t ON t.id = p.topic_id
WHERE p.created_at::date > CURRENT_TIMESTAMP - INTERVAL '365 days'
AND t.deleted_at IS NULL
AND t.visible = TRUE
AND t.closed = FALSE
AND t.archived = FALSE
AND t.archetype = 'regular'
AND p.deleted_at IS NULL
UNION
SELECT u.user_id
FROM user_visits u
WHERE u.posts_read > 0
AND u.visited_at > CURRENT_TIMESTAMP - INTERVAL '365 days'
ORDER BY user_id
There’s probably a more effective way of doing it, but it works . If you want a specific period you could change > CURRENT_TIMESTAMP - INTERVAL '365 days' (both of them) to something like this: BETWEEN '20200101'::date AND '20210101'::date .
You provided great info and references in your question! I only had to pick the right stuff and combine them.
I actually ended up doing something a little different as I wanted more info and UNION proved a bit restrictive. I also wanted to break it down by groups. I achieved success (I think) by hacking the included Active Lurkers query with another one I have for extracting group specific info:
-- [params]
-- int :number_of_days = 365
-- string :group_name = trust_level_0
With included_users AS (
SELECT
gu.user_id
FROM group_users gu
JOIN groups g
ON g.id = gu.group_id
WHERE g.name = :group_name
),
posts_by_user AS (
SELECT COUNT(*) AS posts, p.user_id
FROM posts p
LEFT JOIN topics t ON t.id = p.topic_id
WHERE p.created_at::date > CURRENT_TIMESTAMP - INTERVAL ':number_of_days' day
AND t.deleted_at IS NULL
AND t.visible = TRUE
AND t.closed = FALSE
AND t.archived = FALSE
AND t.archetype = 'regular'
AND p.deleted_at IS NULL
GROUP BY p.user_id
),
posts_read_by_user AS (
SELECT SUM(posts_read) AS posts_read, uv.user_id
FROM user_visits uv
WHERE uv.posts_read > 0
AND uv.visited_at > CURRENT_TIMESTAMP - INTERVAL ':number_of_days' day
GROUP BY uv.user_id
)
SELECT
u.id AS "user_id",
u.username_lower AS "username",
u.last_seen_at,
COALESCE(pbu.posts, 0) AS "posts_created",
COALESCE(prbu.posts_read, 0) AS "posts_read"
FROM users u
LEFT JOIN posts_by_user pbu ON pbu.user_id = u.id
LEFT JOIN posts_read_by_user prbu ON prbu.user_id = u.id
WHERE u.active = true
AND u.id > 0
AND u.id IN (SELECT user_id FROM included_users)
AND (COALESCE(pbu.posts, 0) > 0 OR COALESCE(prbu.posts_read, 0) > 0)
ORDER BY u.id
Badge queries are pretty curly - and they require simply a ‘user_id’ and ‘granted_at’ to be returned. So you’ll need to hack this a bit.
I’d recommend digging into the topics on badge queries (read them carefully) and have a bit of a go. I’d also only run it once a day as it might be a bit heavy otherwise.