Mysql group by fetch last row - php

SELECT * FROM conversation_1
LEFT JOIN conversation_2
ON conversation_1.c_id = conversation_2.c_id
LEFT JOIN user
ON conversation_2.user_id = user.user_id
LEFT JOIN message
ON conversation_1.c_id = message.c_id
WHERE conversation_1.user_id=1
GROUP BY message.c_id
conversation_1 conversation_2
c_id user_id c_id user_id
1 1 1 2
2 1 2 3
3 2
I have a message DB build in Mysql
I make 4 tables user, conversation_1, conversation_2, message
when user try to open his message box, it will fetch out all conversations(conversation_1)
than join to user conversation_2 and use conversation_2 to find out which user
than join to the message.
c_id user_id user_name message
1 2 Alex Hi user_1, this is user_2
2 3 John hi user_3, user_2 don't talk to me
it works fine, however I want to display the message from last row GROUP BY
currently it display the 1st row in this group.
ps.conversation_1.c_id is auto increment and the c_id will insert to conversation_2 who has join this conversation

select * from (SELECT * FROM conversation_1
LEFT JOIN conversation_2
ON conversation_1.c_id = conversation_2.c_id
LEFT JOIN user
ON conversation_2.user_id = user.user_id
LEFT JOIN message
ON conversation_1.c_id = message.c_id
WHERE conversation_1.user_id=1
order by conversation_1.c_id desc) finalData
GROUP BY message.c_id

Beware that, as documented under MySQL Extensions to GROUP BY:
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. This means that the preceding query is legal in MySQL. You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
This is what is happening to select the message (and potentially other columns) in your existing query.
Instead, you want the groupwise maximum:
SELECT messages.* FROM messages NATURAL JOIN (
SELECT c_id, MAX(m_id) m_id FROM messages GROUP BY c_id
) t

Related

Order by result from another tables

HI i have skills tables as below
Various user add skills to their profile. Now i want to list all the skills decreasing order of their uses. Like as below
Php(10) , ASP (5) , Perl(1)
Its means 10 user added php as their skill, 5 user ASP etc.
I have stored the skills in user table in skills column with comma separated
Try this:
select id, name
from (
select *, (select sum(1) from user u where find_in_set(s.id, u.skills)) as cnt
from skills s
) t
order by cnt desc
-- limit 20
Assuming you have another table/relation to represent user_skills, that contains (for example) user_id and skill_id foreign keys, then you'd want to join this to the skills table and group the results similar to this:
select name, count(skill_id) as ranking
from skills join user_skills on skills.id = user_skills.skill_id
group by name
order by count(skill_id) desc, name asc;
It excludes skills which have not been selected by any users. If you wanted to include those too, change the join to a left join.
Edit: With the original question updated to include the definition of the users table, then perhaps this would suffice. Again you would need a left join to include skills that no users had picked.
select skills.name, count(users.id) as qty
from skills join users
on locate(concat(',', skills.id, ','), concat(',', users.skills, ',')) > 0
group by skills.name
order by count(users.id) desc, skills.name asc;

Selecting N rows from each group in MYSQL

