mysql where OR having - mysql

I have a table of users, some of which have articles associated with them, and some of which have type = writer. I'd like to display all users who have articles OR who have type = writer. So, all writers should be displayed, and other user types are only displayed if they have articles.
This is my query so far, which leaves out writers with no articles.
SELECT u.name, u.type, COUNT(a.id) count
FROM users u
LEFT JOIN articles a on u.id = a.writer_id
GROUP BY u.name
HAVING count > 0
Adding the following WHERE clause obviously excludes other user types that have articles.
WHERE u.type = 'writer'
Do I need to do a UNION of these two result sets?

I think you are looking for something like this
SELECT
u.name,
u.type,
COUNT(a.id) count
FROM users u
LEFT JOIN articles a ON u.id = a.writer_id
WHERE
u.type='writer' --all users that are writers
OR
a.writer_id IS NOT NULL --all users that have at least one article
GROUP BY
u.name
--removed the having clause as it seems it may be possible that a writer has no articles.

Just change the WHERE clause to allow any user that has a matching article record:
SELECT u.name, u.type, COUNT(a.id) count
FROM users u
LEFT JOIN articles a on u.id = a.writer_id
WHERE u.type = 'writer' OR a.writer_id IS NOT NULL
GROUP BY u.name
HAVING count > 0

Related

Join results from two tables when data can appear in multiple columns in one table

