Selecting newest row, one per ID - mysql

I'm trying to select the one row per user ID for a conversation list. I'm trying to show users a list of their active conversations (similar functionality can be shown on Facebook's messages page, it shows all of your conversations with the profile name and the latest message, then you click on them to see the entire chat history), but only displaying the last message from each user they're chatting with. Here's the query I tried using so far to select a single message from each user they're chatting with, but it's showing the oldest message. I tried changing the ORDER by it didn't help, so any help offered is appreciated:
SELECT `conversations`.`from_id`,
`profile`.`username`,
`conversations`.`date`,
`conversations`.`body`
FROM
`website`.`conversations`,
`website`.`profile`
WHERE
`conversations`.`to_id` = ?
AND `profile`.`profile_id` = `conversations`.`from_id`
GROUP BY `profile`.`user_id`
ORDER BY `conversations`.`id` DESC
LIMIT 15
I'm ordering by the conversation id (auto incrementing ID), so I was trying to get the highest ID (which would be the newest record) to be returned but I'm doing something wrong.
Update: Here's the table structure:
CREATE TABLE `conversations` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`to_id` int(10) unsigned NOT NULL,
`from_id` int(10) unsigned NOT NULL,
`body` mediumtext NOT NULL,
`date` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `to_id` (`to_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I've tried multiple queries from other "similar" questions (obviously editing them to make them reflect my table), none of which worked.
Update again: I tried the following query but now it is returning just one single row, and I'm hoping to return a maximum of 15 (LIMIT 15 in my previous query) unique conversations:
SELECT p1.id, p1.body, profile.username
FROM profile, conversations p1 LEFT JOIN conversations p2
ON (p1.to_id = p2.to_id AND p1.id < p2.id)
WHERE p2.id IS NULL AND p1.to_id = *INSERT_USER_ID* AND profile.user_id=p1.from;

Related

Mysql count number of result with join

I have 2 tables.
CREATE TABLE $media_table (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`options` longtext DEFAULT NULL,
`order_id` int(11) unsigned DEFAULT NULL,
`player_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`))
CREATE TABLE $category_table (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`category` varchar(300) DEFAULT NULL,
`media_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`))
I get id, options, category for rows matching category 'foo','bar'. I also use limit to get only x number of results.
SELECT mt.id, mt.options, ct.category
FROM $media_table as mt
LEFT JOIN $category_table as ct
ON mt.id = ct.media_id
WHERE mt.player_id = %d AND ct.category IN ('foo','bar')
GROUP BY ct.media_id
ORDER BY mt.order_id
LIMIT $limit
This works as intended. But I dont know how to get total number of results?
I tried this but the count is not correct.
SELECT COUNT(mt.id), ct.category
FROM $media_table as mt
LEFT JOIN $category_table as ct
ON mt.id = ct.media_id
WHERE mt.player_id = %d AND ct.category IN ('foo','bar')
GROUP BY ct.media_id
Where I select all results without the limit (in my previous query) the count is correct.
If I had only one table with primary key id I would do this to get count:
SELECT COUNT(id) FROM table
I dont know how to apply the same to my query.
Edit: I found my answer here select count(*) from select
Question 1: Are you looking at the raw results of the query using a tool like phpMyAdmin or MySQL WorkBency or what?
Question 2: Will the ultimate query results be delivered to the client via a web browser or what?
Answer 1: "The SUM() function returns the total sum of a numeric column."
SELECT SUM(column_name) FROM table_name WHERE condition;
Answer Possibility 2: If the results will be delivered in a web browser you should be able to use PHP or some other server side language like MS Active Server Pages to add up he "COUNT" field of each result.
Answer Possibility 3: Alternative 1: Export the results to a CVS file and import into a spreadsheet.
Maybe some of these suggestions will get the wheels turning and help you find the solution you are looking for.

MySQL query with count and row

I have a messages table and, I would like to know what would be the most efficient query to accomplish the following result:
Note thread field is null if the thread is the very first message all other messages are linked to that thread with is the emid
CREATE TABLE `messages` (
`emid` BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`emitter` BIGINT(20) NOT NULL,
`receiver` BIGINT(20) NOT NULL,
`thread` BIGINT(20) DEFAULT NULL,
`opened` TINYINT(4) DEFAULT 0,
`message` BLOB NOT NULL,
`timecard` DATETIME DEFAULT CURRENT_TIMESTAMP,
ADD CONSTRAINT `efk` FOREIGN KEY (`emitter`) REFERENCES `members` (`user_id`),
ADD CONSTRAINT `rfk` FOREIGN KEY (`receiver`) REFERENCES `members` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I would like to get the first row for a given receiver including its messages count. Say for instance a user has 4 non opened messages (unread) and two replies. I would like to get the first message received under this thread and the total replies from both sides. At first I thought about sub queries but that seems like it will perform badly.
The following selects threads for user (receiver = id)
SELECT * FROM `messages` WHERE thread IS NULL AND receiver = 2 ORDER BY timecard DESC
This one get the message count under a given thread
SELECT COUNT(*) FROM `messages` WHERE thread = 20
Join your two queries:
SELECT m1.*, IFNULL(COUNT(m2.emid), 0) AS replies
FROM messages AS m1
LEFT JOIN messages AS m2 ON m2.thread = m1.emid
WHERE m1.thread is NULL
GROUP BY m1.emid
The WHERE clause selects just the starting message from each thread from table m1.
LEFT JOIN then relates that to all the replies to that thread, using the thread column to link them back to the original message. I've used LEFT JOIN so that messages with no replies will be included.
COUNT(m2.emid) then counts the number of related rows, which is the number of replies in the thread. COUNT() only counts non-null values; if there are no replies, LEFT JOIN sets this column to NULL, so you'll get a count of 0.

Why is this group by query not working?

I have a query that is not grouping properly and returning the wrong results and I can't figure out what the problem is.
The query is shown below. FYI - It's not obvious in it's current form why I need the group by because I've removed all other parts of the query to get to the most basic form where I see a problem.
SELECT * FROM (
SELECT *
FROM notifications n
WHERE 1
-- and group_id = '5b35c8eb075881f8bbdfbcb36b052aa7'
GROUP BY `from`
) t
WHERE group_id = '5b35c8eb075881f8bbdfbcb36b052aa7'
The problem is that when I use put the where on the inside subquery (currently commented out), for this case, I end up with 4 results. Each of the 4 results have a different "from" value so should be listed separately. When I put the where on the outside of the subquery I end up with 3 results.
For completeness the table definition is:
CREATE TABLE `notifications` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`mem_id` int(10) unsigned DEFAULT NULL,
`type` varchar(255) NOT NULL,
`from` varchar(255) DEFAULT NULL,
`entry_id` int(11) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`table_id` varchar(255) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`emailed` tinyint(1) DEFAULT NULL,
`read` tinyint(1) NOT NULL,
`group_id` char(32) NOT NULL,
PRIMARY KEY (`id`),
KEY `mem_id` (`mem_id`),
KEY `created_at` (`created_at`),
KEY `entry_id` (`entry_id`),
KEY `parent_id` (`parent_id`),
KEY `group_id` (`group_id`)
)
Any ideas what could cause this? I'm completely stumped. At this point I'm ready to attribute it to some bug in mysql but that also seems unlikely.
Update
I wasn't clear by what I meant by "wrong results" There were 7 records in the data set with this group_id. There were 2 records with a unique "from" and 5 more records with 2 other "from" ids (one had 3 records, one had 2).
Doing the where for the group by on the inside resulted in in the 4 records that I wanted. I don't care about which row was selected as the result because I'm doing other sums/counts which I excluded from the example because it wasn't directly relevant to the problem.
If I do the where on the outer group by one of the two records with a single "from" did not return at all.
I'll try to update with a sqlfiddle (didn't know about that!) - the issue is that this database I was testing on is wiped daily so I don't have the original data, I'll see if I can reproduce.
update #2
I noticed that in my questions, I've been referring to inner and outer group by - the group by is always on the inner query it's just where the "where" is. I've tried to adjust the phrasing. Again, it's not immediately obvious why I care about the location of the where - but in my final use case, I need the selection to happen on the outside (I'm building a count of notifications that are read/unread and I need a count both per member and total per message - eg the group_id)
sqlfiddle: http://www.sqlfiddle.com/#!2/7d746/5
screenshot of query with inner where:https://www.evernote.com/shard/s48/sh/e355e96e-e48d-4550-bbaf-ffb18bc0bb9c/08e2454867e00e3a05535303429748f1
screenshot of query with outer where:https://www.evernote.com/shard/s48/sh/60b10427-e417-4196-8b92-7d6d8031d21e/c779bc9c46d23472983ac6fa0d25e42d
With the sqlfiddle I get back 4 results each time! Which leads me more to think it's a server issue. We're running MySQL 5.5.28-29.2 Percona Server (GPL), Release rel29.2, Revision 360
This query:
SELECT *
FROM notifications n
WHERE 1
GROUP BY `from`
is simply wrong in ANSI SQL and on almost all DBMS (oracle, postgres, MS SQL etc.).
It runs on MySql only because of their nonstandard group by extension
See this link: http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html
Hovever they warn about something:
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.
Because of this "feature" your query (select from select * group by) is unpredicable, results are dependent on the order of records in the table.
Take a look at this simple demo: http://www.sqlfiddle.com/#!2/b762e/2
There are two identic tables in this demo, with the same content, the only difference is a physical rows order. And the same queries give completely different results.
---- EDIT how to solve this problem -----
To solve this problem in your query, just add both columns to the GROUP BY clause.
select * FROM (
SELECT * FROM notifications n
GROUP BY `from`, `group_id`
) x
WHERE group_id = 'A';
select * FROM (
SELECT * FROM notifications n
WHERE group_id = 'A'
GROUP BY `from`, `group_id`
) x
Above two queries give always the same results for columns from and group_id, other columns (not included in the GROUP BY clause`) can be random.
Take a look at simple demo --> http://www.sqlfiddle.com/#!2/5d19b/5

Mysql Group By implementation details - which row mysql chooses in a Group By query without operators?

I have a table with multiple rows per "website_id"
CREATE TABLE `MyTable` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`tagCheckResult` int(11) DEFAULT NULL,
`website_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IX_website_id` (`website_id`),
) ENGINE=InnoDB;
I am trying to select the latest entry per website_id
-- This creates a temporary table with the last entry per website_id, and joins it
-- to get the entire row
SELECT *
FROM `WebsiteStatus` ws1
JOIN (
SELECT MAX(id) max_id, website_id FROM `WebsiteStatus`
GROUP BY website_id) ws2
ON ws1.id = ws2.max_id
Now, I know the correct way to get the last row per website_id is as above. My qusetion is - I also tried the following simpler query, at it seemed to return the exact same results as above:
SELECT * FROM `WebsiteStatus`
GROUP BY website_id
ORDER BY website_id DESC
I know that in principle GROUP BY without operators (e.g. MAX), like I do in my 2nd query can return any of the relevant rows ... but in practice it returns the last one. Is there an implementation detail in mysql that guarantees this is always the case?
(Just asking for academic curiosity, I know the 1st query is "more correct").

