Stuck with mysql join with count query - mysql

I'm having a problem with joining 2 queries but in the second query I only want to bring in the count.
This first query works well
SELECT DISTINCT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid, members.first_name, views
FROM forum_sub, members
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
which displays the following
----------------------------------------------------------------------
sub_id | forum_id | title | text | url | userid | first_name | views |
----------------------------------------------------------------------
20 | 1 | test | test |test | 1001 | JOhn | 123 |
----------------------------------------------------------------------
1 | 1 | test | test |test | 1002 | Pete | 23 |
----------------------------------------------------------------------
10 | 1 | test | test |test | 1003 | Harry | 34 |
----------------------------------------------------------------------
But now I want to join the above sub_id to another table called forum_topics and count how many of the same sub_id's there are and bring in that value
for example I could use
SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id
-----------------
|sub_id | topics|
---------------
| 1 | 4 |
-----------------
| 10 | 3 |
-----------------
| 20 | 5 |
-----------------
My question is how can I join those 2 queries so I get something like this
----------------------------------------------------------------------------
sub_id | forum_id | title | text | url | userid | first_name | views | count|
-----------------------------------------------------------------------------
20 | 1 | test | test |test | 1001 | JOhn | 123 | 5 |
-----------------------------------------------------------------------------
1 | 1 | test | test |test | 1002 | Pete | 23 | 4 |
-----------------------------------------------------------------------------
10 | 1 | test | test |test | 1003 | Harry | 34 | 3 |
-----------------------------------------------------------------------------
Any help would be great, I know I need to use a subquery but I've been stuck on this nearly all day with no luck

First avoid SELECT DISTINCT when ever possible. It is evil. It will hide cross joins etc that you have in your query. From this query it is hard to tell what exactly your are doing.
However to include the count you have a couple of options:
One would be to do a sub select in the query:
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views , (SELECT COUNT(sub_id) as topics FROM forum_topics WHERE sub_id = forum_sub.sub_id) count
FROM forum_sub, members
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
Another would be to actually join to the sub query you created:
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views, counts.topics
FROM forum_sub, members
JOIN (SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id) counts ON (counts.sub_id = forum_sub.sub_id)
WHERE members.userid = forum_sub.userid AND forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC
You should also look at joins instead of selecting multiple tables in your from
SELECT forum_sub.sub_id, forum_id, title, text, url, forum_sub.userid,
members.first_name, views, counts.topics
FROM forum_sub
JOIN members ON (members.userid = forum_sub.userid)
JOIN (SELECT sub_id, COUNT(sub_id) as topics FROM forum_topics GROUP BY sub_id) counts ON (counts.sub_id = forum_sub.sub_id)
WHERE forum_sub.forum_id = 1
ORDER BY forum_sub.timestamp DESC

Try
group by sub_id
at the end of your second query instead of WHERE.
Then join it to the other table.

Related

MySQL select unique rows in two columns with the highest value in one column

I have a basic table:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 2 | jamie | 2 | 100 |
| 3 | jamie | 1 | 50 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
I tried using the "Left Joining with self, tweaking join conditions and filters" part of this answer: SQL Select only rows with Max Value on a Column but some reason when there are records with a value of 0 it breaks, and it also doesn't return every unique answer for some reason.
When doing the query on this table I'd like to receive the following values:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
Because they are unique on name and cat and have the highest time value.
The query I adapted from the answer above is:
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, id, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON a.id = b.id AND a.time = b.time
The issue here is that ID is unique per row you can't get the unique value when getting the max; you have to join on the grouped values instead.
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON A.Cat = B.cat and A.Name = B.Name AND a.time = b.time
Think about it... So what ID is mySQL returning form the Inline view? It could be 1 or 3 and 2 or 4 for jamie. Hows does the engine know to pick the one with the max ID? it is "free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. " it could pick the wrong one resulting in incorrect results. So you can't use it to join on.
https://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
If you want to use a self join, you could use this query:
SELECT
d1.*
FROM
date d1 LEFT JOIN date d2
ON d1.name=d2.name
AND d1.cat=d2.cat
AND d1.time<d2.time
WHERE
d2.time IS NULL
It is very simple
SELECT MAX(TIME),name,cat FROM table name group by cat

