mysql search with two joins and a count - mysql

I am currently trying to write a general query which returns the content of 1 table and another joined table plus the count of resulting rows from a third table.
Now my description might seem abstract so I'll try to visualize it
Tables:
posts
| ID | title | description | creator_id |
1 Title1 Descr1 1
2 Title2 Descr2 1
users
| ID | name | avatar |
1 User1 PATH
interactions
| ID | type | target_id | identifier |
1 view 1 IP
2 view 1 IP
Now what I am looking for is an output like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
2 Title2 Descr2 User1 PATH 0
My current query looks like following:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
COUNT(interactions.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
LEFT JOIN
interactions
ON
posts.id = interactions.target_id
But only prints out the posts result which has an interaction like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
How do I need to alter the query in order to also get the other rows which happen to not have any interactions yet?
Thank you for your help!

You can simply subquery third table to count entries:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
(SELECT COUNT(*) FROM interactions i WHERE i.target_id = posts.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
This is also better for performance (no groups, no unoptimized joins)

Try this:
SELECT P.ID
, P.title
, P.description
, U.name
, U.avatar
, IFNULL(COUNT(I.ID), 0) AS view_count
FROM posts P
LEFT JOIN users U ON U.ID = P.creator_id
LEFT JOIN interactions I ON I.target_id = P.ID
GROUP BY P.ID
It seems like you missed the GROUP BY clause. Without this, when you use an aggregate function like COUNT, the documentation says:
there is a single group and it is indeterminate
which name value to choose for the group
That's why your query only returned 1 row.

Try this;)
select posts.id, posts.title, posts.description, users.name, users.avatar, coalesce(t3.view_count, 0) as view_count
from posts
left join users on posts.creator_id = users.id
left join (
select target_id, count(1) as view_count from interactions group by target_id
) t3 on posts.id = t3.target_id
SQLFiddle HERE

Related

Joining 3 tables and using SUM to calculate ratings of forum posts belonging to one thread

I'm doing a simple discussion board for my school project. I have these tables to store users, posts and ratings for those posts (I'll leave out columns and tables that are insignificant for this question.
+------------+
| User |
+------------+
|PK| id_user|
+------------+
| username|
| profile_pic|
+------------+
+------------+
| Post |
+------------+
|PK| id_post|
+------------+
| id_user|
| id_thread|
| content|
| date_posted|
| deleted|
+------------+
+------------+
|Post_ratings|
+------------+
|PK| id_voter|
|PK| id_post|
+------------+
| rating|
+------------+
What I want to do is select all rows from the Post table with a specific id_thread, join it with the User table to select the username and profile_pic of the poster of each post, and a sum of ratings given to each post, so the columns of the result should be id_post, id_user, content, date_posted, deleted, username, profile_pic, and rating.
I managed to come up only with this sloppy query:
SELECT * FROM Post p LEFT JOIN
(SELECT id_user, username, profile_pic FROM User) u
ON p.id_user = u.id_user LEFT JOIN
(SELECT id_post redundant, SUM(rating) rating FROM Post_ratings) pr
ON p.id_post = pr.redundant
WHERE id_thread = 5 AND deleted = 0
ORDER BY date_posted
This does return all posts belonging to one thread, but it shows post's ID twice (the redundant column) and displays SUM of all ratings across all threads in a row with the lowest id_post, shows NULL in other rows.
If anyone can help, thank you in advance.
I would use a join to associate the users and the posts table, and a subquery to sum the ratings:
select p.*, u.username, u.profile_pic,
(select sum(pr.rating) from post_ratings pr where pr.id_post = p.id_post) as rating
from posts p
inner join users u on u.id_user = p.id_user
where p.id_thread = 5 and p.deleted = 0
Of course, you can also use outer aggregation:
select p.*, u.username, u.profile_pic, sum(pr.rating) as rating
from posts p
inner join users u on u.id_user = p.id_user
inner join post_ratings pr on pr.id_post = p.id_post
where p.id_thread = 5 and p.deleted = 0
group by p.id_post, u.user_id
MySQL understand functionally-dependent columns, so it is sufficient to put the primary key of the posts and users table in the group by clause.

MYSQL Optimising JOINED UNION SELECT query

I've re-written this query a few times now (it's Monday) with the attempt of finding the most efficient way of getting the data I require however I'm not sure I'm even approaching it correctly at the moment.
To summarise the problem;
Users have two sets of tags (key_terms, project_terms), there's a link table between each of these between users and tags tables.
I would like to pull out any users that have specified tags in either table. Ideally it'd also include the 'most relevant' tag to that user - but lets put that aside for now.
users
| id | name |
| 1 | dayjo |
| 2 | stackoverflow |
tags
| id | tag |
| 1 | tag1 |
| 2 | tag2 |
user_key_term
| user_id | tag_id |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
project_key_term
| user_id | tag_id |
| 1 | 3 |
| 2 | 3 |
What I want to be able to query on is the named tags, i.e if I search for "tag1" both users should be returned, however if I search for "tag2" only User 1 should be returned.
My Solutions
I tried by selecting users, and joining tags twice (one for each link table), this seemed to work ok but wasn't sure it was the best way, and couldn't figure out how to get the most relevant tag.
SELECT t1.tag, t2.tag as most_relevant_tag, users.* FROM users
LEFT JOIN user_key_term ON user_key_term.user_id = users.id
LEFT JOIN tags t1 ON user_key_term.tag_id = t1.id
LEFT JOIN project_key_term ON project_key_term.user_id = users.id
LEFT JOIN tags t2 ON project_key_term.tag_id = t2.id
WHERE t1.tag IN ('tag1','tag2') OR t2.tag IN ('tag1','tag2')
GROUP BY users.id;
My next attempt was a UNION select, but this one feels dirty;
SELECT users.* FROM
`users`
INNER JOIN (
SELECT project_key_term.user_id, tags.id, tags.tag FROM project_key_term
JOIN tags ON tags.id = project_key_term.tag_id AND tags.tag IN ('tag1')
UNION ALL
SELECT user_key_term.user_id,tags.id, tags.tag FROM user_key_term
JOIN tags ON tags.id = user_key_term.tag_id AND tags.tag IN ('tag1')
) tags ON tags.user_id = users.id
WHERE tags.tag IN ('tag1')
GROUP BY users.id;
But
I've tried running EXPLAIN on both queries to see which is best, but it doesn't reveal anything particularly useful to me. Especially because at the moment there's not a lot of data in the tables, there will potentially be hundreds / thousands of tags.
Any help on the 'correct' or best practice way to do this sort of query would be great!
The union query can be simplified to:
SELECT users.*
FROM users
INNER JOIN (
SELECT user_id,tag_id
FROM project_key_term
UNION ALL
SELECT user_id,tag_id
FROM user_key_term
) alltags ON alltags.user_id = users.id
INNER JOIN tags t on t.id = alltags.tag_id
where t.tag IN ('tag1')
Edit: Getting the most relevant tags
SELECT score, t.tag, users.*
FROM users
INNER JOIN (select user_id, tag_id, count(*) as score
from (SELECT user_id,tag_id
FROM project_key_term
UNION ALL
SELECT user_id,tag_id
FROM user_key_term
) alltags
group by user_id,tag_id) tagcounts ON tagcounts.user_id = users.id
INNER JOIN tags t on t.id = tagcounts.tag_id
where t.tag IN ('tag1','tag2','tag3')
ORDER BY score DESC