I need to search for the updates sent by the friends of a giving user.
There is a table called friendship. It has a column called profile1 and another one called profile2. It represents the friendship between two users in this websystem, and a friendship is the presence of two giving ids, no matter in what position. So the profile with id 1 may have 2 friends, profile with id 2 and with id 3 as following:
friendship
profile1 profile2
1 2 <--
3 1 <--
2 5
...
Now I want to search for the updates sent by some user's friends. There is this table update
update
id content time profile
1 A text ... 2
2 A text ... 2
3 A text ... 3
4 A text ... 2
5 A text ... 3
6 A text ... 2
7 A text ... 10
8 A text ... 11
If my profile/user is identified by the id 1, and it has only 2 friends (the profiles identified by id 2 and 3) and also I need my search to return only 2 results by each user, my SELECT has to return updates 1,2,3 and 5.
Preferably updates should be grouped by its author and it would be great if I could set the number of different profiles to be considered in this search (for example, if profile 1 had 10 friends and I wanted only updates from 3 profiles, the most recent must appear first).
Do you know how can I achieve this??
thank you very much!
#EDIT
This returns all updates sent by friends of profile 1. But i'm not sure whether or not i'm in the right direction
SELECT u.*
FROM `update` u
INNER JOIN friendship f1 ON f1.profile1 = u.author
WHERE f1.profile2 =1
UNION
SELECT u.*
FROM `update` u
INNER JOIN friendship f2 ON f2.profile2 = u.author
WHERE f2.profile1 =1
If you are willing to do it in two queries, you can do it like this. First, get three profiles who have most recently posted based on your constraints:
-- Get the three latest updated profiles from here.
-- (we can't use a CTE because MySQL doesn't support
-- them yet).
SELECT DISTINCT p.profile FROM
(
SELECT ui.profile, ui.time FROM
(
SELECT u.profile, u.time
FROM `update` u
INNER JOIN `friendship` f ON f.profile2 = u.profile
WHERE f.profile1 = 1
UNION ALL
SELECT u.profile, u.time
FROM `update` u
INNER JOIN `friendship` f ON f.profile1 = u.profile
WHERE f.profile2 = 1
) ui ORDER BY ui.time DESC
) p LIMIT 0, 3;
From that query, get the three profile IDs out and put them in place of <id1>, <id2> and <id3> in the following query
-- Use a union to get the result set back
(SELECT a.content, a.time, a.profile FROM `update` a
WHERE a.profile = <id1>
ORDER BY a.time DESC
LIMIT 0, 2)
UNION ALL
(SELECT a.content, a.time, a.profile FROM `update` a
WHERE a.profile = <id2>
ORDER BY a.time DESC
LIMIT 0, 2)
UNION ALL
(SELECT a.content, a.time, a.profile FROM `update` a
WHERE a.profile = <id3>
ORDER BY a.time DESC
LIMIT 0, 2);
If you get less than three profiles back, either remove parts of the query in your PHP code, or set the WHERE clause to something like 0 so it always evaluates to fault (assuming you don't have a profile ID of zero)
The 2 in the limit clauses above can be changed if you want more or fewer results per profile.
Sample SQL fiddle: http://sqlfiddle.com/#!2/22e57/1 (updated fiddle to make the content more meaningful and to use times)
I would suggest doing a series of queries for each author within one transaction, that way there would not be a need for grouping - you could simply append results together outside of your SQL.
SELECT * FROM `update` WHERE
profile IN (SELECT profile2 FROM `friendship` WHERE profile1=1) OR
profile IN (SELECT profile1 FROM `friendship` WHERE profile2=1);
try this sqlFiddle
SELECT T1.profile,T1.content,T1.time
FROM
(SELECT UPD.profile,UPD.content,UPD.time,
IF (#prevProfile != UPD.profile,#timeRank:=1,#timeRank:=#timeRank+1) as timeRank,
#prevProfile := UPD.profile
FROM
(SELECT UP.profile,UP.content,UP.time
FROM
(SELECT profile,max(time) as latestUpdateTime
FROM friendship F INNER JOIN updates U
ON (F.profile1 = 1 AND U.profile = profile2) /* <-- specify profile on this line */
OR(F.profile2 = 1 AND U.profile = profile1) /* <-- specify profile on this line */
GROUP BY profile
ORDER BY latestUpdateTime DESC
LIMIT 3 /* limit to 3 friends profiles that have the most recent updates */
)as LU
INNER JOIN updates UP
ON (UP.profile = LU.profile)
ORDER BY profile,time DESC
)as UPD,(SELECT #prevProfile:=0,#timeRank:=0)variables
)T1
WHERE T1.timeRank BETWEEN 1 AND 2 /* grab 2 lastest updates for each profile */
ORDER BY T1.time DESC
in my example, profile id 1 has more than 3 friends, but i am only grabbing 3 friends that made the most recent updates.
explanation of above query.
LU grabs 3 profiles that are friends with profile id 1 that made the latest updates.
UPD grabs all contents that belong to these 3 friends.
T1 returns the contents along with a timeRank number for each content in order from 1 counting upward order by time DESCENDING for each profile
and finally the WHERE we only grab 2 content updates for each profile
then we finally ORDER these updates based on TIME starting from most recent.

MySQL query to find the most popular value in a column joined by another value in a second table

I have two tables:
users: user_id, user_zip
settings: user_id, pref_ex_loc
I need to find the single most popular 'pref_ex_loc' from the settings table based on a particular user_zip, which will be specified as the variable $userzip.
Here is the query that I have now and obviously it doesn't work.
$popularexloc = "SELECT pref_ex_loc, user_id COUNT(pref_ex_loc) AS countloc
FROM settings FULL OUTER JOIN users ON settings.user_id = users.user_id
WHERE users.user_zip='$userzip'
GROUP BY settings.pref_ex_loc
ORDER BY countloc LIMIT 1";
$popexloc = mysql_query($popularexloc) or die('SQL Error :: '.mysql_error());
$exlocrow = mysql_fetch_array($popexloc);
$mostpopexloc=$exlocrow[0];
echo '<option value="'.$mostpopexloc.'">'.$mostpopexloc.'</option>';
What am I doing wrong here? I'm not getting any kind of error from this either.
Give this a try:
select s.pref_ex_loc from settings s
join users u on (u.user_id = s.user_id)
where user_zip = $userzip
group by s.pref_ex_loc
order by count(*) desc
limit 1
As you said, this will give you the "single most popular 'pref_ex_loc' from the settings table based on a particular user_zip"
Well, for one thing you are missing a comma before the COUNT():
SELECT pref_ex_loc, user_id COUNT(...
You should have a comma between each field in your select-list:
SELECT pref_ex_loc, user_id, COUNT(...
I would recommend using COUNT(*) instead of COUNT(pref_ex_loc). In this case, either should give the right answer, but in MySQL COUNT(*) usually performs slightly better.
You're using outer join, but then in the WHERE clause you're testing one of the columns of users so it's effectively not an outer join anymore. In this query, I believe you simply need an INNER JOIN, unless you need to handle the possibility that none of the users reference any of your pref_ex_loc values. Read A Visual Explanation of SQL Joins.
Also, MySQL does not support FULL OUTER JOIN.
Your user_id in the select-list, when it is neither in the GROUP BY clause nor in an aggregate function, is an ambiguous field, taking its value from one arbitrary row in the group. You should remove user_id from the select-list.
Sort by the countloc DESC to get the greatest value first.
So here's what I see as a better query:
SELECT pref_ex_loc, COUNT(*) AS countloc
FROM settings INNER JOIN users ON settings.user_id = users.user_id
WHERE users.user_zip='$userzip' GROUP BY settings.pref_ex_loc
ORDER BY countloc DESC LIMIT 1
this will allow values (duplicate most popular) with the highest pref_ex_loc to be shown in the list.
It doesn't use LIMIT, because LIMIT forces the maximum number of rows to be shown. Now, here's the question, What if there are two or more rows that ties up with the most popular pref_ex_loc?
SELECT b.pref_ex_loc
FROM users a
INNER JOIN settings b
ON a.user_ID = b.user_ID
WHERE a.user_zip = 1 -- change the value here
GROUP BY b.pref_ex_loc
HAVING COUNT(*) =
(
SELECT MAX(totalCount)
FROM
(
SELECT b.pref_ex_loc, COUNT(*) totalCount
FROM users a
INNER JOIN settings b
ON a.user_ID = b.user_ID
WHERE a.user_zip = 1 -- change the value here
GROUP BY b.pref_ex_loc
) s
)
SQLFiddle Demo
SQLFiddle Demo (with duplicate most popular)
Try with this query:
SELECT user_id, COUNT(pref_ex_loc) AS countloc
FROM users LEFT JOIN settings ON users.user_id = settings.user_id
WHERE users.user_zip='$userzip' GROUP BY user_id ORDER BY countloc LIMIT 1

Complicated MySQL Database Query

I have the following database structure:
Sites table
id | name | other_fields
Backups table
id | site_id | initiated_on(unix timestamp) | size(float) | status
So Backups table have a Many to One relationship with Sites table connected via site_id
And I would like to output the data in the following format
name | Latest initiated_on | status of the latest initiated_on row
And I have the following SQL query
SELECT *, `sites`.`id` as sid, SUM(`backups`.`size`) AS size
FROM (`sites`)
LEFT JOIN `backups` ON `sites`.`id` = `backups`.`site_id`
WHERE `sites`.`id` = '1'
GROUP BY `sites`.`id`
ORDER BY `backups`.`initiated_on` desc
The thing is, with the above query I can achieve what I am looking for, but the only problem is I don't get the latest initiated_on values.
So if I had 3 rows in backups with site_id=1, the query does not pick out the row with the highest value in initiated_on. It just picks out any row.
Please help, and
thanks in advance.
You should try:
SELECT sites.name, FROM_UNIXTIME(b.latest) as latest, b.size, b.status
FROM sites
LEFT JOIN
( SELECT bg.site_id, bg.latest, bg.sizesum AS size, bu.status
FROM
( SELECT site_id, MAX(initiated_on) as latest, SUM(size) as sizesum
FROM backups
GROUP BY site_id ) bg
JOIN backups bu
ON bu.initiated_on = bg.latest AND bu.site_id = bg.site_id
) b
ON sites.id = b.site_id
In the GROUP BY subquery - bg here, the only columns you can use for SELECT are columns that are either aggregated by a function or listed in the GROUP BY part.
http://dev.mysql.com/doc/refman/5.5/en/group-by-hidden-columns.html
Once you have all the aggregate values you need to join the result again to backups to find other values for the row with latest timestamp - b.
Finally join the result to the sites table to get names - or left join if you want to list all sites, even without a backup.
Try with this:
select S.name, B.initiated_on, B.status
from sites as S left join backups as B on S.id = B.site_id
where B.initiated_on =
(select max(initiated_on)
from backups
where site_id = S.id)
To get the latest time, you need to make a subquery like this:
SELECT sites.id as sid,
SUM(backups.size) AS size
latest.time AS latesttime
FROM sites AS sites
LEFT JOIN (SELECT site_id,
MAX(initiated_on) AS time
FROM backups
GROUP BY site_id) AS latest
ON latest.site_id = sites.id
LEFT JOIN backups
ON sites.id = backups.site_id
WHERE sites.id = 1
GROUP BY sites.id
ORDER BY backups.initiated_on desc
I have removed the SELECT * as this will only work using MySQL and is generally bad practice anyway. Non-MySQL RDBSs will throw an error if you include the other fields, even individually and you will need to make this query itself into a subquery and then do an INNER JOIN to the sites table to get the rest of the fields. This is because they will be trying to add all of them into the GROUP BY statement and this fails (or is at least very slow) if you have long text fields.

sorting by value in another table php mysql

I have two tables, one called episodes, and one called score. The episode table has the following columns:
id | number | title | description | type
The score table has the following columns:
id | userId | showId | score
The idea is that users will rate a show. Each time a user rates a show, a new row is created in the score table (or updated if it exists already). When I list the shows, I average all the scores for that show ID and display it next to the show name.
What I need to be able to do is sort the shows based on their average rating. I've looked at joining the tables, but haven't really figured it out.
Thanks
To order the results, use and ORDER BY clause. You can order by generated columns, such as the result of an aggregate function like AVG.
SELECT e.title, AVG(s.score) AS avg_score
FROM episodes AS e
LEFT JOIN scores AS s ON e.id=s.showId
GROUP BY e.id
ORDER BY avg_score DESC;
You're right. You have to JOIN these tables, then use GROUP BY on the 'episodes' table's 'id' column. Then you'll be able to use AVG() function on 'the scores' tables's 'score' column.
SELECT AVG(scores.score) FROM episodes LEFT JOIN scores ON scores.showId = episodes.id GROUP BY episodes.id
SELECT episodes.*, AVG(score.score) as AverageRating FROM episodes
INNER JOIN score ON (episodes.id = score.showId)
GROUP BY episodes.id
ORDER BY AVG(score.score) DESC

Categories