MySQL Select use IN and GROUP BY

I have two tables messages and users I want to find out which users received the messages however the query is only returning one message.
My Schemas are as follow
Messages
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | 1,2,3,4,5
2 | Test | 1,3,5
3 | Welcome | 1,2,4
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
So I would love to see my results simply as
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | John,Jane,Mark,Mary,Anthony
2 | Test | John,Mark,Anthony
3 | Welcome | John,Jane,Mary
So I am doing my query as so
SELECT msg_id,msg_content,fname AS recepients FROM messages a
LEFT JOIN users ON uid IN(a.recipients)
When I run that query I only get one recipient. Please advice. Thanks.
I think you have to use a alternative way for create tables
Messages
msg_id | msg_content |
----------------------
1 | Hello world |
2 | Test |
3 | Welcome |
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
users_has_messages
uhm_id | uid | msg_id |
---------------------------
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 2 | 2 |
5 | 1 | 3 |
Then you can use your code
Okay, so this schema isn't the best (using comma separated lists of IDs is not a great idea, and the performance of any joins will get pretty bad pretty quick). Best bet is to have a third table mapping uid's to msg_id's as mentioned by #Thilina.
That said, this query will do probably what you're after:
SELECT msg_id,msg_content,GROUP_CONCAT(fname) AS recepients FROM messages a
LEFT JOIN users ON FIND_IN_SET(uid, a.recipients)
GROUP BY msg_id
I tried this in Oracle 12c and it is working fine.
So basically what I did is
- Separate the userid from recipient field and used this a columns.
- Join with USERS table to get user fnames
- Used LISTAGG function to aggregate it back.
For MySql we need to find the corresponding functions to Separate the IDs between commas, Convert it to rows and Aggregate. But the inherent logic would be same.
with users (user_id,fname) as (
select 1 ,'John' from dual union
select 2 ,'Jane' from dual union
select 3 ,'Mark' from dual union
select 4 ,'Mary' from dual union
select 5 ,'Anthony' from dual
),
messages(msg_id, msg_content,recipients) as(
select 1,'Hello world','1,2,3,4,5' from dual union
select 2 , 'Test' ,'1,3,5' from dual union
select 3,' Welcome','1,2,4' from dual
),
flat as(
select msg_id,msg_content,
REGEXP_SUBSTR (recipients, '[^,]+', 1, COLUMN_VALUE) as user_id
from messages,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(recipients ,',' ) + 1
) AS SYS.ODCINUMBERLIST
)
)
),
unames as
( select f.msg_id,f.msg_content,u.fname from flat f inner join users u
on f.user_id = u.user_id
order by f.msg_id
)
SELECT msg_id,msg_content,LISTAGG(fname, ',') WITHIN GROUP (ORDER BY fname) as recipients
from unames
group by msg_id,msg_content

How do I get multiple COUNT with multiple JOINS and multiple conditions?