mysql - selecting groups and users all in same query

I have following two tables 'USERS' and 'GROUPS':
USERS
-id
-name
-groupid
GROUP
-id
-name
I'd like to return all users along with their group's name and group id. It should be an outer join on group id field correct?
A simple INNER JOIN should be enough:
SELECT `USERS`.*, `GROUP`.name AS group_name
FROM `USERS`, `GROUP`
WHERE `USERS`.groupid = `GROUP`.id
You're going to want to look at the JOIN statement
Doing this from my phone, so pardon any moderately incorrect syntax, but something a long the lines of
Edit: other guy's syntax is better. It's too early here
You can use a LEFT JOIN between users and groups so that users who are not in a group still show up in the result set, but with group name and id NULL:
SELECT
a.*,
b.name AS group_name
FROM
users a
LEFT JOIN
`group` b ON a.group_id = b.id
Side note: Ensure that you're encasing the table name group in backticks because it is a reserved keyword.
The result-set should look something like:
id | name | group_id | group_name
-----------------------------------------------------------------------------
1 | John | 5 | ThisIsGroup5
3 | Tim | 3 | ThisIsGroup3
6 | NotInGroup | NULL | NULL
Changing LEFT to INNER in the above query would INNER JOIN the two tables and exclude the user "NotInGroup" from the result-set.

