Select only some entries based on what's in another table - mysql

I'm trying to grab all of the offers provided to a user that are not already queued up in their projects. So, I'm grabbing offers from the OfferSuggestionHeader table based on my user's ID but also try to make sure it doesn't grab anything that the user has already added to their projects (stored in the Projects table)
I've got the following query:
SELECT DISTINCT ofh.OfferID, ofh.OfferTitle, ofh.OfferVendor, ofh.Savings,ofh.SavingsPercent
FROM OfferSuggestionHeader ofh
LEFT JOIN OfferSuggestionDetail osd
ON ofh.OfferID = osd.OfferID
LEFT JOIN Facilities f
ON osd.FacilityID = f.id
LEFT JOIN UserFacility uf
ON f.id = uf.fid
LEFT JOIN Users u
ON uf.uid = u.uid
LEFT JOIN Projects p
ON p.uid = u.uid
WHERE p.uid = '1'
AND ofh.OfferID <> ANY (SELECT offer_id FROM Projects WHERE uid = '1')
It pulls up all of the offers. If I take away ANY then I get an error saying that the subquery returns too many results.
There are 6 offers. Three are queued up by user 1. I shouldn't see offers 1, 4, or 5.
Thanks for any pointers and help.

As per my comment explanation, why not try this? I am not sure why you need a subquery when you already have PROJECT table JOINED in your main query..
WHERE p.uid = '1'
AND ofh.OfferID NOT IN (1, 4, 5);

Related

SQL query to find users with apps with no releases

In my database, I have users, apps, and releases. A user can have 0..n apps through a permissions table and an app can have 0..n releases.
I'm trying to get a list of users who have at least 1 app, but none of that user's apps have any releases.
The schema is roughly
users permissions apps releases
----- ----------- ---- --------
id user_id id id
email app_id app_id
I think I've got something working with this, but it appears inefficient to me because I mention the permissions table twice and I'm using nested exists clauses. Is there a more efficient way to write this query?
select u.email from users u
join permissions p on p.user_id = u.id
where not exists (
select a.id from apps a
join permissions p on p.app_id = a.id
where p.user_id = u.id and exists (
select r.id from releases r
where r.app_id = a.id
)
);
You just need to use a LEFT JOIN on releases, and then look for the case where the number of released apps (r.app_id is non-NULL) is 0. If all you want is a list of users, I don't think you need to JOIN the apps table at all, as JOINing on permissions will ensure that only users that have permission for 1 or more apps are included.
SELECT u.email
FROM users u
JOIN permissions p ON p.user_id = u.id
LEFT JOIN releases r ON r.app_id = p.app_id
GROUP BY u.email
HAVING COUNT(r.app_id) = 0
The first Join seems to be correct between users and permissions table. You just need to check whether the app_id from joined result-set exists in releases table or not. You can try this query -
select u.email from users u
join permissions p on p.user_id = u.id
where not exists ( Select 1 from releases r where r.App_id = p.app_id)
I will do something like this, hope this helps:
SELECT
u.id, u.email
FROM
users AS u
INNER JOIN
permissions AS p ON p.user_id = u.id
LEFT JOIN
releases AS r ON r.app_id = p.app_id
GROUP BY
u.id, u.email
HAVING
SUM(CASE WHEN r.id IS NOT NULL THEN 1 ELSE 0 END) = 0
Another thing you could try is a combination of left and inner joins like this:
Select
email
From users u
Inner Join (
Select
p.user_id
, p.app_id
From permissions p
Left Join releases r
on p.app_id = r.app_id
Where r.app_id is null) a
on u.user_id = a.user_id
Group by email
It's hard to tell which is faster between this and the previous posted solution without knowing the size of the different tables (and hence how many rows SQL will be trying to join).
One thing that is clear - without the 'Group by email' line at the end, you might see users' email repeated multiple times in your list. Generally, literature on SQL states that using a "group by" statement at the end of your query is a faster way to get a distinct set than a "select distinct" statement at the beginning of your query.

SELECT, 2 counts from 2nd table, RIGHT JOIN on 3rd

