Reputation: 13
I'm in a bit of a struggle right now, I've been looking all day but I haven't managed to find out how to do what I'm supposed to do.
I would like to do a view for my API using postgres (can be a function if needed) which will show the few top players (the one who have created the most Events)
The result I'm looking for would be an output like:
{"nickname": "Username" , ... , "total_event_created": 12, "Events":{"eventID": 1 , "event_name":CS:go, "total": 6}, {"eventID: 2 , "event_name": lol, "total": 6}
First I've got my Event table like that
CREATE TABLE "event" (
"id" INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
"user_id" INT NOT NULL REFERENCES "user"("id"),
"game_id" INT NOT NULL REFERENCES "game"("id"),
"event_time" TIMESTAMPTZ NOT NULL,
"duration" INTERVAL ,
"player_count" INT NOT NULL DEFAULT 1 CONSTRAINT max_player CHECK (player_count <= player_max),
"player_max" INT NOT NULL ,
"description" TEXT,
"status" INT NOT NULL DEFAULT 0 ,
"vocal" TEXT);
I've tried a lot of different things using json_agg()
but I never manage to get something right.
So far my best try is this one:
SELECT DISTINCT ON (e.user_id) "user_id" AS "_user_id",
us.nickname AS "_name",
us.avatar AS "_avatar",
us.banner AS "_banner",
(SELECT COUNT(id) FROM user_access."event" ev WHERE us.id = ev.user_id) AS "_total_events",
(SELECT COUNT (CASE WHEN g.id = 1 THEN 1 END) ) AS "_total_cs",
(SELECT COUNT (CASE WHEN g.id = 2 THEN 1 END) ) AS "_total_lol"
FROM user_access."user" us
JOIN user_access."event" e ON us.id = e.user_id
JOIN user_access."game" g ON e.game_id = g.id
GROUP BY e.user_id, us.nickname, us.id ;
output:
_user_id | _name | _avatar | _banner | _total_events | _total_cs | _total_lol
----------+------------+-------------+-------------+---------------+-----------+------------
1 | test1login | avatar1.png | banner1.png | 3 | 2 | 1
2 | test2login | avatar2.png | banner2.png | 1 | 0 | 1
Thanks guy's for clearing my code, from what you send me i arrived to something better :
SELECT us."id" AS "_user_id",
us.nickname AS "_name",
us.avatar AS "_avatar",
us.banner AS "_banner",
COUNT(DISTINCT e.id) as "_total_events",
jsonb_build_object('cs', COUNT(*) FILTER (WHERE g.id = 1 ),'lol', COUNT(*) FILTER (WHERE g.id = 2)) AS "_total_by_game"
FROM user_access."user" us
JOIN user_access."event" e ON us.id = e.user_id
JOIN user_access."game" g ON e.game_id = g.id
GROUP BY us.id
ORDER BY "_total_events" DESC;
output:
_user_id | _name | _avatar | _banner | _total_events | _total_by_game
----------+------------+-------------+-------------+---------------+---------------------
1 | test1login | avatar1.png | banner1.png | 3 | {"cs": 2, "lol": 1}
2 | test2login | avatar2.png | banner2.png | 1 | {"cs": 0, "lol": 1}
Upvotes: 1
Views: 66
Reputation: 94884
There are several issues in your query:
DISTINCT ON
user ID. Why? The GROUP BY
already gives you one row per user. Then with DISTINCT ON
you say: from this one row, give me one row, no matter which (because there is no ORDER BY
clause that you'd usually have for DISTINCT ON
, but well, you only have one row per user anyway, so this all is just void).(SELECT COUNT (CASE WHEN g.id = 1 THEN 1 END) )
is invalid and should raise an error. It can be re-written to CASE WHEN g.id = 1 THEN 1 ELSE 0 END
. But as you are aggregating per user there can be multiple g.id. Which one do you want to show? There would have to be an aggregation function for this to work, e.g. SUM((SELECT COUNT (CASE WHEN g.id = 1 THEN 1 END)))
, which of course would again just be SUM(CASE WHEN g.id = 1 THEN 1 ELSE 0 END)
.It seems you just want an aggregation:
SELECT
us.id AS "_user_id",
us.nickname AS "_name",
us.avatar AS "_avatar",
us.banner AS "_banner",
COUNT(*) AS "_total_events",
COUNT(CASE WHEN g.id = 1 THEN 1 END) AS "_total_cs",
COUNT(CASE WHEN g.id = 2 THEN 1 END) AS "_total_lol"
FROM user_access.user us
JOIN user_access.event e ON e.user_id = us.id
JOIN user_access.game g ON g.id = e.game_id
GROUP BY us.id
For the top 5 add:
ORDER BY COUNT(*) DESC
FETCH FIRST 5 ROWS ONLY;
BTW: Do you really want to count how many events a user had in game 1 (_total_cs) and 2 (_total_lol) or is this a typo?
Upvotes: 1
Reputation: 1269563
You can use conditional aggregation:
SELECT us."user_id" AS "_user_id",
us.nickname AS "_name",
us.avatar AS "_avatar",
us.banner AS "_banner",
COUNT(DISTINCT e.id) as "_total_events",
COUNT(*) FILTER (WHERE g.id = 1 ) AS "_total_cs",
COUNT(*) FILTER (WHERE g.id = 2) AS "_total_lol"
FROM user_access."user" us JOIN
user_access."event" e
ON us.id = e.user_id JOIN
user_access."game" g
ON e.game_id = g.id
GROUP BY us.id ;
Upvotes: 1