I have SQL (MySQL) that I've can't figure out. The application is using uploaded photos where there are many tagged participants in a photo and there is the possibility to give photos a vote between 1 to 5.
The original query gets all the votes for a photo and orders them by amount of votes and the average of those votes.
Now I need to limit the returned photos by the ones with more than 1 participant. So photos with only 1 participant should not be accounted for.
Simplified schema looks like this.
PHOTOS
----------------------
| id | title |
----------------------
| 1 | Fun stuff |
| 2 | Crazy girls |
| 3 | Single boy |
PHOTO_VOTES
-------------------------------------------
| photo_id | grade | date | user_id |
-------------------------------------------
| 1 | 3 | … | 12 |
| 1 | 3 | … | 12 |
| 2 | 5 | … | 14 |
| 2 | 4 | … | 14 |
| 3 | 4 | … | 15 |
| 3 | 4 | … | 18 |
PHOTO_PARTICIPANTS
-------------------------
| photo_id | user_id |
-------------------------
| 1 | 12 |
| 1 | 21 |
| 1 | 33 |
| 2 | 14 |
| 2 | 33 |
| 3 | 12 |
This is how far I got:
SELECT vote.photo_id,
COUNT(vote.photo_id) AS vote_count,
AVG(vote.grade) AS vote_average,
COUNT(pp.photo_id) AS participant_count
FROM photo_votes vote
LEFT JOIN photos p ON (vote.photo_id = p.id)
LEFT JOIN photo_participants pp ON (pp.photo_id = p.id)
GROUP BY vote.post_id,
HAVING vote_count >= 2
AND vote_average >= 3
AND participant_count > 1
ORDER BY count DESC, average DESC;
Basically what I'm looking for to end up with, excluding the photo with only one participant:
VOTES
-----------------------------------------------------------
| photo_id | vote_count | average | participant_count
-----------------------------------------------------------
| 1 | 2 | 3 | 3
| 2 | 2 | 4.5 | 2
Update
It turned out this is a very inefficient way of trying to do what I want. Gordons answer below did solve the problem, but as soon as I wanted to join fields from the photos table as well, the "cartesian product"-issue became a real problem - it became a very heavy and slow query.
The solution I finally ended up with is adding a cache-field into the photos table keeping track of how many participants are in the photo. In other words I added a 'participant_count' field to 'photos' that is being updated every time a change is made to the participants table. I also run a cron-job regularly to make sure all photos 'participant_count' are properly up-to-date.
First, you don't need left joins for this. But that shouldn't affect the results. The problem is that you have a cartesian product, because you have two 1-n relationships to photos: votes and participants.
The proper way to fix this is by using subqueries:
SELECT pv.photo_id, pv.vote_count, pv.vote_average, pp.participant_count
FROM (SELECT pv.photo_id, count(*) AS vote_count, avg(grade) AS vote_average
FROM photo_votes pv
GROUP BY pv.photo_id
) pv
JOIN
(SELECT pp.photo_id, count(*) AS participant_count
FROM photo_participants p;
GROUP bY pv.photo_id
) pp
ON pv.photo_id = pp.photo_id
WHERE pv.vote_count >= 2 AND
pv.vote_average >= 3 AND
pp.participant_count > 1
ORDER BY pv.vote_count DESC, pv.vote_average DESC;
Note that you don't even need the photos table, because you are not using any fields in it.

Mysql count records grouped by ID in multiple tables

I'm developing an application integrated with facebook. This application can be embedded in FB page as tab app.
Using FB SDK feeds of page will be stored in Feeds table.
Page fans will may have liked and commented on feeds posted by page.
Users' likes store in Like Table and users' comments store in Comment table
I want to get total count ( Likes count + comment count) of each users'.
SQL Fiddle : http://sqlfiddle.com/#!2/ecb37/10/0
Table : Feeds
| ID | POST_ID |
|----|---------------------------------|
| 56 | 150348635024244_795407097185058 |
| 55 | 150348635024244_795410940518007 |
| 54 | 150348635024244_795414953850939 |
| 53 | 150348635024244_797424133650021 |
| 52 | 150348635024244_797455793646855 |
| 51 | 150348635024244_798997120159389 |
| 50 | 150348635024244_798997946825973 |
Table : Likes
SELECT user_id, COUNT(*) FROM likes GROUP by user_id
| USER_ID | LIKECOUNT |
|------------------|-----------|
| 913403225356462 | 4 |
| 150348635024244 | 3 |
| 356139014550882 | 2 |
| 753274941400012 | 2 |
| 1559751687580867 | 1 |
Table : Comments
SELECT user_id, COUNT(*) FROM comments GROUP by user_id
| USER_ID | COMMENTSCOUNT |
|-----------------|---------------|
| 150348635024244 | 2 |
| 356139014550882 | 2 |
| 913403225356462 | 2 |
Result should be like this
| POINTS | LIKESCOUNT | COMMENTSCOUNT | USER_ID |
|--------|------------|---------------|-----------------|
| 6 | 4 | 2 | 913403225356462 |
| 5 | 3 | 2 | 150348635024244 |
| 4 | 2 | 2 | 356139014550882 |
| 2 | 2 | 0 | 753274941400012 |
| 1 | 1 | 0 |1559751687580867 |
I tried this query. but count of each user's is wrong
SELECT COUNT(likes.user_id)+COUNT(comments.user_id) as points, likes.user_id FROM `likes`
LEFT JOIN comments ON likes.user_id = comments.user_id
LEFT JOIN feeds ON likes.post_id = feeds.post_id
WHERE likes.post_id LIKE '153548635024244%'
GROUP BY likes.user_id
ORDER BY points DESC
The two queries are unrelated and a join is useless. Use a UNION ALL:
SELECT user_id, sum(n) from (
SELECT user_id, COUNT(*) n FROM likes GROUP by user_id
UNION ALL
SELECT user_id, COUNT(*) FROM comments GROUP by user_id
) x
GROUP BY user_id
UNION ALL is needed instead of just UNION, because UNION removes duplicates and would cause incorrect results for the edge case of the two subqueries yielding the same counts.
The simple way to get what you want is to use count(distinct). But that will likely have lousy performance. Instead, use correlated subqueries:
SELECT COUNT(*) +
(select COUNT(c.user_id) from comments c where c.user_id = l.user_id)
) as points, l.user_id
FROM likes l
WHERE l.post_id LIKE '153548635024244%'
GROUP BY l.user_id
ORDER BY points DESC;
I'm not sure what the feeds table is for. However, you version of the query creates a cartesian product between the different tables. If you have a lot of activity for a given user, that would be very bad for performance.

