Natural join and left join together in mysql - mysql

I have following tables
questions -> id, question_data, user_id
users -> id, fname, lname
question_connect-> id, question_id, user_id
My initial query was as follows
select questions.id, questions.question_data, users.id, users.fname from questions, users where questions.user_id = users.id limit 30
But over here, i want count of users on that question, so i tried following query
select questions.id, questions.question_data, users.id, users.fname, count(questions_connect.id) from questions, users LEFT JOIN questions_connect ON `questions`.`id` = `questions_connect`.`question_id` where questions.user_id = users.id group by `questions_connect`.`id` limit 30
this shows error
Unknown column 'questions.id' in 'on clause'
SO can we make 1 call with natural join and left join and if yes, where i am going wrong..?

Using an explicit join should sort you out:
select questions.id, questions.question_data, users.id, users.fname, count(questions_connect.id)
from questions
join users on questions.user_id = users.id
left join questions_connect on `questions`.`id` = `questions_connect`.`question_id`
group by `questions_connect`.`id`
limit 30
You're better off specifying all your joins explicitly, try to forget that implicit joins exist.

I believe you don't need to put the quotes on the joins
ON questions.id = questions_connect.question_id

Related

SQL Merge Two SELECT statements together

I have the following SQL query which returns 2 results which are the same only different by a where clause.
I would like to have them combined and group them under the user names. At the moment the two results are displayed but the results are not fully combined and the users are shown twice because of the two select statements.
How can I combine the two to create a single result.
The SQL query is as follows
SELECT
COUNT (asings.user_id),
post_status.status_id as status_id,
users.name,
users.id as user_id
from asigns
LEFT JOIN
post_status on asigns.post_id = post_status.post_id
RIGHT JOIN
users on asigns.user_id = users.id
WHERE
post_status.status_id = 2
GROUP BY users.id
UNION
SELECT
COUNT(asigns.user_id),
post_status.status_id as status_id,
users.name,
users.id from asigns
LEFT JOIN
post_status on asigns.post_id = post_status.post_id
RIGHT JOIN
users on asigns.user_id = users.id
WHERE
post_status.status_id = 3
GROUP BY users.id
See in this photo the usernames are appearing twice, what I want is to join the two username, and move count as two different columns depending on the status_id
How can I solve this. Thanks
You should use a single query with conditional aggregation:
SELECT
SUM(ps.status_id = 2) AS cnt_2,
SUM(ps.status_id = 3) AS cnt_3,
u.name,
u.id as user_id
FROM users u
LEFT JOIN assigns a ON a.user_id = u.id
LEFT JOIN post_status ps ON a.post_id = ps.post_id
GROUP BY u.id;
Use SELECT distinct for the union:
SELECT distinct * FROM ([FIRST SELECT] UNION [SECOND SELECT])
Or GROUP BY username
SELECT * FROM ([FIRST SELECT] UNION [SECOND SELECT]) GROUP BY username
BUT I suggest to review your both selects, if it differs just in the where clause so use where clause with the operator OR and you handle it just by one SELECT
Although your are using a LEFT and a RIGHT join and implicitly an INNER join between users and asigns, the WHERE clause transforms all the joins to INNER joins, because you are picking only the matching rows from post_status.
So, you only need INNER joins and conditional aggregation:
SELECT COALESCE(SUM(ps.status_id = 2), 0) counter_2,
COALESCE(SUM(ps.status_id = 3), 0) counter_3,
u.name,
u.id AS user_id
FROM users u
INNER JOIN asigns a ON a.user_id = u.id
INNER JOIN post_status ps ON ps.post_id = a.post_id
WHERE ps.status_id IN (2, 3)
GROUP BY u.id;

Join two different tables ordered by common value (hotness)

