Count with subquery, group_by and Left join - sql

I am struggeling for along time with this particular query. I am making a 'leaderboard' of users whom have added or received the most places to their guides.
My data model is as follows:
Users
uuid id
string first_name
bool blogger
Guests
uuid id
Tips (join model)
uuid id
uuid guide_id
uuid place_id
integer status
uuid owner_id
string owner_type
Guides
uuid id
uuid user_id
string name
Place
uuid id
string name
A user can add places (trough tips) to his/her guide. This same user can also receive places (tips) from other users to his/her guide. These tips can be accepted tips.status = 1.
What I want is the following:
first_name and count of all places added or accepted tips over all their guides, but not tips they have given to other users grouped by users.blogger = 1.
example:
Guest = true
you 40
user1 30
user2 25
Guest = false
user3 20
user4 15
user5 5
this is what I have so far:
SELECT tips.owner_id, tips.owner_type, count(tips.owner_id) AS places_count
FROM "tips"
LEFT JOIN users on (owner_type ='User' AND users.id = owner_id)
GROUP BY "tips"."owner_id", "tips"."owner_type"
ORDER BY places_count DESC
LIMIT 16
This query does return counts, but does not take received tips in regard and it also counts given tips to other users. I have a hunch that I need to use subqueries, firstly select all the guide id's from a given user and secondly 'simply' select a count of all tips where guide_id = selected_guide_ids AND tips.status = 1. Lastly group the results by users.blogger = 1
But how do I write this?
Edit 1:
I have updated my original question with an additional Guest table (this is why I use owner_type and owner_id instead of table_id. And I've updated the user table with blogger (bool) on which I want to group the results.
Sample data:
Users
id first_name blogger
user1 Daniel true
user2 Quassnoi false
user3 vkp true
Guests
id
guest_1
guest_2
Guides
id user_id name
guide_1 user_1 Bugers
guide_2 user_1 Cool places
guide_3 user_2 Amsterdam
Tips
id guide_id place_id status owner_id owner_type
tip1 guide_1 place_1 1 user_1 User # user_1 added place_1 to his own guide guide_1 (accepted)
tip2 guide_1 place_2 1 guest_1 Guest # guest_1 suggested place_2 to user_1's guide guide_1 (accepted)
tip2 guide_1 place_2 0 guest_1 Guest # guest_1 suggested place_2 to user_1's guide guide_1 (rejected)
tip_3 guide_2 place_1 1 user_2 User # user_2 added place_1 to his own guide guide_3 (accepted)
tip_4 guide_2 place_2 1 user_2 User # user_2 added place_2 to his own guide guide_3 (accepted)
tip_5 guide_2 place_3 1 user_1 User # user_1 added place_3 to user_2's guide guide_2 (accepted)
Places
id name
place1 burgerbar
place2 burgermeester
place_3 bbq shack
What my desired outcome is:
Note that tips given to other users don't count for the tip giver.
first_name tips_count blogger
Quassnoi 3 false (2 added by himself, 1 received from user_1)
Daniel 2 true (1 added by himself, 1 received from guest1. Note that the rejected tip does not count)
vkp 0 false
Edit 2
I've altered Quassnoi's answer a little bit to this:
SELECT *
FROM users u
LEFT JOIN
(
SELECT g.user_id, COUNT(*) tips_count
FROM guides g
JOIN tips t
ON t.guide_id = g.id
AND (t.owner_id = g.user_id AND t.status = 1)
GROUP BY g.user_id
) g
ON g.user_id = u.id
ORDER BY tips_count DESC
This however returns all the records where tips_count is NULL first. I want those to be 0 instead of NULL. How can I cast NULL tips_count to 0?
Edit 3:
I've updated the query so that it only counts the tips where the guide_id is equal to the guide ids from the given user.
SELECT *
FROM users u
LEFT JOIN
(
SELECT g.user_id, COUNT(*) tips_count
FROM guides g
JOIN tips t
ON t.guide_id = g.id
AND (t.guide_id = g.id AND t.status = 1)
GROUP BY g.user_id
) g
ON g.user_id = u.id
ORDER BY tips_count DESC

It seems that it doesn't really matter who added/suggested a place. Once it is accepted (status 1) it belongs to the guide and thus to the guide's user. Hence:
select u.first_name, u.blogger, count(t.id)
from users u
left join guides g on g.user_id = u.id
left join tips t on t.guide_id = g.id and t.status = 1
group by u.id
order by count(t.id) desc;

The way your schema is set up now, it's impossible to tell which guide belongs to which user.
Assuming there's a guide.owner you forgot to mention (or forgot to add), it would be:
SELECT *
FROM user u
LEFT JOIN
(
SELECT g.owner, COUNT(*) cnt
guide g
JOIN tips t
ON t.guide_id = g.id
AND (t.owner_id = g.owner OR t.status = 1)
GROUP BY
g.owner
) g
ON g.owner = u.id

Related

SQL : one to many query on the many data

I'm having a really hard time figuring this out, hopefully someone can shed some light
Consider the following data
Users:
id username password
--------------------------
1 Bob 123
2 Alice abc
...
Services:
id user_id name status
-----------------------------
1 1 product1 canceled
2 1 product2 canceled
3 1 product3 live
4 2 product1 canceled
I need to do a query and find all users who only have canceled services. So the response should be "alice" only as bob has at least 1 live service.
If I try filter where status!='live' then (obviously) I still get Bob & Alice, how can I do a query where all the status is canceled is this possible?
Using exists logic we can try:
SELECT u.username
FROM Users u
WHERE NOT EXISTS (SELECT 1 FROM Services s
WHERE s.user_id = u.id AND s.status <> 'canceled');
This query says, in plain English, to find any users for which we cannot find in the Services table a record for that user whose status is not cancelled.
select distinct u.username from(
select * from service where status = 'canceled') canceled
inner join users u on u.id = canceled.user_id
where canceled.user_id not in(select user_id from service where status = 'live')
It selects all users which is not in the subset with the live status
Here is another way of approaching the problem
select distinct u.user_name from (
select user_id, sum(case when status = 'live' then 1 else 0 end) as live_cnt from services
group by user_id
having live_cnt = 0) s join users u on s.user_id =u.id
;

select a column from another table mulipletimes problem

User
USER_ID USERNAME
1 -
2 Chris
3 Dave
4 Vlad
Issue
Creator RESOLVER VERIFIER
2 3 4
2 3 1
3 1 1
expected output:
Creator RESOLVER VERIFIER
Chris Dave Vlad
Chris Dave -
Dave - -
current code I have:
SELECT creatorid.username, resolverid.username, verifierid.username
FROM issue
JOIN user creatorid ON issue.creator = creatorid.user_id
JOIN user resolverid ON issue.resolver = resolverid.user_id
JOIN user verifierid ON issue.verifier = verifierid.user_id
do i have to join the table 3 times to get the corresponding username of the user_id in issue table or is there is a simpler way of doing this? Asking as this is a simplified version of the tables, the User and Issue table contains a lot of other columns. Thanks
Because of the join, you will see each issue three times which is not what you want. You could use three scalar subqueries to get around that:
select i.id,
(select username from users u1 where u1.user_id = i.creator) as creator,
(select username from users u2 where u2.user_id = i.resolver) as resolver,
(select username from users u3 where u3.user_id = i.verifier) as verifier
from issue i;
This isn't going to be fast though.
Another option is to aggregate all user_id / username pairs into a JSON object, then use that in a sub-query:
select i.id, -- other columns from the issue table
u.names ->> i.creator::text as creator,
u.names ->> i.resolver::text as resolver,
u.names ->> i.verifier::text as verifier
from issue i
join lateral (
select jsonb_object_agg(user_id, username) as names
from users u
where u.user_id in (i.creator, i.resolver, i.verifier)
) u on true;
The traditional way to do this is:
select i.*, uc.username, ur.username, uv.username
from issue i left join
users uc
on uc.user_id = i.creator left join
users ur
on ur.user_id = i.resolver left join
users uv
on uv.user_id = i.verifier;

sql: many to many relationship join

I'm very new to SQL so if there are multiple possibilities I'd like to see them all (and hear which possibilities are better than others). I'm using sqlite3.
I have the following 3 tables: user, channel, subscriptions
user:
user_id name
1 Johnny
2 Stacy
3 Allana
channel:
channel_id channel_name
1 ESPN
2 Disney
subscriptions:
user_id channel_id
1 1
2 2
3 1
3 2
What SQL command do I need to perform to get the following table? I basically want to see who is subscribed to which channels by names (so exactly what's laid out in the subscriptions table but mapping numbers to names based on the other tables).
user_id channel_id
Johnny ESPN
Stacy Disney
Allana ESPN
Allana Disney
I've tried the following but I'm getting nothing in the return statement:
select user.name, channel.channel_name from user, channel, subs where user.user_id == subs.user_id and channel.channel_id == subs.channel_id
Try this out and let me know in case you face any difficulty.
select a.name,c.channel_name
from
user a
left join
subscriptions b
on a.user_id = b.user_id
left join
channel c
on b.channel_id = c.channel_id;
or (in the format u asked in comments)
select u.name,c.channel_name
from
user u
left join
subscriptions s
on u.user_id = s.user_id
left join
channel c
on s.channel_id = c.channel_id;
Haven't tested it but try this:
select
u.name
,c.channel
from
user_id u
inner join subscriptions s
on u.user_id=s.user_id
inner join channel c
on s.channel_id=c.channel_id

SQL query (Join without duplicates)

I have tables users and topics. Every user can have from 0 to several topics (one-to-many relationship).
How I can get only those users which have at least one topic?
I need all columns from users (without columns from topics) and without duplicates in table users. In last column I need number of topics.
UPDATED:
Should be like this:
SELECT user.*, count(topic.id)
FROM ad
LEFT JOIN topic ON user.id = topic.ad
GROUP BY user.id
HAVING count(topic.id) > 0;
but it takes 0 result. But it should not be 0.
Firstly you need to have your two tables, because you have left limited information about your table structure I will use an example to explain how this works, you should then be able to easily apply this to your own tables.
Firstly you need to have two tables (which you do)
Table "user"
id | name
1 | Joe Bloggs
2 | Eddy Ready
Table "topic"
topicid | userid | topic
1 | 1 | Breakfast
2 | 1 | Lunch
3 | 1 | Dinner
Now asking for a count against each user is done using the follwing;
SELECT user.name, count(topic.topicid)
FROM user
INNER JOIN topic ON user.id = topic.userid
GROUP BY user.name
If you use a left join, this will include records from the "user" table which does not have any rows in the "topic" table, however if you use an INNER JOIN this will ONLY include users who have a matching value in both tables.
I.e. because the user id "2" (which we use to join) is not listed in the topic table you will not get any results for this user.
Hope that helps!
use inner join and distinct
select distinct user_table.id
from user_table
inner join topics_table on topic_table.user_id = user_table.id
select u.id
, u.name
, count(b.topicName)
from user u
left join topic t on t.userid = u.id
group by u.id, u.name
You can select topic number per user and then join it with user data. Something like this:
with t as
(
select userid, count(*) as n
from topic
group by userid
)
SELECT user.*, t.n
FROM user
JOIN t ON user.id = t.userid

joining tables while keeping the Null values

I have two tables:
Users: ID, first_name, last_name
Networks: user_id, friend_id, status
I want to select all values from the users table but I want to display the status of specific user (say with id=2) while keeping the other ones as NULL. For instance:
If I have users:
? first_name last_name
------------------------
1 John Smith
2 Tom Summers
3 Amy Wilson
And in networks:
user_id friend_id status
------------------------------
2 1 friends
I want to do search for John Smith for all other users so I want to get:
id first_name last_name status
------------------------------------
2 Tom Summers friends
3 Amy Wilson NULL
I tried doing LEFT JOIN and then WHERE statement but it didn't work because it excluded the rows that have relations with other users but not this user.
I can do this using UNION statement but I was wondering if it's at all possible to do it without UNION.
You need to put your condition into the ON clause of the LEFT JOIN.
Select
u.first_name,
u.last_name,
n.status
From users u
Left Join networks n On ( ( n.user_id = 1 And n.friend_id = u.id )
Or ( n.friend_id = 1 And n.user_id = u.id )
Where u.id <> 1
This should return you all users (except for John Smith) and status friend if John Smith is either friend of this user, or this user is friend of John Smith.
You probably don't need a WHERE clause, and instead of that, put the condition into the "ON" clause that follows your "LEFT JOIN". That should fix your issues. Also, make sure that the main table is on the left side of the left join, otherwise, you should use a right join.
In addition to the (correct) replies above that such conditions should go in the ON clause, if you really want to put them in the WHERE clause for some reason, just add a condition that the value can be null.
WHERE (networks.friendid = 2 OR networks.friendid IS NULL)
From what you've described, it should be a case of joining a subset of networks to users.
select id, first_name, last_name, status
from users u
left join networks n on u.id = n.user_id
and n.friend_id = 1
where id <> 1;
The left join will keep rows from users that do not have a matching row in networks and adding the and n.friend_id = 1 limits when the 'friends' status is returned. Lastly, you may choose to exclude the row from users that you are running the query for.