Join votes table and sum all votes

I've got two tables. One of them contains quotes and the other one lists all given votes (either +1 or -1) for each quote. For demonstration purposes I've made simplified versions of the two tables:
Quotes
+----+-----------------------------------------------------------------------+
| ID | quote |
+----+-----------------------------------------------------------------------+
| 1 | If you stare into the Abyss long enough the Abyss stares back at you. |
| 2 | Don't cry because it's over. Smile because it happened. |
| 3 | Those that fail to learn from history, are doomed to repeat it. |
| 4 | Find a job you love and you'll never work a day in your life. |
+----+-----------------------------------------------------------------------+
Votes
+----+-------+------+
| ID | quote | vote |
+----+-------+------+
| 1 | 1 | -1 |
| 2 | 1 | -1 |
| 3 | 3 | 1 |
| 4 | 3 | -1 |
| 5 | 3 | 1 |
| 6 | 3 | -1 |
| 7 | 4 | 1 |
| 8 | 4 | 1 |
| 9 | 4 | 1 |
+----+-------+------+
I'd like to list all quotes on my site and show the respective vote count besides. At first, the SQL query should read all quotes and afterwards join the votes table. However, it should finally list the sum of all votes for each quote. The result of the SQL query will therefore look as follows:
+----+-----------------+------+
| ID | quote | vote |
+----+-----------------+------+
| 1 | If you stare... | -2 |
| 2 | Don't cry... | NULL |
| 3 | Those that... | 0 |
| 4 | Find a job... | 3 |
+----+-----------------+------+
How does the SQL query look like that does the previously described?
SELECT
`quotes`.`id` as `ID`,
`quote`.`quote` as `quote`,
SUM(`votes`.`vote`) AS `vote`
FROM `quotes`
LEFT JOIN `votes`
ON `quotes`.`id` = `votes`.`quote`
GROUP BY `quotes`.`id`
should do the trick.
Assuming id columns are primary keys (they are unique for each record).
SELECT
ID, quote, (SELECT sum(vote) from votes where votes.quote=quotes.ID)
FROM
quotes
SELECT q.id, q.quote, SUM(v.vote ) as summ
FROM Quotes q
LEFT JOIN Votes v ON q.id=v.quote
GROUP BY q.id, q.quote ;
The following should work. The left join means the vote summaries are included even if there is no line.
select ID, quote, total_votes from quotes
left join
(select quote, sum(vote) as total_votes from quotes
group by quote) ) as vote_totals
on quotes.ID = vote_totals.quote
Select q.id, q.quote, sum(v.vote) from
quotes q
inner join votes v on
q.id= v.quote
group by v.quote
SELECT Quotes.ID ID, Quotes.QUOTE QUOTE, SUM(Votes.vote) VOTE FROM Quotes LEFT JOIN Votes ON Votes.quote = Quotes.id