Avoid duplicated records in nullable relationship - sql

Given a player table:
-------------------------
| id | name | email |
-------------------------
and a friendship table:
------------------------------------
| status | playerId | friendId |
------------------------------------
I' am using the following query to retrieve player informations and status of relationship between players:
declare #id int = 1
select * from
(SELECT
p.id,
p.name,
p.email,
p.email_verified,
p.gender,
p.picture,
f1.playerId,
f1.friendId,
f1.status
FROM players p
LEFT OUTER JOIN friendships f1
ON
f1.playerId = p.id or f1.friendId = p.id) as pl
WHERE
pl.id <> #id
The results are the following:
id name email playerId friendId status
2 Nina el#gmail.com 2 1 1
2 Nina el#gmail.com 2 49 1
49 Ciccio testpast#gmail.com 2 49 1
In this case the user 2 is in friendship with user 1 and with another user, I need to display the first record, since is in relationship with the user id parameter, but I need also to retrieve all users, in friendship and not.
How can I do?

use row_number() window function
select * from (SELECT
p.id,
p.name,
p.email,
p.email_verified,
p.gender,
p.picture,
f1.playerId,
f1.friendId,
f1.status,
row_number() over(partition by p.id,
p.name,
p.email,f1.friendId order by id) rn
FROM players p
LEFT OUTER JOIN friendships f1
ON
f1.playerId = p.id or f1.friendId = p.id
) t where t.rn=1

Related

Efficiently getting multiple counts of foreign key rows in PostgreSQL

