I am building a messaging system similar to facebook's (where it displays messages as threads).
My current table design is:
CREATE TABLE IF NOT EXISTS messages (
mid int(11) NOT NULL auto_increment,
subject text NOT NULL,
message text NOT NULL,
fromid varchar(255) NOT NULL default '',
toid varchar(255) NOT NULL default '',
status varchar(255) NOT NULL default '',
date varchar(255) NOT NULL default '',
time varchar(255) NOT NULL,
PRIMARY KEY (mid)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2825 ;`
I am retrieving results with this select statement:
SELECT
IF(messages.toid = '$uid' OR messages.toid = '$uid', messages.fromid, messages.toid) friend1,
messages.message, messages.fromid, messages.toid, messages.date, messages.status, messages.time
FROM messages
WHERE (messages.toid='$uid' OR messages.fromid='$uid')
AND messages.status!='2'
GROUP BY friend1 ASC
ORDER BY messages.time DESC, messages.mid DESC
This gives me the right results except for it displays the first post from a thread, I would like for it to display the most recent post in a thread.
What am I doing wrong?
This is a nice article directly related to your question: http://kristiannielsen.livejournal.com/6745.html
Taking a wild stab at your specific problem, your query would probably need to look something like below (untested!):
SELECT
IF(derived_messages.toid = '$uid', derived_messages.fromid,
derived_messages.toid) friend1,
derived_messages.message, derived_messages.fromid, derived_messages.toid,
derived_messages.date, derived_messages.status, derived_messages.time
FROM
(SELECT *
FROM messages
ORDER BY time desc) derived_messages
WHERE (derived_messages.toid='$uid' OR derived_messages.fromid='$uid')
AND derived_messages.status!='2'
GROUP BY friend1 ASC
ORDER BY derived_messages.time DESC, derived_messages.mid DESC
Btw this clause looked fishy to me (in your original post):
IF(messages.toid = '$uid' OR messages.toid = '$uid',
messages.fromid, messages.toid) friend1
What's the "OR" doing between identical conditions? You can probably skip the second condition, I think.
Related
Before I dive in, This example below is what I hope to achieve.
Meanwhile I Have a database table that is structured like so.
CREATE TABLE `notifications` (
`id` int(11) NOT NULL,
`recipient_id` int(11) NOT NULL,
`sender_id` int(11) NOT NULL,
`unread` tinyint(4) NOT NULL DEFAULT '1',
`type` varchar(255) NOT NULL,
`reference_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
`recipient_id` - Notification Reciever
`sender_id` - Notification Sender
`unread` - Mark if notificaton has been read or not
`type` - Holds types of notification, Comment, Likes etc
`reference_id` - the reference like a post id
`created_at` - Time noti was created.
and a PHP to fetch data from the database table.
$query = $this->database->query("SELECT recipient_id, unread, type, reference_id, post_title, post_name, COUNT(reference_id)
FROM notifications n INNER JOIN posts p ON p.id = n.reference_id WHERE n.recipient_id = $user AND n.type = 'post_comment' AND n.unread = 1
GROUP BY n.reference_id HAVING COUNT(n.reference_id) >= 1 ORDER BY unread DESC LIMIT 8")->fetchAll();
return $query;
I'm grouping the results I get by the reference_id if its >= 1
this reason is so I don't get duplicate notification that has the same reference_id.
with this query so far I am able to get the data from the database table and display like so.
Someone commented on your post "I love to code"
but I want to display to the user like the example above or like this below.
James, John and others commented on your post "I Love to Code"
thats if there is more than 1 or 2 sender_id with the same reference_id
this is where I am stuck and don't know which other step to take, please any help I can get is appreciated.
thanks
I am selecting the last message between two users of a chat system. However, though I am able to select it well, I am not able to order the message according to message that have not been read.
This is what my chat table looks like
CREATE TABLE IF NOT EXISTS `mychat_table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`from_id` int(10) unsigned NOT NULL,
`to_id` int(10) unsigned NOT NULL,
`message` text NOT NULL,
`sent` int(10) unsigned NOT NULL DEFAULT '0',
`read_statu` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_deleted` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `to` (`to_id`),
KEY `from` (`from_id`),
KEY `direction` (`is_deleted`),
KEY `read` (`read_statu`),
KEY `sent` (`sent`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=58 ;
Now to select the last message between the two users I did this
$recup_id = mysqli_query($connection, "SELECT MAX(`id`) AS id FROM mychat_table WHERE (`from_id`='$id' OR `to_id`='$id') AND
(is_deleted!='$id' AND is_deleted >=0) ORDER BY read_statu ASC GROUP BY (IF(`to_id`='$id', `from_id`, `to_id`)) LIMIT 5 ") or die(mysqli_error($connection));
Problem
Normally I thought when I do ORDER BY read_statu ASC it will display the message with the status 0 first and so on and so forth but it does not.
How to display the messages between those users by ordering in such a way that it will first display messages which read_statu=0 before displaying those which read_statu are superior to 0 ?
You are right, that ORDER BY read_status ASC will give you a list ordered by read status, BUT
you can not do the ORDER BY before the GROUP BY clause
you are using the MAX(id) function, this will return the largest id of the selected group no matter what order you used
you are using GROUP BY which will reduce the result to just one entry per user so you will not be able to access ALL last five messages
To actually get the result you want, you could simplify your query in order to display the last five messages giving unread ones precedence:
SELECT * FROM mychat_table
WHERE (`from_id`='$id' OR `to_id`='$id') AND (is_deleted!='$id' AND is_deleted >=0)
ORDER BY read_statu ASC, id DESC
LIMIT 5
If you want the result tidy for display you could use this
SELECT (IF(`to_id`='$id', `from_id`, `to_id`)) AS contact, message, read_statu
FROM mychat_table
WHERE (`from_id`='$id' OR `to_id`='$id') AND (is_deleted!='$id' AND is_deleted >=0)
ORDER BY read_statu ASC, id DESC, contact
LIMIT 5
if you need preference to the grouping and not the read_status, then just place contact at the beginning of the ORDER BY clause:
SELECT (IF(`to_id`='$id', `from_id`, `to_id`)) AS contact, message, read_statu
FROM mychat_table
WHERE (`from_id`='$id' OR `to_id`='$id') AND (is_deleted!='$id' AND is_deleted >=0)
ORDER BY contact, read_statu ASC, id DESC
LIMIT 5
I don't have your data to play around with, so please try the order parameter in different order until you have the desired result, this should get you there.
If you just want to see the last message (preferably unread) from each user (only one message per user) and you can live with just having user id and message text (which should be enough for display) then try this:
SELECT DISTINCT from_id, (
SELECT message FROM mychat_table m2
WHERE to_id='$id' AND m2.from_id=m1.from_id
ORDER BY read_statu ASC, id DESC
LIMIT 1
) AS message FROM mychat_table m1
WHERE to_id='$id'
LIMIT 5
I started developing chat application for my website.
First I did some javascript part, before I got to backend.
And Now just created database structure:
CREATE TABLE IF NOT EXISTS `wp_bp_my_chat` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`from` varchar(255) NOT NULL DEFAULT '',
`to` varchar(255) NOT NULL DEFAULT '',
`message` text NOT NULL,
`sent` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`recd` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `to` (`to`),
KEY `from` (`from`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Now, having this databse, I want to make a request to see all messages grouped by "from" OR "To"
Think of it as facebook messages, when you go to actual page, there is a left sidebar with messages grouped by conversation.
the output should be like:
conversation between "user_1" and "user_2" (unread) 2 hours ago
conversation between "user_1" and "user_3" (unread) 3 hours ago
converstation between "user_1" and "user_5" 5 hours ago
so my messages are grouped like conversations.
I might have 10 message from user_2 but it should be displayed as one (and info from last one)
Any Ideas how I go next?
As I have not done any php side yet You can even suggest changing database to adjast for your solution.
Thanks.
I assume you would run this for one person ('user_1') for their conversations, which means they can be either the from or the to. I also assume that it make no difference if they are the from or the to, but to group by the other person in the conversation. If so, try this. (You should put some sample data in SQLFiddle for testing)
SELECT MostRecent.MainPerson AS MainPerson
, MostRecent.OtherPerson AS OtherPerson
, MostRecent.Sent AS Sent
, IF(wp_bp_my_chat.recd = 0, 'Unread','Read') AS Status
FROM wp_bp_my_chat
JOIN (
SELECT 'user_1' AS MainPerson
, IF(msgs.`from` = 'user_1',msgs.to, msgs.`from`) AS OtherPerson
, MAX(msgs.sent) AS sent
FROM wp_bp_my_chat AS msgs
WHERE msgs.`from` = 'user_1' OR msgs.`to` = 'users_1'
GROUP BY MainPerson, OtherPerson) AS MostRecent
ON (wp_bp_my_chat.`from` = MostRecent.MainPerson OR wp_bp_my_chat.`to` = MostRecent.MainPerson)
AND (wp_bp_my_chat.`from` = MostRecent.OtherPerson OR wp_bp_my_chat.`to` = MostRecent.OtherPerson)
AND MostRecent.sent = wp_bp_my_chat.sent
ORDER BY sent DESC
To get the results you described in your update, use:
SELECT count(*), max(sent)
FROM wp_bp_my_chat
WHERE to = 'name of recipient'
GROUP BY from
count(*) gives you the number of messages and max(sent) gives you the time of the latest message, which you can use to calculate the "hours ago" part of the output.
I don't see a flag in your table for whether the message has been read. You'd need to add that in order to add the "(unread)" text.
This can be done with a simple group by query:
SELECT * FROM `wp_bp_my_chat` WHERE `to` = {my_id} GROUP BY `from`
This will give you all the chats that I as a user have had.
I am currently working on a messaging system for one of my project. I am using single table to store messages and replies(i.e with parent_id).
Here is the table structure:
CREATE TABLE IF NOT EXISTS `messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` int(10) unsigned NULL,
`sender_id` int(10) unsigned NOT NULL,
`receiver_id` int(10) unsigned NOT NULL,
`who` enum('bride','member') NOT NULL,
`subject` varchar(200) NOT NULL,
`body` text NOT NULL,
`sent_date` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ;
Where parent_id is the id of the message to which we replied. if parent_id=NULL then its a new message
Where who is the who sent that message- i.e bride or members. I have two separate tables for bride & member details.
Now I want a query to list messages in INBOX(just like GMAIL- bunch of conversations[messages+replies to that]).
Can you please help me to build the query?
This is what I was working on :
SELECT id
, parent_id
, sender_id
, receiver_id
, who
, subject
, body
, sent_date
FROM `messages`
WHERE sent_date IN ( SELECT MAX( sent_date ) FROM `messages` WHERE receiver_id = 1 GROUP BY sender_id )
AND who = 'bride'
AND receiver_id = 1
ORDER
BY id DESC
LIMIT 0, 8
This will give you all top-level messages (ie. not a reply) sent to a person identified with ID 1, as well as all direct replies to these messages (1 level deep, no recursion):
SELECT
messages.*,
replies.*
FROM messages
LEFT JOIN messages AS replies ON (replies.parent_id = messages.id)
WHERE messages.receiver_id = 1
ORDER BY id messages.sent_date, replies.sent_date
Now, from your sample code, it looks like you only want the latest message (and their replies), then:
SELECT
lastMessage.*,
replies.*
FROM (
SELECT * FROM messages ORDER BY sent_date
WHERE messages.receiver_id = 1
LIMIT 1
) AS lastMessage
LEFT JOIN messages AS replies ON (replies.parent_id = lastMessage.id)
ORDER BY id replies.sent_date
I do not understand the condition who = 'bride' though.
I have a seemingly simple task but I cannot seem to find an elegant solution using 1 query...
Problem:
I have a table of recorded 'clicks' on 'posts', where each post is part of a 'category'.
I want to find the 16 highest clicked posts in the last 30 days -- but I want to avoid duplicate categories.
It seems very simple actually, but I seem to be stuck.
I know how to get the most clicked in last 30, but I can't figure out how to avoid duplicate cats.
SELECT cat_id,
post_id,
COUNT(post_id) AS click_counter
FROM cs_coupon_clicks
WHERE time_of_click > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY post_id
ORDER BY click_counter DESC
I tried to get creative/hacky with it... it's close but not correct:
SELECT cat_id,
Max(sort) AS sortid
FROM (SELECT cat_id,
post_id,
COUNT(post_id) AS click_counter,
CONCAT(COUNT(post_id), '-', post_id) AS sort
FROM cs_coupon_clicks
WHERE time_of_click > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY cat_id, post_id) t1
GROUP BY cat_id
ORDER BY cat_id ASC
Any help would be greatly appreciated as I am not really a MySQL expert. I may end up just doing some PHP logic in the end, but I am very curious as to the correct way to approach a problem like this.
Thanks guys.
EDIT (structure):
CREATE TABLE `cs_coupon_clicks` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`src` varchar(255) NOT NULL DEFAULT '',
`cat_id` int(20) NOT NULL,
`post_id` int(20) NOT NULL,
`tag_id` int(20) NOT NULL,
`user_id` int(20) DEFAULT NULL,
`ip_address` char(30) DEFAULT NULL,
`referer` varchar(255) NOT NULL,
`browser` varchar(10) DEFAULT NULL,
`server_var` text NOT NULL,
`time_of_click` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `cat_id` (`cat_id`),
KEY `post_id` (`post_id`),
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
TEMP WORKING SOLUTION (HACKY):
SELECT
cat_id,
MAX(sort) AS sortid
FROM (
SELECT
cat_id,
post_id,
COUNT(post_id) AS click_counter,
RIGHT(Concat('00000000', COUNT(post_id), '-', post_id), 16) AS SORT
FROM cs_coupon_clicks
WHERE time_of_click > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY cat_id, post_id
) AS t1
GROUP BY cat_id
ORDER BY sortid DESC
There is no easy single query solution to this problem, it's a group-wise maximum kind of problem based on a temporary table (the one with counts) that would require self-joins.
Assuming your database grows big enough (otherwise just go for your php logic) I would go for a statistics table, holding info about categories, posts and click counts:
CREATE TABLE `click_cnts` (
`cat_id` int(20) NOT NULL,
`post_id` int(20) NOT NULL,
`clicks` int(20) NOT NULL,
PRIMARY KEY (`cat_id`,`post_id`),
KEY `cat_id` (`cat_id`,`clicks`)
)
and fill it using the same query as the first one in the question:
INSERT INTO click_cnts(cat_id, post_id, clicks)
SELECT cat_id, post_id, COUNT(post_id) AS click_counter
FROM cs_coupon_clicks
WHERE time_of_click > NOW() - INTERVAL 30 DAY
GROUP BY cat_id,post_id
You could update this table using triggers or running update query periodically (do users really need info up to the very last second? probably not...) and save a lot of processing as finding most clicks for each category on indexed table requires a lot less time using a classic group-wise max approach:
SELECT cg.cat_id, cu.post_id, cg.most_clicks
FROM
( SELECT cat_id, max(clicks) as most_clicks FROM click_cnts
GROUP BY cat_id ) cg
JOIN click_cnts cu
ON cg.cat_id = cu.cat_id
AND cu.post_id = ( SELECT cc.post_id FROM click_cnts cc
WHERE cc.cat_id = cg.cat_id
AND cc.clicks = cg.most_clicks
LIMIT 1 )
ORDER BY cg.most_clicks DESC
LIMIT 16
Shot in the dark here. Did you try Select DISTINCT cat_id