I'm trying to select results from two different unrelated tables, showcase and questions to appear in a feed. They should be ordered by the common column hotness which is a float value.
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
ORDER BY hotness DESC
LIMIT 10
I've tried UNION, but I have no idea how I should be using it here and get this error unknown column hotness
You need to select the value in order for the ORDER BY to recognize it:
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar, s.hotness
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION ALL
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0, q.hotness
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
ORDER BY hotness DESC;
Note that I also changed the UNION to UNION ALL. Unless you intend to remove duplicates, there is no reason to incur the extra processing for doing that.
You can try this query:
SELECT r.* FROM (
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar, s.hotness
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0, q.hotness
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
) as r
ORDER BY r.hotness DESC
LIMIT 10
You need to merge Union result in subquery to apply Order by on the result. I also added hotness in select clause, please check I take field from good table.

Mysql count and return just one row of data

I need to count the amount of users that have have answered all of those 3 profile_options (so they have at least 3 records in the profile_answers table).
SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3
The problem is that this query is return a table with rows for each user and how many they answered (in this case always 3). What I need is to return just one row that has the total number of users (so the sum of all rows of this example)
I know how to do it with another subquery but the problem is that I am running into "Mysql::Error: Too high level of nesting for select"
Is there a way to do this without the extra subquery?
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) sum_sub
Please give this query a shoot
SELECT COUNT(DISTINCT(u.id)) AS users_count
FROM users AS u
INNER JOIN (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a ON a.user_id = u.id
If you have lots of data in your tables, you will get a better/faster performance by using temporary tables like so
CREATE TEMPORARY TABLE a (KEY(user_id)) ENGINE = MEMORY
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3;
Then your final query will look like this
SELECT COUNT(DISTINCT(u.id)) as users_count
FROM a
INNER JOIN on a.user_id = u.id
Unless there is a need to join the users table you can go with this
SELECT COUNT(*) AS users_count
FROM (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a
Should you need another solution, please consider providing us you EXPLAIN EXTENDED for the query and the table definitions along with a better problem description.
I hope this helps
You can give the queries a name using the AS clause. See the updated query below.
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) as users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) as sum_sub
You should not group by on a field not present in select statement.
select id, count(*) from users group by id is fine
select count(id) from users group by id is NOT
Regarding your query I think the link to user table is not necessary. Just using foreign key should be fine.
Try this one:
select count(*) from
(SELECT users_id count(*) as cnt
FROM profile_answers
INNER JOIN users ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
group by users_id
having count(*) >3)

Joining on a Select Distinct in MySQL reorders by joined table

I've looked around for an answer to this but I'm not finding it. I have a site with articles stored in an article table and writers in the user table. I wanted to get a list of authors ordered by how recently they'd written an article.
This gives me the User ids:
SELECT distinct a.user_id
FROM `article` as a
ORDER BY a.id desc
The problem is that as soon as I try to bring the names in by joining the order changes so that it's by user id. I've tried this:
SELECT distinct a.user_id, u.name
FROM `article` as a
LEFT JOIN user as u on u.id = a.user_id
ORDER BY a.id desc, u.id desc
and
SELECT distinct a.user_id, u.name
FROM `article` as a
LEFT JOIN user as u on u.id = a.user_id
ORDER BY u.id desc, a.id desc
but both alter the order of the names. I'm obviously doing something stupid, but what?
The fact that DISTINCT happens to work with ORDER BY in your first example is a fluke, and not standard SQL. You need something like this:
SELECT a.user_id, u.name
FROM article a
LEFT JOIN user u ON u.id = a.user_id
GROUP BY a.user_id
ORDER BY MAX(a.id) desc
try this
SELECT a.user_id, u.name
FROM `article` a
LEFT JOIN user u on u.id = a.user_id
GROUP BY a.user_id
ORDER BY a.id desc
Your ORDER BY sorts the rows by either the User ID and/or Article ID (depending on the example).
If you want reliable sorting by name, then include the u.name field in the ORDER BY fields.
this is postgres syntax, but you can easily translate it into mysql
select u.*, q.last_article_id
from user u
inner join (
select a.user_id, max(a.id) as last_article_id
from article a
group by a.user_id
) q
on u.id = a.q.id
order by q.last_article_id desc
Something along the lines of the following should work:
select users.id, articles.id, max(articles.created_at) as recent_date
from users
left join articles on users.id = articles.user_id
group by articles.id
order by recent_date
Not sure what exactly your fields are named and all, but this should point you in the right direction.
You got a lot of answers to choose from... I believe the key to what you're asking is use of the aggregate function MAX() http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_max

