(SQL) Match users belong to which group given user_id[] - sql

user table
ID | name
1 | ada
2 | bob
3 | tom
group Table
ID | name
1 | group A
2 | group B
3 | group C
user_group Table
user_id | group_id
1 | 1
2 | 1
1 | 2
2 | 2
3 | 2
1 | 3
3 | 3
Given group of user ids : [1, 2, 3]
How to query the group that all users in the above list belongs to? (in this case: Group B)

To get all groups that contain exactly the specified users (i.e. all specified users and no other users)
DECLARE #numUsers int = 3
SELECT ug.group_id
--The Max doesn't really do anything here because all
--groups with the same group id have the same name. The
--max is just used so we can select the group name eventhough
--we aren't aggregating across group names
, MAX(g.name) AS name
FROM user_group ug
--Filter to only groups with three users
JOIN (SELECT group_id FROM user_group GROUP BY group_id HAVING COUNT(*) = #numUsers) ug2
ON ug.group_id = ug2.group_id
JOIN [group] g
ON ug.group_id = g.ID
WHERE user_id IN (1, 2, 3)
GROUP BY ug.group_id
--The distinct is only necessary if user_group
--isn't keyed by group_id, user_id
HAVING COUNT(DISTINCT user_id) = #numUsers
To get groups that contain all specified users:
DECLARE #numUsers int = 3
SELECT ug.group_id
--The Max doesn't really do anything here because all
--groups with the same group id have the same name. The
--max is just used so we can select the group name eventhough
--we aren't aggregating across group names
, MAX(g.name) AS name
FROM user_group ug
JOIN [group] g
ON ug.group_id = g.ID
WHERE user_id IN (1, 2, 3)
GROUP BY ug.group_id
--The distinct is only necessary if user_group
--isn't keyed by group_id, user_id
HAVING COUNT(DISTINCT user_id) = 3
SQL Fiddle: http://sqlfiddle.com/#!6/0e968/3

Try This:
Select t2.name
FROM
(Select group_id
From
user_group
Group by group_id
Having Count(user_id) = (Select Count(*) FROM User_Table)) AS T1
INNER JOIN
Group_Table AS T2
ON T1.group_id = T2.ID
See Fiddle: http://sqlfiddle.com/#!2/fa7250/4

Select UserID,count(*)
From UserGroupTable
group by UserID
This will give a count of 3 where the UserID/GroupID is unique (as zerkms pointed out)

SELECT name FROM group_tbl WHERE id IN (SELECT g_id FROM user_grp GROUP BY g_id HAVING Count(u_id)=(SELECT Count(id) FROM user_tbl));

Related

SQL: Find country name of the team having the most players who have never scored a goal

I have the following tables:
create table Players (
id integer,
name varchar(50) not null,
birthday date,
memberOf integer not null,
position varchar(20).
primary key (id),
foreign key (memberOf) references Teams(id)
);
create table Goals (
id integer,
scoredIn integer not null,
scoredBy integer not null,
timeScored integer not null,
rating varchar(20),
primary key (id),
foreign key (scoredIn) references Matches(id),
foreign key (scoredBy) references Players(id)
);
create table Teams (
id integer,
country varchar(50) not null,
primary key (id)
);
I have the following data in the above tables:
PLAYERS:
id | name | birthday | memberof | position
7 Mina 1997-01-20 1 Captain
9 John 1997-09-01 1 Quarterback
2 Minnie 1995-10-13 3 Goalkeeper
13 Lisa 1997-03-27 4 Captain
12 Rina 1995-01-03 2 Fullback
11 Jasper 2002-09-22 1 Halfback
17 Rose 1997-02-11 1 Goalkeeper
22 Parvin 1993-03-09 3 Goalkeeper
25 Nasom 1996-12-29 3 Fullback
GOALS:
id | scoredin | scoredby | timescored | rating
1 10 7 60 amazing
2 10 7 30 okay
3 10 7 90 amazing
4 20 9 119 nice
5 20 9 80 amazing
6 20 9 75 amazing
7 30 2 30 nice
8 30 2 90 amazing
9 40 13 110 amazing
TEAMS:
id | country
1 Australia
2 Malaysia
3 Japan
4 Thailand
I am trying to output the country name of the team which has the most players who have never scored a goal. The output should be:
Country | Players
Australia 2
Japan 2
I have the following view, which gives the count of players who have never scored a goal for each country:
create or replace view zerogoals as
select t.country, count(*)
from (
select distinct p.id, p.name, p.memberof, g.scoredby
from players p
full outer join goals g
on p.id = g.scoredby where scoredby is null
) s
inner join teams t on t.id = s.memberof group by t.country;
The above query gives me the following output:
country | count
Australia 2
Japan 2
Malaysia 1
I tried using the max function to get the desired output:
select country, max(count)
from zerogoals
group by country;
However I get the following output:
country | max
Australia 2
Japan 2
Malaysia 1
I am not sure how to get the tuples in the view zerogoals with the maximum value for the attribute count. Any insights are appreciated.
You can use a CTE:
with cte as (
select
t.id, t.country, count(*) players
from teams t inner join (
select * from players
where id not in (select scoredby from goals)
) p on p.memberOf = t.id
group by t.id, t.country
)
select country, players
from cte
where players = (select max(players) from cte)
order by country
See the demo.
Results:
country | players
Australia | 2
Japan | 2
You could try using a inner join between the player, team and the list of not in goals ordered by count and limit to 1
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) ) t1 on t1.id = p.id
group by t.name
order by count(*) desc
limit 1
if you want all the max then
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) t1 on t1.id = p.id
group by t.name
having count(*) = (
select t.name , count(*)
from player p
INNER JOIN team t ON t.id = p.memberof
inner join (
select p.id
from PLAYERS p
where p.id NOT IN (
select scoredby
from GOALS
) t1 on t1.id = p.id
group by t.name
order by count(*)
limit 1
)
To get the number of players per country with no goal, you can use:
select t.name, count(*) as num_players_no_goal
from team.t join
player p
on t.id = p.memberof
where not exists (select 1
from goals g
where g.scoredby = p.id
)
group by t.name;
To limit this to just the maximum number, use window functions:
select name, num_players_no_goal
from (select t.name, count(*) as num_players_no_goal,
rank() over (order by count(*) desc) as seqnum
from team.t join
player p
on t.id = p.memberof
where not exists (select 1
from goals g
where g.scoredby = p.id
)
group by t.name
) t
where seqnum = 1;
One slight caveat is that this returns no teams if all players on all teams have scored goals. It is easily modified for that situation, but I'm guessing that you would rather return zero teams than all teams if that were the case.

WHERE condition for Intersection table

Let's assume I have the following intersection table MemberClub:
MemberID | ClubId
1 | 2
2 | 1
2 | 2
2 | 3
I'd like to get all members which are in the clubs with ID 1 and 2. So the result should be member with ID 2.
How would my where statement look like? I tried different variations but I'm not sure which one to choose.
Expected output should be:
MemberID | ClubId
2 | 1
2 | 2
I need a flexible version which works for a flexible number of clubs (it could be that I want all members being in club with just ID 1. Or all members being in club with ID 2, 4 and 6).
General GROUP BY with HAVING COUNT(DISTINCT) solution:
select MemberID
from MemberClub
WHERE ClubId IN (1,2)
GROUP BY MemberID
HAVING COUNT(DISTINCT ClubId) = 2
I.e. make sure there are two different ClubId's for a user.
Alternatively, make sure there are different ClubId's for a user (works just in the two club case):
select MemberID
from MemberClub
WHERE ClubId IN (1,2)
GROUP BY MemberID
HAVING max(ClubId) <> min(ClubId)
Or skip the GROUP BY, do a self JOIN instead:
select distinct m1.MemberID
from MemberClub m1
join MemberClub m2 on m1.MemberID = m2.MemberID
where m1.ClubId = 1
and m2.ClubId = 2
To return members who are in all clubs:
select MemberID
from MemberClub
GROUP BY MemberID
HAVING COUNT(DISTINCT ClubId) = (select count(distinct ClubId) from MemberClub)

How to get max count of referral to a root node in a tree

I've a table with users by structure like this:
id name parent_id
1 Mike
2 Jack 1
3 Sam 1
4 Kurt 1
5 Somebody 3
6 Tommy 4
6 etc.. 2
How to get a max count of referral on first level nesting per user, by this example I expect result:
3 because Jack, Sam, Kurt is a referral of Mike on first level
Assuming "first level" is defined by parent_id IS NULL and the current version Postgres 9.4:
SELECT parent_id, count(*) AS referral_ct
FROM (
SELECT id AS parent_id
FROM tbl
WHERE t1.parent_id IS NULL
) t1
JOIN tbl t2 USING (parent_id)
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1; -- to only get 1 row with max. referral_ct
With only few root nodes, JOIN LATERAL may be faster:
SELECT t1.id, t2.referral_ct
FROM (
SELECT id
FROM tbl
WHERE parent_id IS NULL
) t1
LEFT JOIN LATERAL (
SELECT parent_id, count(*) AS referral_ct
FROM tbl
WHERE parent_id = t1.id
GROUP BY 1
) t2 ON true
ORDER BY 2 DESC
LIMIT 1; -- to only get 1 row with max. referral_ct
Related, with more explanation:
Optimize GROUP BY query to retrieve latest record per user

Left Join with Group By

I am using PostgreSQL 9.4.
I have a table of workouts. Users can create multiple results for each workout, and a result has a score.
Given a list of workout_ids and two user_ids, I want to return the best score for each workout for each user. If the user does not have a result for that workout, I want to return a padded/null result.
SELECT "results".*, "workouts".*
FROM "results" LEFT JOIN "workouts" ON "workouts"."id" = "results"."workout_id"
WHERE (
(user_id, workout_id, score) IN
(SELECT user_id, workout_id, MAX(score)
FROM results WHERE user_id IN (1, 2) AND workout_id IN (1, 2, 3)
GROUP BY user_id, workout_id)
)
In this query, the left join is acting as an inner join; I'm not getting any padding if the user has not got a result for the workout. This query should always return six rows, regardless of how many results exist.
Example data:
results
user_id | workout_id | score
-----------------------------
1 | 1 | 10
1 | 3 | 10
1 | 3 | 15
2 | 1 | 5
Desired result:
results.user_id | results.workout_id | max(results.score) | workouts.name
-------------------------------------------------------------------------
1 | 1 | 10 | Squat
1 | 2 | null | Bench
1 | 3 | 15 | Deadlift
2 | 1 | 5 | Squat
2 | 2 | null | Bench
2 | 3 | null | Deadlift
The where filters out your NULL values, so that is why the result is not what you expect.
Joinint the WHERE clause results instead of filter the where clause results.
SELECT "results".*, "workouts".*,"max_score".*
FROM "results"
LEFT JOIN "workouts" ON "workouts"."id" = "results"."workout_id"
LEFT JOIN (SELECT user_id, workout_id, MAX(score)
FROM results WHERE user_id IN (1, 2) AND workout_id IN (1, 2, 3)
GROUP BY user_id, workout_id) max_score ON workouts.workout_id=max_score.workout_id;
You need to alter the SELECT to get the correct columns.
SELECT DISTINCT ON (1, 2)
u.user_id
, w.id AS workout_id
, r.score
, w.name AS workout_name
FROM workouts w
CROSS JOIN (VALUES (1), (2)) u(user_id)
LEFT JOIN results r ON r.workout_id = w.id
AND r.user_id = u.user_id
WHERE w.id IN (1, 2, 3)
ORDER BY 1, 2, r.score DESC NULLS LAST;
Step by step explanation
Form a complete Cartesian product of given workouts and users.
Assuming the given workouts always exist.
Assuming that not all given users have results for all given workouts.
LEFT JOIN to results. All conditions go into the ON clause of the LEFT JOIN, not into the WHERE clause, which would exclude (workout_id, user_id) combinations that have no result. See:
Rails includes query with conditions not returning all results from left table
Finally pick the best result per (user_id, workout_id) with DISTINCT ON. While being at it, produce the desired sort order. See:
Select first row in each GROUP BY group?
Depending on the size of tables and data distribution there may be faster solutions. See:
Optimize GROUP BY query to retrieve latest row per user
Simple version
If all you want is the maximum score for each (user_id, workout_id) combination, there is simple version:
SELECT user_id, workout_id, max(r.score) AS score
FROM unnest('{1,2}'::int[]) u(user_id)
CROSS JOIN unnest('{1,2,3}'::int[]) w(workout_id)
LEFT JOIN results r USING (user_id, workout_id)
GROUP BY 1, 2
ORDER BY 1, 2;
db<>fiddle here
Old sqlfiddle.
How about using distinct on or row_number()?
SELECT DISTINCT ON (r.user_id, r.workout_id) r.*, w.*
FROM "results" r LEFT JOIN
"workouts" w
ON "w."id" = r."workout_id"
WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3)
ORDER BY r.user_id, r.workout_id, score desc;
The row_number() equivalent requires a subquery:
SELECT rw.*
FROM (SELECT r.*, w.*,
row_number() over (partition by user_id, workout_id order by score desc) as seqnum
FROM "results" r LEFT JOIN
"workouts" w
ON "w."id" = r."workout_id"
WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3)
) rw
WHERE seqnum = 1;
You should choose the columns more judiciously than using a *. The subquery might return errors in the case of duplicate column names.
EDIT:
You need to generate the rows first, and then the results for each. Here is one method, building on the second query:
SELECT u.user_id, w.workout_id, rw.score, rw.name
FROM (SELECT 1 as user_id UNION ALL SELECT 2) u CROSS JOIN
(SELECT 1 as workout_id UNION ALL SELECT 2 UNION ALL SELECT 3) w LEFT JOIN
(SELECT r.*, w.*,
row_number() over (partition by user_id, workout_id order by score desc) as seqnum
FROM "results" r LEFT JOIN
"workouts" w
ON "w."id" = r."workout_id"
WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3)
) rw
ON rw.user_id = u.user_id and rw.workout_id = w.workout_id and
rw.seqnum = 1;