I have a database that consists of users who can perform various actions, which I keep track of in multiple tables. I'm creating a point system, so I need to count how many of each type of action the user did. For example, if I had:
users posts comments shares
id | username id | user_id id | user_id id | user_id
------------- -------------- -------------- --------------
1 | abc 1 | 1 1 | 1 1 | 2
2 | xyz 2 | 1 2 | 2 2 | 2
I would want to return:
user_details
id | username | post_count | comment_count | share_count
---------------------------------------------------------
1 | abc | 2 | 1 | 0
2 | xyz | 0 | 1 | 2
This is slightly different from this question about foreign key counts since I want to return the individual counts per table.
What I've tried so far (example code):
SELECT
users.id,
users.username,
COUNT( DISTINCT posts.id ) as post_count,
COUNT( DISTINCT comments.id ) as comment_count,
COUNT( DISTINCT shares.id ) as share_count
FROM users
LEFT JOIN posts ON posts.user_id = users.id
LEFT JOIN comments ON comments.user_id = users.id
LEFT JOIN shares ON shares.user_id = users.id
GROUP BY users.id
While this works, I had to use DISTINCT in all of my counts because the LEFT JOINS were causing high numbers of duplicate rows. I feel like there must be a better way to do this since (please correct me if I'm wrong) on each LEFT JOIN, the DISTINCT is having to filter out an exponentially growing number of duplicated rows.
Thank you so much for any help you could give me with this!
You can join derived tables that already do the aggregation.
SELECT u.id,
u.username,
coalesce(pc.c, 0) AS post_count,
coalesce(cc.c, 0) AS comment_count,
coalesce(sc.c, 0) AS share_count
FROM users AS u
LEFT JOIN (SELECT p.user_id,
count(*) AS cc
FROM posts AS p
GROUP BY p.user_id) AS pc
ON pc.user_id = u.id
LEFT JOIN (SELECT c.user_id,
count(*) AS
FROM comments AS c
GROUP BY c.user_id) AS cc
ON cc.user_id = u.id
LEFT JOIN (SELECT s.user_id,
count(*) AS c
FROM shares AS s
GROUP BY s.user_id) AS sc
ON sc.user_id = u.id;

Join one table with two other ones by id

I am trying to join one table with two others that are unrelated to each other but are linked to the first one by an id
I have the following tables
create table groups(
id int,
name text
);
create table members(
id int,
groupid int,
name text
);
create table invites(
id int,
groupid int,
status int \\ 2 for accepted, 1 if it's pending
);
Then I inserted the following data
insert into groups (id, name) values(1,'group');
insert into members(id, groupid, name) values(1,1,'admin'),(1,1,'other');
insert into invites(id, groupid, status) values(1,1,2),(2,1,1),(3,1,1);
Obs:
The admin does not has an invite
The group has an approved invitation with status 2 (because the member 'other' joined)
The group has two pending invites with status 1
I am trying to do a query that gets the following result
groupid | name | inviteId
1 | admin | null
1 | other | null
1 | null | 2
1 | null | 3
I have tried the following querys with no luck
select g.id, m.name, i.id from groups g
left join members m ON m.groupid = g.id
left join invites i ON i.groupid = g.id and i.status = 1;
select g.id, m.name, i.id from groups g
join (select groupid, name from members) m ON m.groupid = g.id
join (select groupid, id from invites where status = 1) i ON i.groupid = g.id;
Any ideas of what I am doing wrong?
Because members and invites are not related, you need to use two separate queries and use UNION (automatically removes duplicates) or UNION ALL (keeps duplicates) to get the output you desire:
select g.id as groupid, m.name, null as inviteid from groups g
join members m ON m.groupid = g.id
union all
select g.id, null, i.id from groups g
join invites i ON (i.groupid = g.id and i.status = 1);
Output:
groupid | name | inviteid
---------+-------+----------
1 | admin |
1 | other |
1 | | 3
1 | | 2
(4 rows)
Without a UNION, your query implies that the tables have some sort of relationship, so the columns are joined side-by-side. Since you want to preserve the null values, implying that the tables are not related, you need to concatenate/join them vertically with UNION
Disclosure: I work for EnterpriseDB (EDB)

How to find record ids with missing property ids in another table

The PostgreSQL database has two tables: user_properties and properties. The properties table contains a list of all possible properties with ids (a dictionary). The user_properties table contains what properties a user has, referencing property id from the properties table.
The properties table:
----------------------
prop_id | prop_name
----------------------
1 | Email
----------------------
2 | Phone number
----------------------
3 | Something else 1
----------------------
4 | Something else 2
----------------------
The user_properties table:
--------------------------------
user_id | prop_id | prop_value
--------------------------------
100 | 1 | asd#zxc.com
--------------------------------
100 | 2 | 1234567
--------------------------------
100 | 2 | 2345678
--------------------------------
101 | 3 | *******
--------------------------------
101 | 3 | +++++++
--------------------------------
I need to know which properties are missing for every user_id.
The expected result should look like:
-----------------------
user_id | missing_prop_id
-----------------------
100 | 3
-----------------------
100 | 4
-----------------------
101 | 1
-----------------------
101 | 2
-----------------------
101 | 4
-----------------------
You can use except as :
with properties(prop_id,prop_name) as
(
values(1, 'Email'),(2, 'Phone number'),
(3, 'Something else 1'),(4, 'Something else 2')
), user_properties( user_id, prop_id, prop_value) as
(
values(100,1,'asd#zxc.com'),(100,2,'1234567'),(100,2,'2345678'),
(101,3,'*******'),(101,3,'+++++++')
), t2 as
(
select u.user_id, p.prop_id as missing_prop_id
from user_properties u
cross join properties p
group by u.user_id, p.prop_id
except
select u.user_id,
p.prop_id
from user_properties u
right join properties p
on u.prop_id = p.prop_id
group by u.user_id, p.prop_id
)
select * from t2 order by user_id, missing_prop_id;
user_id missing_prop_id
100 3
100 4
101 1
101 2
101 4
Demo
You can simply solve this by join...
SELECT DISTINCT t3.user_id, t3.prop_id FROM
(SELECT DISTINCT user_id, t2.prop_id FROM user_properties t1, properties t2) t3
LEFT JOIN user_properties t4 ON t3.user_id = t4.user_id and t3.prop_id = t4.prop_id WHERE t4.prop_id is null
http://sqlfiddle.com/#!17/0f4e3/2/0
You can use a cross join to generate all the rows and left join to filter out the ones that don't exist:
select u.user_id, p.prop_id
from (select distinct user_id from user_properties
) u cross join
properties p left join
user_properties up
on up.user_id = u.user_id and
up.prop_id = p.prop_id
where up.user_id is null;
Presumably, you have a users table so the subquery for u is not needed:
select u.user_id, p.prop_id
from users u cross join
properties p left join
user_properties up
on up.user_id = u.user_id and
up.prop_id = p.prop_id
where up.user_id is null;
Thanks everyone for the help. I've come up with the following query myself:
select mp.user_id, mp.prop_id missing_prop_id from
(select distinct up.user_id, p.prop_id from user_properties up cross join properties p) mp
except
select distinct user_id, prop_id from user_properties
http://sqlfiddle.com/#!17/0f4e3/3/0

sql select where column is a count

I have a table with users, and I have another table with activity, the user who had the activity is logged in a column. how could I make a query so that I can select each user with the count of activities they have.
I really can't think of how to do it nor search for something like this on the web.
so for example
User table
id | name
1 | john
2 | karen
Activity table
id | user_id
1 | 1
2 | 1
3 | 2
Results
name | Count
john | 2
karen| 1
Make use of LEFT JOIN and COUNT aggregate
SELECT name, COUNT(a.user_id) count
FROM [User] u LEFT JOIN Activity a
ON u.id = a.user_id
GROUP BY u.id, u.name
Output:
| name | count |
|-------|-------|
| john | 2 |
| karen | 1 |
Here is a SQLFiddle demo
Recommended reading:
A Visual Explanation of SQL Joins
select name, count(a.Id) as ActivityCount
from [user] u
inner join activity a on u.Us = a.UserId
group by name
very simple to do. You can combine the two tables by using a join. To have the count (ie the total count) added, there is a function you can use which is conveniently called "Count". So all together, it would look something like this-
select u.id, u.name, count(*) as ct
from tblUser u
left join tblActivity a on u.id = a.id
group by u.id, u.name
order by ct desc
select
u.id as user_id, -- name is not necessary unique
max(u.name) as name,
count(a.Id) as [count]
from
[User] u
left join Activity a -- left join becuase some users can have no activities
on u.Id = a.user_id
group by u.id

How to SQL in many-to-many relationship

I want to display how many hobbies does john have. Could you tell me how to write SQL statement?
PERSON table
ID | NAME
1 | John
HOBBY table
ID | NAME
1 | music
2 | sport
PERSON_HOBBY_COMBINATION table
ID | PERSON_ID | HOBBY_ID
1 | 1 | 1
expected result
HOBBY_NAME | HOBBY_EXIST
music | YES
sport | NO
This might work for you:
SELECT h.name,
CASE WHEN ph.id IS NULL THEN 'No' ELSE 'Yes' END AS hobby_exist
FROM hobby h
CROSS JOIN person p
LEFT JOIN person_hobby_conbination ph ON (ph.p_id = p.id AND ph.h_id = h.id)
WHERE (p.name = 'John')
Select NAME, count(*) AS NoHobbies from Person p
inner join PERSON_HOBBY_COMBINATION phc
on p.ID = phc.PERSON_ID
group by p.ID, p.NAME
Note that you should group on both ID and NAME of the person.
You need to group on NAME because you have it in the output, but if you have duplicate names grouping on NAME will sum several persons hobbies together, that is why you need to group on ID too.
Edit
When inspection your expected result you don't want how many hobbies John has, but which hobbies. Then you need to write
Select p.NAME as PersonName, h.Name as HobbyName, case when phc.ID is null then 'No' else 'Yes' end as HasHobby from Person p
inner join Hobby h
on 1 = 1
left outer join dbo.PersonHobbyCombination phc
on p.ID = phc.PersonID and h.ID = phc.HobbyID
If you already have John's ID and just want a raw count, then this should work.
select count(*) from person_hobby_combination where person_id=?