I'm trying to gather "followers" for a specific user (#1 in this code).
I'm doing my primary select from followers as the column following will have user #1 and followers.userid will have the userid of the person doing the following.
Next I'm trying to get a count of records from the experiences that have the user id of the follower (how many experiences does this follower have?)
Next, the follower will have rated each experience (1-5 stars) and I want to sum those ratings (experiences.stars) to get an average rating of all experiences.
Lastly, I want to join the followers user record from the users table.
I should end up with
userid, jobs, stars, * from users
SELECT * FROM followers AS F
RIGHT JOIN
(SELECT count(id) FROM experiences AS M WHERE M.userid = F.userid) AS jobs
RIGHT JOIN
(SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
RIGHT JOIN
users AS U ON U.userid = F.userid
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
I've also tried:
SELECT * FROM followers AS F,
(SELECT count(id) FROM experiences AS M WHERE M.userid = F.userid) AS jobs,
(SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
RIGHT JOIN
users AS U ON U.userid = F.userid
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
In cPanel, I'm getting an error that I have syntax error at WHERE F.userid in both statements.
A) what am I missing and B) is there a better way to do this?
It seems to me, the query would be easier to follow like so:
SELECT *
FROM followers AS F
LEFT JOIN users AS U ON U.userid = F.userid
LEFT JOIN (SELECT count(id) FROM experiences AS M WHERE M.userid = **F.userid)** AS jobs
LEFT JOIN (SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
;
All those RIGHT JOINs you originally had would only give you followers that had both "types" of experiences.
Also, correlated subqueries can be expensive (and you didn't need two of them...actually, you didn't even need subqueries), so I'd also rework it like so....
SELECT F.*, U.*, count(x.id), sum(x.stars)
FROM followers AS F
LEFT JOIN users AS U ON U.userid = F.userid
LEFT JOIN experiences AS x ON F.userid = x.user_id
WHERE F.following = 1
GROUP BY [all the fields selected in F and U, or just F.userid if server settings allow]
;
Seems like there's a couple of ON clauses missing.
I know that RIGHT outer joins are supported, but why would we write it that way, and not write it as LEFT outer joins. (We typically reserve RIGHT joins to the towers of academia.)
And it's well past time to ditch the old-school comma syntax for join operations. (Yes, it's still supported for backwards compatibility with existing statements. But new development should use the newer JOIN syntax.)
The condition requiring a non-NULL value of F.following would effectively negate the "outerness" of the join, rendering it equivalent to an INNER join. For clarity, we should either write that as an inner JOIN, or if we want an outer join, we should relocate that condition to the appropriate ON clause.
Also, best practice is to qualify all column references; even when they aren't ambiguous to the optimizer, it makes it easier on the future reader (so the future reader doesn't have to confirm which table contains the id column), as well as protecting the query from throwing "ambiguous column" errors in the future if a column named id is added to another table used by the query.
Also, it's not valid to reference columns from F in the outer query inside inline view queries. We can use a correlated subquery, but not as an inline view.
The specification isn't clear. Example data and sample of expected output would go a long ways to clarifying the requirements.
If we want to use correlated subqueries that return a single row, with a single column, we can put those in the SELECT list ...
SELECT f.*
, u.*
, ( SELECT COUNT(m.id)
FROM experiences m
WHERE m.userid = f.userid
) AS jobs
, ( SELECT SUM(s.stars)
FROM experiences s
WHERE s.userid = f.userid
) AS stars
FROM followers f
LEFT
JOIN users u
ON u.userid = f.userid
WHERE f.following = 1 /* #1 = the user # I want the follwers of/for */
ORDER BY ...
We could get an equivalent result using inline views, but that would look quite different.
I would tend to do the aggregation inside the inline view, something along the lines of this:
SELECT f.*
, u.*
, IFNULL(e.jobs,0) AS jobs
, IFNULL(e.stars,0) AS stars
FROM followers f
LEFT
JOIN users u
ON u.userid = f.userid
LEFT
JOIN ( SELECT ef.userid
, COUNT(ee.id) AS jobs
, SUM(ee.stars) AS stars
FROM followers ef
JOIN experiences ee
ON ee.userid = ef.userid
WHERE ef.following = 1 /* argument */
GROUP BY ef.userid
) e
ON e.userid = f.userid
WHERE f.following = 1 /* argument */
ORDER BY ...

Return a set of SQL Results based on criteria from other tables

I wasn't sure how to actually title this question, but if you got in here, I'll try to explain it to the best of my ability.
You can find my SQL Fiddle here.
SELECT *
FROM `challenges`
LEFT JOIN `challenges_competitors` ON `challenges_competitors`.`challenge_id` = `challenges`.`id`
LEFT JOIN `users` ON `users`.`id` = `challenges_competitors`.`user_id`
WHERE `challenges`.`owner_id` != 3 AND `challenges`.`status` = 'pending'
ORDER BY `challenges`.`id`;
Basically what I want to do with the query is return any challenge that user3 does not own or is part of.
There are two tables involved for this. The first table is challenges. This table holds the user id as owner_id, as well as the challenge id and some other data. Then there's the challenges_competitors table that holds challenge_id and user_id to connect that table with both challenges and the users.
When I run the query and join the tables, there are bound to have duplicates because a challenge can have many competitors. So what I want to do is if there is a challenge that the user3 does not own, but he is part of this challenge, to not get that row back.
I really hope I explained this well. lol
To get the most basic information about which challenges (their id) aren't being owned by users.id = 3 and that don't have that user as a competitor altering your query by adding NOT EXISTS clause would be sufficient.
SELECT c.id
FROM `challenges` AS c
WHERE
c.`owner_id` <> 3 -- discard challenges that have user 3 as their owner
AND c.`status` = 'pending'
AND NOT EXISTS ( -- discard challenges that have user 3 as their competitor
SELECT 1
FROM `challenges_competitors` AS cc
WHERE
cc.`user_id` = 3 -- limit this query to return only rows where user 3 is a competitor
AND cc.`challenge_id` = c.`id` -- join condition with challenges table
);
Here's your modified SQL FIDDLE.
To get the entire set of columns that you have attached in your query you could keep the joins, as #xQbert suggested in his comment.
SELECT *
FROM `challenges` c
LEFT JOIN `challenges_competitors` cc
ON cc.`challenge_id` = c.`id`
LEFT JOIN `users` u ON u.`id` = cc.`user_id`
WHERE
c.`owner_id` != 3
AND c.`status` = 'pending'
AND NOT EXISTS (
SELECT 1
FROM `challenges_competitors` cc2
WHERE cc.`id` = cc2.`id` and cc2.`user_id` = 3
)
ORDER BY c.`id`;
Because owner is also a row in the user table you need to include the user table as an alias. I'm not quite sure what you need to get back - I don't understand exactly what the question is, but you should be able to add a where clause to this to get what you want.
SELECT *
FROM `challenges`
JOIN `challenges_competitors` ON `challenges_competitors`.`challenge_id` = `challenges`.`id`
JOIN `users` ON `users`.`id` = `challenges_competitors`.`user_id`
JOIN `users` AS `owners` ON `owners`.`id` = `challenges`.`owner_id`

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

