MySQL GROUP By and ORDER BY conflict - php

So i'm trying to select some contacts (TABLE2) based on their HISTORY (TABLE1).
So I am testing with this SQL Query:
SELECT
contacts_history.userId,
contacts_history.contactId,
contacts_history.dateAdded,
contacts.firstName
FROM
contacts_history
INNER JOIN contacts ON contacts_history.contactId = contacts.contactId
AND contacts_history.userId = contacts.userId
GROUP BY
contacts.contactId
ORDER BY
contacts_history.dateAdded DESC
LIMIT 0, 10
But i'm noticing that i'm not getting the most 'recent' values.
If I remove the GROUP BY I get totally different DATE RANGES.
I have tried using DISTINCT, but no decent response either.
Take a look at the comparison table results.
I'm having a hard time getting the most recent items, without having duplicate contactId's in there.
Any help would be greatly appreciated... i'm sure this is super basic, just not sure why it's not working the best.

GROUP BY is for use with aggregate methods. Without something like MAX(contacts_history.dateAdded) it doesn't make any sense to use GROUP BY
I think what you want is along the lines:
SELECT
contacts_history.userId,
contacts_history.contactId,
MAX(contacts_history.dateAdded) AS last_date,
contacts.firstName
FROM
contacts_history
INNER JOIN contacts ON contacts_history.contactId = contacts.contactId
AND contacts_history.userId = contacts.userId
GROUP BY
contacts_history.userId,
contacts.contactId
contacts.firstName
ORDER BY
last_date DESC
LIMIT 0, 10
This should give you (I haven't tested it) one line for each user and contact, sorted by the date the contact was added.

Related

ORDER BY with two columns

I have two tables in the mysql database:
trips(id,name,desc,createdat)
trip_ratings(id,ratingvalue,tripid,userid)
I want to get 100 most recent trips sorted by higher average ratings.
I have tried following mysql query:
SELECT AVG(ratingvalue),tripid,tripcreatedat FROM trip_ratings
INNER JOIN trips on trip_ratings.tripid = trips.id
GROUP BY trip_ratings.tripid
ORDER BY AVG(ratingvalue) DESC, tripcreatedat DESC
LIMIT 100
But as it sorts by rating value first I only get trips sorted by higher ratings.
Is it possible within a single query? can anyone hint me what should I do?
EDIT: example:
I have data in trip_ratings table like this:
and from my tried query I can get results like this.
But my problem is: Get 100 most recent trips sorted by higher average ratings.
Instead of downvoting and close votes, can anyone have any solution for that or can anyone give me a hint that is it possible within a single query? thanks.
One option is to use an inline view, to first get the "100 most recent trips".
Then join that to trip_ratings, to calculate the "average rating" and order by that result.
SELECT m.id AS tripid
, AVG(r.ratingvalue) AS average_rating
, m.tripcreatedat
FROM ( SELECT t.id
, t.tripcreatedat
FROM trips t
ORDER BY t.tripcreatedat DESC
LIMIT 100
) m
LEFT
JOIN trip_ratings r
ON r.tripid = m.id
GROUP BY m.id, m.tripcreatedat
ORDER BY AVG(r.ratingvalue) DESC
If there are multiple trips that have the same average rating, it's indeterminiate what order those will be returned in. You can add other expressions to the order by to make it more deterministic.
ORDER BY AVG(r.ratingvalue) DESC, m.tripcreatedat DESC, m.id DESC
This isn't the only way to do it. There are other approaches that will achieve an equivalent result.

Ordering By Another Table Column If Exists

I'm putting together a simple forum script with topics/posts. I'm trying to order topics by latest post and I've had some luck with the answer in the following question: MYSQL Order from another Table, but the problem I have is that some topics don't have any posts yet, so the query doesn't select those topics at all.
Current coding is:
SELECT DISTINCT forum.*
FROM forum
INNER JOIN forum_posts
ON forum.id = forum_posts.parent_id
GROUP BY forum.id
ORDER BY forum_posts.parent_id DESC,
forum_posts.time_created DESC
LIMIT ?,?
I want to tell the ORDER BY to order by forum.time_created if there are no matches in forum_posts.parent_id for a topic.
On a side note, I would also like to know how to put a WHERE clause into this query as well. I want to only get rows from forum table "WHERE access <= ?", but can't work out where to put that snippet in.
Any help much appreciated!
EDIT:
Target is to return topics (from forum table)
According to following details:
WHERE forum.access <= ?
LIMIT ?,?
ORDER BY
Latest Post From forum_posts table with forum_posts.parent_id matching on forum.id,
or forum.time_created
EDIT 2:
An example SQLfiddle with relevant data. This doesn't work as the order should really come out as 11,10,9,1,2
http://sqlfiddle.com/#!2/83535/2
Have a look at this: http://sqlfiddle.com/#!2/be909/1
Here's my final query:
SELECT forum.*, recent_forum_posts.time_created as latest_post
FROM forum
LEFT JOIN (SELECT MAX(forum_posts.time_created) as time_created, forum_posts.parent_id
FROM forum_posts
GROUP BY forum_posts.parent_id) AS recent_forum_posts
ON forum.id = recent_forum_posts.parent_id
WHERE forum.access > 2
ORDER BY recent_forum_posts.time_created IS NULL DESC,
recent_forum_posts.time_created DESC,
forum.time_created DESC
LIMIT 5
Essentially, the subquery (the bit in brackets after LEFT JOIN) selects the most recent post for each parent_id from the forum_posts table.
That is then LEFT JOINed (as we want to list all forum's even if there are no posts yet).

How can I get this database to order before the GROUP BY [duplicate]

This question already has answers here:
MySQL Order before Group by
(10 answers)
Closed 9 years ago.
I made a website for golf scorecards. The page I am working on is the players profile. When you access a players profile, it shows each course in order of last played (DESC). Except, the order of last played is jumbled due to the ORDER BY command below. Instead, when it GROUPs, it takes the earliest date, rather than the most recent.
After the grouping is done, it correctly shows them in order (DESC)... just the wrong order due to the courses grouping by date_of_game ASC, rather than DESC. Hope this isn't too confusing.. Thank you.
$query_patrol321 = "SELECT t1.*,t2.* FROM games t1 LEFT JOIN scorecards t2 ON t1.game_id=t2.game_id WHERE t2.player_id='$player_id' GROUP BY t1.course_id ORDER BY t1.date_of_game DESC";
$result_patrol321 = mysql_query($query_patrol321) or die ("<br /><br />There's an error in the MySQL-query: ".mysql_error());
while ($row_patrol321 = mysql_fetch_array($result_patrol321)) {
$player_id_rank = $row_patrol321["player_id"];
$course_id = $row_patrol321["course_id"];
$game_id = $row_patrol321["game_id"];
$top_score = $row_patrol321["total_score"];
Try to remove the GROUP BY-clause from the query. You should use GROUP BY only when you have both normal columns and aggregate functions (min, max, sum, avg, count) in your SELECT. You have just normal columns.
The fact that it shows the grouping result in ASC order is a coincidence because that is the order of their insertion. In contrast to other RDBMS like MS SQL Server, MySQL allows you to add non-aggregated columns to a GROUPed query. This non-standard behavior creates the confusion you're seeing. If this were not MySQL, you'd need to define the aggregation for all your selected columns given the grouping.
MySQL's behavior is (I believe) to take the first row matching the the GROUP for non-aggregated columns. I would advise against doing this.
Even though you're aggregating, you're not ORDERing by the aggregated column.
So What you want to do is ORDER BY the MAX date DESC
In this way, you are ordering by the latest date per course (your grouping criteria).
SELECT
t1.* -- It would be better if you actually listed the aggregations you wanted
,t2.* -- Which columns do you really want?
FROM
games t1
LEFT JOIN
scorecards t2
ON t2.[game_id] =t1[.game_id]
WHERE
t2.[player_id]='$player_id'
GROUP BY
t1.[course_id]
ORDER BY
MAX(t1.[date_of_game]) DESC
If you want the maximum date, then insert logic to get it. Don't depend on the ordering of columns or on undocumented MySQL features. MySQL explicitly discourages the use of non-aggregated columns in the group by when the values are not identical:
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. (see [here][1])
How do you do what you want? The following query finds the most recent date on each course and just uses that -- and no group by:
SELECT t1.*, t2.*
FROM games t1 LEFT JOIN
scorecards t2
ON t1.game_id=t2.game_id
WHERE t2.player_id='$player_id' and
t1.date_of_game in (select MAX(date_of_game)
from games g join
scorecards ss
on g.game_id = ss.game_id and
ss.player_id = '$player_id'
where t1.course_id = g.course_id
)
GROUP BY t1.course_id
ORDER BY t1.date_of_game DESC
If game_id is auto incrementing, you can use that instead of date_of_game. This is particularly important if two games can be on the same course on the same date.

PHP MYSQL search based on rating and timestamp

My search query runs like:
select * from posts p where p.post like '%test%' ORDER BY p.upvotes DESC,
p.unix_timestamp DESC LIMIT 20
If there are more than 20 results for the searched keyword, i find out the minimum timestamp value, store it in a hidden element and run another query to Load More results like:
select * from posts p where p.post like '%test%' and p.unix_timestamp < 1360662045
ORDER BY p.upvotes DESC, p.unix_timestamp DESC LIMIT 20
Whats really happening is that my first query is ignoring (Obviously, my mistake) posts which haven't had any votes(meaning 0 votes) because of my ORDER BY p.upvotes DESC and as a result of this, i noticed that it fetched the first post in the table in the first 20 results, so the minimum timestamp becomes first post's timestamp. Now after this, if i try to fetch the next 20 results which is less than the minimum timestamp, it doesn't give anything.
Right now, i am simply using the upvotes ordering to fetch top records. Should i be using some algorithm like Bayesian Average or some other algorithm?
Please advise how i can improve the queries if i had to stay with current system of ordering or is there any viable and more efficient method i should be using?
P.S. If possible, please refer some resources about the Bayesian Average(it seems to be most used) or some other alternative?
Storing the timestamp when you first do a search and then using that for the next query you could use something like this:-
$sql = "SELECT *
FROM posts p
LEFT OUTER JOIN (SELECT post_id, COUNT(*) FROM post_ratings WHERE timestamp_rated <= $SomeTimeStoredBetweenPages GROUP BY post_id) pr ON p.id = pr.post_id
WHERE p.post like '%test%'
ORDER BY pr.post_ratings DESC, p.unix_timestamp
DESC LIMIT ".(($PageNo - 1) * 20)." 20";
This is very much an example as I have no real idea of you table structures. Also not sure if you just have a row for each up vote, or whether there are down votes to take account of as well.

MySQL LEFT JOIN, INNER JOIN etc, complicated query, PHP + MySQL for a forum

So I've got a little forum I'm trying to get data for, there are 4 tables, forum, forum_posts, forum_threads and users. What i'm trying to do is to get the latest post for each forum and giving the user a sneak peek of that post, i want to get the number of posts and number of threads in each forum aswell. Also, i want to do this in one query. So here's what i came up with:
SELECT lfx_forum_posts.*, lfx_forum.*, COUNT(lfx_forum_posts.pid) as posts_count,
lfx_users.username,
lfx_users.uid,
lfx_forum_threads.tid, lfx_forum_threads.parent_forum as t_parent,
lfx_forum_threads.text as t_text, COUNT(lfx_forum_threads.tid) as thread_count
FROM
lfx_forum
LEFT JOIN
(lfx_forum_threads
INNER JOIN
(lfx_forum_posts
INNER JOIN lfx_users
ON lfx_users.uid = lfx_forum_posts.author)
ON lfx_forum_threads.tid = lfx_forum_posts.parent_thread AND lfx_forum_posts.pid =
(SELECT MAX(lfx_forum_posts.pid)
FROM lfx_forum_posts
WHERE lfx_forum_posts.parent_forum = lfx_forum.fid
GROUP BY lfx_forum_posts.parent_forum)
)
ON lfx_forum.fid = lfx_forum_posts.parent_forum
GROUP BY lfx_forum.fid
ORDER BY lfx_forum.fid ASC
This get the latest post in each forum and gives me a sneakpeek of it, the problem is that
lfx_forum_posts.pid =
(SELECT MAX(lfx_forum_posts.pid)
FROM lfx_forum_posts
WHERE lfx_forum_posts.parent_forum = lfx_forum.fid
GROUP BY lfx_forum_posts.parent_forum)
Makes my COUNT(lfx_forum_posts.pid) go to one (aswell as the COUNT(lfx_forum_threads.tid) which isn't how i would like it to work. My question is: is there some somewhat easy way to make it show the correct number and at the same time fetch the correct post info (the latest one that is)?
If something is unclear please tell and i'll try to explain my issue further, it's my first time posting something here.
Hard to get an overview of the structure of your tables with only one big query like that.
Have you considered making a view to make it easier and faster to run the query?
Why do you have to keep it in one query? Personally I find that you can often gain both performance and code-readability by splitting overly complicated queries into more parts.
But hard to get an overview so can't really give a good answer to your question:)
Just add num_posts column to your table. Don't count posts with COUNT().
Can we get some...
Show Tables;
Desc Table lfx_forum_posts;
Desc Table lfx_forum_threads;
Desc Table lfx_forum_users;
Desc Table lfx_forum;
Here's some pseudo code
select f.*, (select count(*) from forum_posts fp where fp.forum_id = f.id) as num_posts,
(select count(*) from forum_threads ft where ft.forum_id = f.id) as num_threads,
(select max(fp.id) from forum_posts fp
where fp.id = f.id
) as latest_post_id,
from forums f;
Then go on to use latest_post_id in a seperate query to get it's information.
if it doesn't like using f before it's declared then make a temporary table for this then you update every time the query is ran.

Categories