SQL: Select from many-many through, joined on conditions in through table

Can't get my head around this...
I have 3 tables like this:
Computers
---------
Id
Name
ComputerLogins
--------------
Computer_Id
User_Id
NumberOfLogins
Users
-----
Id
Name
Computers "have and belong to many" Users "through" ComputerLogins.
Sample data:
Computers: Id Name
1 "Alpha"
2 "Beta"
3 "Gamma"
Users: Id Name
1 "Joe"
2 "Fred"
ComputerLogins: Computer_Id User_Id NumberOfLogins
1 1 5
1 2 12
2 1 10
2 2 6
3 1 2
3 2 4
I'm trying to construct a view that will output one row for each record in Computers, and join a Users row through MAX(NumberOfLogins) in ComputerLogins.
Desired output:
Computer_Id User_Id NumberOfLogins
1 2 12
2 1 10
3 2 4
Can you suggest a view query that will produce the desired output?
Thanks!
SELECT
CL.*, U.* --change this as needed
FROM
(
SELECT
Computer_ID, MAX(NumberOfLogins) AS NumberOfLogins
FROM
ComputerLogins
GROUP BY
Computer_ID
) maxC
JOIN
ComputerLogins CL On maxC.Computer_ID = CL.Computer_ID AND maxC.NumberOfLogins = CL.NumberOfLogins
JOIN
Users U On CL.User_ID = U.ID
Wrap in a view etc
Use:
CREATE VIEW your_view AS
SELECT c.id AS computer_id,
u.id AS user_id,
COUNT(*) AS NumberOfLogins
FROM COMPUTERS c
JOIN COMPUTERLOGINS cl ON cl.computer_id = c.id
JOIN USERS u ON u.id = cl.user_id
GROUP BY c.id, u.id