Counting results from multiple tables with same column

I have a system where, essentially, users are able to put in 3 different pieces of information: a tip, a comment, and a vote. These pieces of information are saved to 3 different tables. The linking column of each table is the user ID. I want to do a query to determine if the user has any pieces of information at all, of any of the three types. I'm trying to do it in a single query, but it's coming out totally wrong. Here's what I'm working with now:
SELECT DISTINCT
*
FROM tips T
LEFT JOIN comments C ON T.user_id = C.user_id
LEFT JOIN votes V ON T.user_id = V.user_id
WHERE T.user_id = 1
This seems to only be getting the tips, duplicated for as many votes or comments there are, even if the votes or comments weren't made by the specified user_id.
I only need a single number in return, not individual counts of each type. I basically want a sum of the number of tips, comments, and votes saved under that user_id, but I don't want to do three queries.
Anyone have any ideas?
Edit: Actually, I don't even technically need an actual count, I just need to know if there are any rows in any of those three tables with that user_id.
Edit 2: I almost have it with this:
SELECT
COUNT(DISTINCT T.tip_id),
COUNT(DISTINCT C.tip_id),
COUNT(DISTINCT V.tip_id)
FROM tips T
LEFT JOIN comments C ON T.user_id = C.user_id
LEFT JOIN votes V ON T.user_id = V.user_id
WHERE T.user_id = 1
I'm testing with user_id 1 (me). I've made 11 tips, voted 4 times, and made no comments. My return is a row with 3 columns: 11, 0, 4. That's the proper count. However, I tested it with a user that hasn't made any tips or comments, but has voted 3 times, that returned 0 for all counts, it should have returned: 0, 0, 3.
The problem that I'm having seems to be that if the table that I'm using for the WHERE clause doesn't have any rows from that user_id, then I get 0 across the board, even if the other tables DO have rows with that user_id. I could use this query:
SELECT
(SELECT COUNT(*) FROM tips WHERE user_id = 2) +
(SELECT COUNT(*) FROM comments WHERE user_id = 2) +
(SELECT COUNT(*) FROM votes WHERE user_id = 2) AS total
But I really wanted to avoid running multiple queries, even if they're subqueries like this.
UPDATE
Thanks to ace, I figured this out:
SELECT
(COUNT(DISTINCT T.tip_id) + COUNT(DISTINCT C.tip_id) + COUNT(DISTINCT V.tip_id)) AS total
FROM users U
LEFT JOIN tips T ON U.user_id = T.user_id
LEFT JOIN votes V ON U.user_id = V.user_id
LEFT JOIN comments C ON U.user_id = C.user_id
WHERE U.user_id = 4
the users table contains the actual information bout the user including, obviously, the user id. I used the user table as the parent, since I could be 100% sure that the user would be present in that table, even if they weren't in the other tables. I got the proper count that I wanted with this query!
As I understand your question. You want to count the total comments + tips + votes for each user. Though is not really clear to me take a look at below query. I added columns for details this is a cross tabs query as someone teach me.
EDITED QUERY:
SELECT
COALESCE(COALESCE(t2.tips,0) + COALESCE(c2.comments,0) + COALESCE(v2.votes,0)) AS `Totals`
FROM parent p
LEFT JOIN (SELECT t.user_id, COUNT(t.tip_id) AS tips FROM tips t GROUP BY t.user_id) t2
ON p.user_id = t2.user_id
LEFT JOIN (SELECT c.user_id, COUNT(c.tip_id) AS comments FROM comments c GROUP BY c.user_id) c2
ON p.user_id = c2.user_id
LEFT JOIN (SELECT v.user_id, COUNT(v.tip_id) AS votes FROM votes v GROUP BY v.user_id) v2
ON p.user_id = v2.user_id
WHERE p.user_id = 1;
Note: This used a parent table in order to get the result of a table which doesn't in other table.
The reason why I use a sub-query in my JOIN is to create a virtual table that will get the sum of tip_id for each table. Also I'm having problem with the DISTINCT using the same query of yours, so I end up with this query.
I know you prefer not using sub-queries, but I failed without a sub-query. For now this is all I can.