Join 2 tables on foreign key while using count() in SQL - sql

So I have two tables: Please see the ER diagram here
I want to use SELECT to create one table with "name" from the USER table, "id" as the foreign key for the two tables, and the count of friend_id as the number of friends each user has.
Here is my code:
SELECT name, id, (SELECT count(friend_id) as number
FROM friend
GROUP BY user_id)
FROM user
ORDER BY number DESC
I'm wondering what's the problem with these lines. Thank you!

You can use a subquery to calculate the count.
SELECT name, id, COALESCE(f.Count, 0) AS friend_count
FROM user u
LEFT JOIN (
SELECT user_id, COUNT(DISTINCT friend_id) AS Count
FROM friend
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY friend_count DESC
I used a LEFT JOIN so that if a user doesn't have a row in friend, it will still return a row with a friend count of 0 (thanks to COALESCE). I also added a DISTINCT so that if the friend has duplicates the friend is counted only one, might not be necessary especially if you have a UNIQUE INDEX setup on columns user_id, friend_id

Just add where to find only one id and remove group by because you have only one id for one or more friends as your diagram says.
SELECT name, id, (SELECT count(friend_id) as number
FROM friend
WHERE user_id = user.id)
FROM user
ORDER BY number DESC

I think this will be correct for you puprose
CREATE TABLE #user(
id VARCHAR(22),
[name] VARCHAR(255),
)
CREATE TABLE #friend(
user_id VARCHAR(22),
friend_id VARCHAR(22)
)
SELECT name, id, (SELECT COALESCE(COUNT(friend_id), 0)
FROM #friend f
WHERE f.user_id = u.id
GROUP BY user_id) as number
FROM #user u
ORDER BY number DESC
--Same query with join:
SELECT u.[name], u.id, COALESCE(COUNT(f.friend_id),0) number
FROM #user u
LEFT JOIN #friend f ON f.user_id = u.id
GROUP BY u.[name], u.id
ORDER BY number

Related

Postgres many to one relationship join multiple tables and select all rows, provided that at least one row matches some criterea

Suppose I have a schema something like
create table if not exists user (
id serial primary key,
name text not null
);
create table if not exists post (
id serial primary key,
user_id integer not null references user (id),
score integer not null
)
I want to run a query that selects a row from the user table by ID, and all the rows that reference it from the post table, provided that at least one row in the post table has a score of greater than some number n (e.g. 50). I'm not exactly sure how to do this though.
You can use window functions. Let me assume that post has a user_id column so the tables can be tied together:
select u.*
from user u join
(select p.*, max(score) over (partition by user_id) as max_score
from post p
) p
on p.user_id = u.id
where p.max_score > 50;
If you just wanted all scores, then aggregation with filtering might be sufficient:
select u.*, array_agg(p.score order by p.score desc)
from user u join
post p
) p
on p.user_id = u.id
group by u.id
having max(p.score) > 50;

SQL Oracle - Multiple count queries on multiple tables

Maybe for some people it might look very simple, but I just cant get it.
My tables are:
CREATE TABLE USERS (user_ID number PRIMARY KEY, username varchar2(32), password varchar2(32));
CREATE TABLE VIDEOS (video_ID number PRIMARY KEY, title varchar(64), description varchar(128));
CREATE TABLE VIEWS (view_ID number PRIMARY KEY, user_ID number, video_ID number);
CREATE TABLE FAVORITES (fav_ID number PRIMARY KEY, user_ID number, video_ID number);
I ve created those separated queries:
SELECT u.username AS "Username", count(*) AS "Views"
FROM Views v, Videos vd, Users u
WHERE v.user_id = u.user_id
AND v.video_id = vd.video_id
GROUP BY u.username
SELECT u.username AS "Username", count(*) AS "Favorites"
FROM Favorites f, Videos vd, Users u
WHERE f.user_id = u.user_id
AND f.video_id = vd.video_id
GROUP BY u.username
And I want a query to show something like that in only one simple query:
Username Views Favorites
-------------------------------
Person1 12 1
Person2 234 21
...
I Googled bunch of similar questions but I couldnt make any of them to work.
So any help is greatly appreciated.
You are progressing on the right track. -> You got two queries and you wish to see them together. You could perform a full outer join to get your results you are looking for as below.
with fave
as (
SELECT u.username AS "Username"
, count(*) AS "Favorites"
FROM Favorites f
JOIN Videos vd
ON f.video_id = vd.video_id
JOIN Users u
ON f.user_id = u.user_id
GROUP BY u.username
)
,views
as (SELECT u.username AS "Username"
, count(*) AS "Views"
FROM Views v
JOIN Videos vd
ON v.video_id = vd.video_id
JOIN Users u
ON v.user_id = u.user_id
GROUP BY u.username
)
select isnull(f.username,v.username) as username
,f.favourites
,v.views
from fave f
full outer join views v
on f.username=v.username
Since you know your data better, you could optimize the query further. Eg: it could be a rule that user who has set a favourite would also have viewed the video. If this is true then you can write a better query to optimize the dataset in a single block, instead of two blocks using full outer join
Aggregate separately in Views:
select user_id, count(*) counter
from Views
group by user_id
and Favorites
select user_id, count(*) counter
from Favorites
group by user_id
and finally LEFT join Users to the above queries:
select u.username,
coalesce(v.counter, 0) Views,
coalesce(f.counter, 0) Favorites
from users u
left join (
select user_id, count(*) counter
from Views
group by user_id
) v on v.user_id = u.user_id
left join (
select user_id, count(*) counter
from Favorites
group by user_id
) f on f.user_id = u.user_id
I used LEFT joins because there may exist users that did not see any video or do not have any favorites. In any of these cases COALESCE() will return 0 instead of null.
The table Videos is not needed.