Using a limit on a left join in mysql

The following query selects all posts and each post's owner, all of the comments that belong to each post, and the owner of each comment.
I need to only retrieve 5 comments per post. I rewrote the query, but I get an error of "each derived table must have it's own alias".
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname, comments.id AS commentId, comments.text, commenters.id, commenters.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN comments ON posts.id = comments.postid
JOIN users AS commenters ON comments.userId = commenters.id
ORDER BY posts.createdAt
New Query:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) AS comments ON comments.postid = posts.id
ORDER BY posts.createdAt
UPDATE The query now works, but it does not produce the desired output. I want to output 10 posts, with 5 comments for each post. This limit clause will only apply for the comments of the first post encountered.
From the edits and comment feedback, here's the query I think you are looking for... The inner most will prequery gets the posts and who initiated the post, comments and who posted the comments. This inner query is also pre-sorted with the MOST RECENT COMMENTS to the top per postID. Using the result of that, I'm joining to the sql variables (#variables) to get the #varRow increased every time a new comment and reset back to 1 each time a post ID changes (hence the inner PreQuery orders by post ID FIRST). Finally, using the HAVING clause to have the comment's #varRow count < 6 will get at MOST 5 of each post.
If you want to limit what posts you are trying to retrieve, I would apply a WHERE clause (such as date/time if available) at the INNER most that generates the "PreQuery".
select straight_join
PreQuery.*,
#varRow := if( #LastPost = PreQuery.PostID, #varRow +1, 1 ) CommentRow,
#LastPost := PreQuery.PostID PostID2
from
( select
posts.id PostID,
posts.body,
posts.CreatedAt,
u1.id UserID,
u1.DisplayName NameOfPoster,
c.id,
c.userid CommentUserID,
c.text CommentText,
u2.DisplayName CommentUserName
from
posts
join users u1
on posts.ownerUserID = u1.id
LEFT JOIN comments c
on posts.id = c.PostID
join users u2
on c.userid = u2.id
where
posts.id = TheOneParentIDYouWant
OR posts.parentid = TheOneParentIDYouWant
order by
posts.ID,
c.id desc ) PreQuery,
(select #varRow := 0, #LastPost = 0 ) SQLVars
having
CommentRow < 6
order by
PreQuery.postid,
CommentRow
--- EDIT --- per comment
I THINK what you mean by which "Parent Post" the comments are associated with is because they have the post ID directly. Since the inner-most query does a join of all elements / tables across the board, all are coming along for the ride...
Post -> User (to get posting user name )
Post -> Comment (on Common Post ID -- left joined)
Comment -> User ( to get commenting user name)
Once THAT is all done and sorted by common Post ID and most recent comment sorted to the top, I then apply the #vars against ALL returned rows. The HAVING clause will strip out any comment where it's sequence is BEYOND the 5 you were looking for.
You need to give your derived table an alias:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) AS derived_table_alias
ORDER BY posts.createdAt
Since you're using a subquery (which is what it means by "derived table"), it must indeed have an alias. Thus, all you need to do is:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) as some_alias --This is what's triggering the error
ORDER BY posts.createdAt
Even if you're not selecting from the subquery, and just using it as a filter, you have to alias it.
By Error
Add an alias following your subquery.
Example: SELECT * FROM foo JOIN (select * from bar) AS <alias_here>
Make sure you have a field in the posts table and that it is called createdAt. I'm not sure MySQL is case-sensitive, but the error you posted says createdat (with the 'A' lowercased)
You have two LEFT JOINs, but only one ON statement. A join isn't anything without the hook to join it on. Example:
SELECT *
FROM foo JOIN bar ON (foo.id=bar.id)
LEFT JOIN (select * from foobar) AS baz **ON foo.id=baz.id**
In order to join on a field, the field needs to be present in the table involved in the join. So in the above example, if you match foo.id with baz.id, id needs to be returned in the subquery (baz).