How to write inner query that returns latest message for a given user?

I have some tables like this:
USERS TABLE:
| id | created | active | fname | lname |
MESSAGES TABLE:
| id | userId| active | date | content |
I am trying to return some user information, along with the most recently added message for a given user.
Below is the structure of the results that I am rying to achieve:
| userId | userCreated | latestMessageDate| latestMessageContent |
The following query returns the user information:
SELECT
user.id,
user.created
FROM user
WHERE user.active = 1
... But how do I now attach the date of the latest message, along with the actual latest message?
I believe using an inner query is one such approach, but how do you write such a query??
SELECT u.fname, u.lname, m.id, m.userID, m.datem, m.content
FROM USERS AS u
LEFT JOIN ( SELECT id, userID, date, content
FROM MESSAGES
WHERE active
ORDER BY date DESC) AS m
ON u.id = m.userId
WHERE u.active
# AND u.id = {$int_user_id}
GROUP BY u.id
Maybe something like this:
SELECT
Users.id AS userId,
Users.created AS userCreated,
LatestMessage.LatestMessageDate,
MESSAGES.content AS latestMessageContent
FROM
Users
LEFT JOIN
(
SELECT
MAX(date) AS LatestMessageDate,
MESSAGES.userId
FROM
MESSAGES
GROUP BY
MESSAGES.userId
) AS LatestMessage
ON Users.id=LatestMessage.userId
LEFT JOIN MESSAGES
ON LatestMessage.LatestMessageDate=MESSAGES.date
AND LatestMessage.userId=MESSAGES.userId

MySQL Count Items In Category

I don't understand MySQL very well, here are the table structures I am using.
users
id | first_name | last_name | username
| password
categories
id | user_id | name | description
links
id | user_id | category_id | name |
url | description | date_added |
hit_counter
I am trying to return a result set like this, to give information about the category for a user that includes how many links are in it.
id | user_id | name | description | link_count
At the moment I have this query, but it only returns rows for categories that have links. It should return rows for categories that do not have any links (empty categories).
SELECT categories.*, COUNT(links.id)
FROM categories LEFT JOIN links ON
categories.id=links.category_id;
How to do this query? Thanks.
we can't do select table dot "star" with an aggregate.
what you wanna do is something like (pseudocode):
select
categories.field1,
categories.field2,
{etc.}
count(links.id)
from categories
left join links
on categories.id = links.category_id
group by
categories.field1,
categories.field2,
{etc.}
iow: you're missing the group by code-block to get the right aggregate in your query result set.
To mold alien052002's answer to fit your specific question, the following (untested) should work:
select c.id,
c.user_id,
c.name,
c.description,
count(l.link_count)
from categories c
left join links l on l.category_id = c.id
group by c.id, c.user_id, c.name, c.description
try this
SELECT categories.*, COUNT(links.id) FROM categories LEFT JOIN links ON categories.id=links.category_id group by categories.id;