I have one table containing a list of users, all I need from this are users.id and users.username
I have a second table that links the users as "friends", all I need from this is friends.one and friends.two
I want to output a result that shows all friends of a user with a certain user id (this will be a variable, but for the sake of the example we'll use user id '1'). User id '1' can appear in either friends.one or friends.two.
I've tried a few different ideas, but I'm not sure I'm any closer. The code below is obviously awful but I think it describes the idea well(ish). Though I'm probably overly complicating something which there is an easier method for,
SELECT users.username, users.id
FROM users
INNER JOIN friends
ON users.id = friends.friendone
WHERE friends.friendtwo='1'
UNION
SELECT users.username, users.id
FROM users
INNER JOIN friends
ON users.id = friends.friendtwo
WHERE friends.friendone='1'
ORDER BY users.username ASC;
With conditional join:
SELECT u.username, u.id
FROM friends f INNER JOIN users u
ON u.id = CASE '1'
WHEN f.friendone THEN f.friendtwo
WHEN f.friendtwo THEN f.friendone
END

How to count rows on joined table without WHERE [duplicate]

This question already has answers here:
Left Outer Join doesn't return all rows from my left table?
(3 answers)
Closed 3 years ago.
I have two related tables users and networks — social-media accounts of corresponding user. Accounts (networks) could be like these: facebook, linkedin, youtube, vimeo, etc...
Every user could have zero or more social-media accounts (in networks table).
In my case I need to count only video accounts (youtube and/or vimeo) of each user.
I'm trying this two queries:
SELECT users.email, COUNT(networks.network) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
WHERE networks.network='youtube' OR networks.network='video'
GROUP BY users.id
and
SELECT users.email,
CASE networks.network WHEN 'youtube' THEN 1 ELSE 0 END as video_networks_counter,
CASE networks.network WHEN 'vimeo' THEN 1 ELSE 0 END as video_networks_counter
FROM users
LEFT JOIN networks ON networks.user_id = users.id
GROUP BY users.id
Which don't work as desired.
Problem with first query is that user could not have any networks but he also have to be in a list with 0 counter. So WHERE condition seems to be useless in my case.
Second query with CASE also doesn't work.
Move the conditions to the on clause:
SELECT u.email, COUNT(n.network) as video_networks_counter
FROM users u LEFT JOIN
networks n
ON n.user_id = u.id AND
n.network IN ('youtube', 'video')
GROUP BY u.email;
Notes:
Your WHERE clause turns the outer join into an inner join, because NULL values do not match.
IN is simpler than OR clauses for this type of comparison.
The GROUP BY key should match the unaggregated columns in the SELECT.
Table aliases make the query easier to write and to read.
You may intend 'vimeo' rather than 'video'.
And, if you want to count them separately:
SELECT u.email, COUNT(n.network) as video_networks_counter,
SUM( n.network = 'youtube') as num_youtube,
SUM( n.network = 'vimeo') as num_vimeo
FROM users u LEFT JOIN
networks n
ON n.user_id = u.id AND
n.network IN ('youtube', 'vimeo')
GROUP BY u.email;
In your 1st query set the conditions in the ON clause and remove WHERE:
SELECT users.email, COUNT(networks.network) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
AND (networks.network='youtube' OR networks.network='video')
GROUP BY users.id
Your WHERE clause removes all the users that do not have an account making the LEFT JOIN useless.
You could just sum up how often your condition is true
SELECT u.email,
SUM(n.network IN ('youtube', 'video')) as video_networks_counter,
FROM users u
LEFT JOIN networks n ON n.user_id = u.id
GROUP BY u.email
You can try this option with CASE and SUM-
SELECT users.email,
SUM(
CASE
WHEN networks.network='youtube' OR networks.network='video' THEN 1
ELSE 0
END
) as video_networks_counter,
FROM users
LEFT JOIN networks ON networks.user_id = users.id
GROUP BY users.email

How to count a number from different table when there's no common field?

There are a user table and a user_follow table that describes which user.id is following/followed. I'd like to count the occurrences of that user is following and being followed.
The problem is that user_follow table doesn't have user_id as a foreign key, so I'm not able to join enter image description here the two tables by a common field. I've tried to use LEFT OUTER JOIN on user.id=user_follow.following_user_id and GROUP BY user.id, but it only counts the times of following(followed times is exactly the same as the following, which is not right).
The way to solve this is to join on USER_FOLLOW twice, once for Followed By and once for Following.
You haven't posted the structure of USER_FOLLOW, so this is a guess and you'll need to correct it to fit your schema.
select u.id, u.first_name, u.last_name
, count(f.following_user_id) as following_count
, count(fb.user_id) as followed_by_count
from user u
left_outer join user_follow f on where f.user_id = u.id
left_outer join user_follow fb on where fb.following_user_id = u.id
group by u.id, u.first_name, u.last_name

Get users with followers, and without followers

I have a really simple table - follow - in which I store followers.
user | following
-----------------
1 | 2
The above means user 1 is following user 2.
I want to display all users on the home page and order them buy who has the most followers, and then return the rest of the users who have no followers. The below query is working as far as displaying the users, but I can't figure out how to retrieve the users who do not have any followers. I've tried RIGHT JOIN users u ON f.following=u.id but that gives me weird results.
This query returns user 2 who has a follower, but doesn't return users 1 and 3, who do not have followers.
Edit: this query is also checking to see if the user is following back, which is why I'm joining using the ID of 1 as a test.
SELECT
u.id
,u.username
,u.avatar
,COUNT(1) AS followers
,ul.*
,fo.*
FROM follow f
LEFT JOIN users u ON f.following=u.id
LEFT JOIN follow fo ON fo.following=u.id AND fo.user=1
LEFT JOIN users_likes ul ON ul.likes=u.id AND ul.user=1
GROUP BY f.following
ORDER BY COUNT(1) DESC
SQL Fiddle: http://sqlfiddle.com/#!2/98f65/1
The problem with your query in the question is that you are left-joining to the follow table. That means that all rows in the follow table are included regardless of their connection to another table. What you want is to show all users, so that is the table that should be on the outer end of the join.
I also think you're trying to do too many things at once here, which is why you're having trouble figuring it out. You want to know who has followers and who doesn't, who's following back, order them, consider the users_likes and so on. I recommend taking a step back and breaking them down into individual queries, and then building those into one result set as needed.
To get the users and number of followers, you can outer join the users table with the follow table like this:
SELECT u.id, u.username, u.avatar, (IFNULL(COUNT(f.following), 0)) AS numFollowers
FROM users u
LEFT JOIN follow f ON f.following = u.id
GROUP BY u.id
ORDER BY numfollowers DESC;
IFNULL is used to check the cases when there are no followers, and no link is made in the outer join so a null value appears.
If you want to work in the users_likes table, you should add it in as another left join. The problem this causes, is that it will return null values for all columns if there are no likes. (Example, if I left join the users_likes table here, I will see null for users 1 and 3 because nobody 'likes' them.) To make the result set a little more understandable, I recommend you don't collect all rows of the users_likes table. Perhaps this query would make more sense:
SELECT u.id, u.username, u.avatar, (IFNULL(COUNT(f.following), 0)) AS numFollowers, ul.user AS likedByUser, ul.created_at
FROM users u
LEFT JOIN follow f ON f.following = u.id
LEFT JOIN users_likes ul ON ul.likes = u.id
GROUP BY u.id
ORDER BY numfollowers DESC;
As far as whether or not a user is following back, I think this would change a bit, as the above only shows the number of followers, and doesn't produce a row for each follower.
Let me know if you have any more questions, here is an SQL Fiddle for the above. I will leave it up to you for handling the null values that occur right now.
You can use an outer join (left or right) from Users to your current query in any number of ways. An easy example that should get you started. This isn't a clean-up up solution, just a dmeo of a way that will work.
SELECT a.*
,b.*
FROM users a
LEFT JOIN (
SELECT
u.id
,u.username
,u.avatar
,COUNT(1) AS followers
FROM follow f
LEFT JOIN users u ON f.following=u.id
LEFT JOIN follow fo ON fo.following=u.id AND fo.user=1
LEFT JOIN users_likes ul ON ul.likes=u.id AND ul.user=1
GROUP BY f.following
) b
ON a.id = b.id
ORDER BY followers DESC
You can do this:
SELECT * FROM (
SELECT u.id, u.username, u.avatar, COUNT(f.user) as followers
FROM users AS u
LEFT JOIN follow AS f ON u.id = f.following
GROUP BY u.id
) AS subselect ORDER BY subselect.followers DESC

mysql joining for relational lookup

I've never been all that great with much more then regular select queries. I have a new project that has users, roles and assigned_roles (lookup table for users with roles).
I want to group_concat the roles.name so that my result shows me what roles each user has assigned.
I've tried several things:
select users.id, users.displayname,users.email, rolenames from `users`
left join `assigned_roles` on `assigned_roles`.`user_id` = `users`.`id`
left join (SELECT `id`, group_concat(`roles`.`name`) as `rolenames` FROM `roles`) as uroles ON `assigned_roles`.`role_id` = `uroles`.`id`
This gives me the grouped role names but shows me duplicate entries if a user has two roles, so the second row in the result shows the same user but no role names.
select users.id, users.displayname,users.email, rolenames from `users`
join `assigned_roles` on `assigned_roles`.`user_id` = `users`.`id`
join (SELECT `id`, group_concat(`roles`.`name`) as `rolenames` FROM `roles`) as uroles ON `assigned_roles`.`role_id` = `uroles`.`id`
Just regular joins shows me what I want but wont lists users who do not have any assigned.roles, so its not complete.
I'll keep plugging away but I thought stack could help, hopefully I'll learn a bit more about joins today.
Thank you.
For GROUP CONCAT to work in this scenario, you'll need a GROUP BY to get the group info per user, something like;
SELECT u.id, u.displayname, u.email, GROUP_CONCAT(r.name) rolenames
FROM users u
LEFT JOIN assigned_roles ar ON ar.user_id = u.id
LEFT JOIN roles r ON r.id = ar.role_id
GROUP BY u.id, u.displayname, u.email