How to let COUNT show zeros in tables with many to many relationship?

the following query does not show the Groups where no users belong to.
I would like to have the shown with a count of 0 too. How do I do this?
Like this should it be
Group A 8
Group B 0
Group C 2
This is it now
Group A 8
Group C 2
SELECT UsersToGroups.GroupID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
LEFT JOIN users_Users AS users ON
UsersToGroups.UserID =
users.ID
GROUP BY GroupID,
groups.Group
ORDER BY groups.Group ASC
Query will select all groups
SELECT groups.ID,
groups.Group,
FROM users_Groups AS groups
If you add LEFT JOIN AssociationUsersToGroups you should receive groups with number of participants:
SELECT groups.ID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
GROUP BY groups.ID, groups.Group
First of all i don't see why you need to join the user table?
There's no need to assuming that you have a foreign key relationship between "users" and "users-to-group" association
table with ON DELETE CASCADE
This works for me:
-- setting up test-tables and test-data
create table #Groups
(
ID int,
GroupName varchar(100)
)
create table #UsersToGroup
(
GroupID int,
UserID int
)
insert into #Groups
values (1,'Group A'),(2,'Group B'),(3,'Group C')
insert into #UsersToGroup
values (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(3,1),(3,2)
-- the query you want:
select g.ID as GroupID,
g.GroupName,
count(utg.UserID) as countUsersPerGroup
from #Groups g
left join #UsersToGroup utg on g.ID = utg.GroupID
group by g.ID, g.GroupName
order by g.GroupName asc
-- cleanup
drop table #Groups
drop table #UsersToGroup
output:

Select by frequency

I have two tables, like that:
users(id, name)
phones(user_id, number)
I'd like to select all user's names that are in more than three rows in the table phones. How can I do that?
Join the tables and add a having clause that limits the results returned by the count of the user_ids
select name,
count(user_id)
from users u
join phones p
on u.id = p.user_id
group by name
having count(user_id) > 3
SQL Fiddle: http://sqlfiddle.com/#!2/c5516/2
select name from user
join phones on id = user_id
Group By user_id
Having Count(number) > 3

Retrieve rows that matches with all the values listed

Hi I need to get the rows which matches all the groupid listed as an array
SELECT user_id,group_id
FROM group_privilege_details g
WHERE g.group_id in (102,101)
This will return me if any one of the groupid matches. But, I need userid which has all the groupid mention in the list.
Assuming that you cannot have duplicate user_id/group_id combinations:
SELECT user_id,count(group_id)
FROM group_privilege_details g
WHERE g.group_id in (102,101)
GROUP BY user_id
HAVING count(group_id) = 2
Here is a variant of Steven's query for generic arrays:
SELECT user_id
FROM group_privilege_details
WHERE group_id = ANY(my_array)
GROUP BY 1
HAVING count(*) = array_length(my_array, 1)
Works as long as these requirements are met (not mentioned in the question):
(user_id, group_id) is unique in group_privilege_details.
array has only 1 dimension
base array-elements are unique
A generic solution that works regardless of these preconditions:
WITH ids AS (SELECT DISTINCT unnest(my_array) group_id)
SELECT g.user_id
FROM (SELECT user_id, group_id FROM group_privilege_details GROUP BY 1,2) g
JOIN ids USING (group_id)
GROUP BY 1
HAVING count(*) = (SELECT count(*) FROM ids)
unnest() produces one row per base-element. DISTINCT removes possible dupes. The subselect does the same for the table.
Extensive list of options for this kind of queries: How to filter SQL results in a has-many-through relation
Please find my solved query:
select user_id,login_name from user_info where user_id in (
SELECT user_id FROM
group_privilege_details g WHERE g.group_id in
(select group_id from group_privilege_details g,user_info u where u.user_id=g.user_id
and login_name='123')
GROUP BY user_id HAVING count(group_id) = (select count(group_id)
from group_privilege_details g,user_info u where u.user_id=g.user_id
and login_name='123') ) and login_name!='123'