Challenging MySQL query

I'm working on a blogging app that requires a unique query.
Problem: I need to display one parent post, all it's children posts (up to a certain number before requiring pagination), and up to 5 comments associated with each child post, and the parent.
I wrote this query, but it doesn't work because it will return only 5 comments that belong to the parent post.
SELECT
posts.id, posts.postTypeId, posts.parentId, posts.ownerUserId, posts.body
, users.id AS authorId, users.displayname AS authorDisplayName
, comments.id AS commentId, comments.text AS commentText
, comments.commentOwnerUserId, comments.commentOwnerDisplayName
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN ( SELECT comments.id, comments.postId, comments.text, commenters.id AS commentOwnerUserId, commenters.displayname AS commentOwnerDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
ORDER BY comments.createdat ASC
LIMIT 0,5 ) AS comments ON comments.postid = posts.id
WHERE posts.id = #postId OR posts.parentId = #postId
ORDER BY posts.posttypeid, posts.createdAt
The query returns the parent post, all it's children, and the first 5 comments it encounters, (usually they belong to the parent because we are ordering by postTypeId, and the parent is the first post). If the first post doesn't have 5 comments, it moves on the next post and returns those comments, until the 5 limit is reached.
What I need is to return one parent post and all it's children posts, and up to 5 comments for each child, the parent. I also need the owner data for each post and comment.
UPDATE I'm open to doing this with more than one query if it will scale well. The only condition is that the parent and children posts retrieval happens in the same query.
Any idea how I can write such a query? I included my schema below.
/* Posts table */
CREATE TABLE `posts` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`posttypeid` int(10) NOT NULL,
`parentid` int(10) DEFAULT NULL,
`body` text NOT NULL,
`userid` int(10) NOT NULL,
`createdat` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `parentId` (`parentid`)
KEY `userId` (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=572 DEFAULT CHARSET=utf8
/* Comments table */
CREATE TABLE `comments` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`postid` int(10) NOT NULL,
`userid` int(10) NOT NULL,
`text` text NOT NULL,
`createdat` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `postId` (`postid`),
KEY `userId` (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
/* users table */
CREATE TABLE `users` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`email` varchar(50) NOT NULL,
`displayname` varchar(50) NOT NULL,
`createdat` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8
It sounds like you're looking to store hierarchical data. This isn't so much a hard question as it is a bit of a time-consuming one. I would suggest reading a really good article from a few years ago named Managing Hierarchical Data in MySQL by Mike Hillyer. It's got some really good conceptual suggestions as well as example implementations of the kind of system it sounds like you're designing. Definitely read the "Find the Immediate Subordinates of a Node" section. :)
I assume you will have paging of some sort to restrict the amount of top level posts.
You would also need some summary information on the number of comments, childposts in the posts table or a post summary table.
The comments table would need sequence column by post
1: Get all parent posts, (parentId = 0) & construct an IN clause of postids
2: Get all children posts by passing the postids obtained in 1, order by postid which will help in segregation. Add these posts to the overall IN clause
Get comments by passing the postids from 1 & 2.
Restrict the number of comments by using the number of comments & the sequence column
For ex: join comments & post_summary where post_comment_seq between (noofcommentsforthepost - 5) and noofcommentsforthepost
You can look at in clause performance here Performance of MYSQL "IN"
I've adjusted the other query from Your Previous Question to simply include a WHERE clause on your ParentID. That was the condition I didn't know you were looking for to limit return set. I added where the post ID = the one you want OR the ParentID = the one you want.
By having the ORDER by the POST ID, it will naturally have the originating parent ID in the first position as others would be derived from it sequentially. I think that will solve you again.
I take as granted that every child has exactly one parent. Then, I think this will work:
SELECT p.* <-- post details
, u.* <-- user details
, cc.* <-- comment details
FROM
( ( SELECT parentid AS id
FROM posts
WHERE posts.id = #mypostid <-- the id of the post we want
)
UNION ALL
( SELECT child.id
FROM posts AS parent
JOIN posts AS child
ON child.parentid = parent.id
WHERE parent.id =
( SELECT posts.parentid
FROM posts
WHERE posts.id = #mypostid) <-- the id of the post we want
ORDER BY child.createdat <-- any order you prefer
LIMIT x, 5 <-- 5 children posts
)
) AS pp
JOIN posts p
ON p.id = pp.id
JOIN users
ON users.id = p.userid
JOIN comments cc
ON cc.postid = pp.id
WHERE cc.postid IN
( SELECT c.id
FROM comments c
WHERE c.postid = pp.id
ORDER BY c.createdat <-- any order you prefer
LIMIT y, 5 <-- 5 comments for every post
)
The x,5 should be replaced with 0,5 for first five childen posts and y,5 with 0,5 for first five comments. Then with 5,5 for next five, 10,5 for next five, etc.
UPDATE
Sorry, my mistake. The above gives the error:
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
I'll wrap my head up to work around this :)