PHP MYSQL - Distinct multiple columns as one - php

I've read a lot of questions and answers related to this topic, but I can't find anything that's working for me, so I could really use some help.
I am trying to make a simple private messaging system for my website. I have a table like this:
private messages: id, to_id, from_id, message, time_sent
I am now trying to show a list on my page with the different users a member interacted with, something like Facebook messages so every name should appear only once in the list.
This is what I have now which shows me every message that I as a user ($my_id) participate in:
SELECT * FROM private_messages WHERE (from_id = '$Sid' AND to_id = '$my_id') OR (from_id = '$my_id' AND to_id = '$Sid') ORDER BY time_sent ASC
This query shows a distincted list, but only of the people that I received a message from:
SELECT DISTINCT(from_id) FROM private_messages WHERE to_id='$my_id' ORDER BY time_sent DESC
How should I rewrite this so it gives me a distinct list of every user that I interacted with (like a list of facebook messages)? I think I need to distinct on two columns, from_id and to_id, but how should I do it?
I know the questions sounds rather vague, but I really can't explain it any better. Thanks in advance!

Try this:
SELECT DISTINCT IF(from_id = '$my_id', to_id, from_id) AS other_id
FROM private_messages
WHERE '$my_id' IN (to_id, from_id)
ORDER BY time_sent DESC

Related

sql - select data from 2 tables

I have 2 tables - chats_topices and chats_replies
I would like to select all chats_topices fields + the field createDate (timestamp) from chats_replies which closest to the current date.
chats_replies contain field CHATID that connect between the tables
$sql = "SELECT *
FROM `chats_topics` AS topics, `chats_replies` AS replies
WHERE (".$search.") AND
(topics.id = replies.chatID) AND
(XXXX)
ORDER BY topics.createDate DESC";
It's bad practice to use SELECT * in live code, I'd go with something like this;
SELECT topics.Field1, topics.Field2, MAX(replies.createDate) createDate
FROM `chats_topics` AS topics
LEFT JOIN `chats_replies` AS replies
ON topics.id = replies.chatID
WHERE (".$search.")
AND (topics.id = replies.chatID)
AND (XXXX)
GROUP BY topics.Field1, topics.Field2
ORDER BY topics.createDate DESC
Add in your fields that you need to the select and also add them in the group by.
Rich Benner gave you a good join statement for this particular problem. I would like to follow up and attach an image I have found useful when making join statements in the hopes that it might help in the future. I don't remember where I got this picture (although you can see a copyright on the bottom).

Get MAX results of pm messages

I'm trying to get the last result of each conversation between two users with no prevail. I've looked at a few examples online such as
[php Mysql Grouping and Ordering user messages together
AND a more advanced query
[GROUP BY messages MySQL
My database structure below.
My conversations rely on getting id's of both message_creator and message_target to link them into one chat.
message_id,
message_content,
message_target,
message_creator,
message_status,
message_time
I need message_status to also select 1 AND 2 in the query so if a user has read the last message it still shows as last message in the conversation.
Here is the query I currently have.
$callmessage=" SELECT message_id,MAX(message_content) AS message_content ,message_target,message_status,message_creator,message_throughurl,MAX(message_time) AS message_time FROM messages WHERE message_target='$user1_id' OR message_creator='$user1_id' AND message_status=1 OR message_status=2
Group By
(if(message_creator > message_target, message_creator,message_target))
,(if(message_creator > message_target, message_target,message_creator))
ORDER BY message_id DESC";
If you want to get the very last message between two users, then this should work:
SELECT *
FROM messages
WHERE (
(message_creator='$user1_id' AND message_target='$user2_id')
OR
(message_creator='$user2_id' AND message_target='$user1_id')
)
AND message_status IN (1,2)
ORDER BY message_id DESC
LIMIT 1
Ok. I've worked it out and its working. This is what I've done with my query, just a few little bits to add.
$user1_id= mysqli_real_escape_string($mysqli,$_SESSION['id']);
$callmessage=" SELECT * FROM messages WHERE message_id IN
(SELECT MAX(message_id) AS message_id FROM
(SELECT message_id, message_creator AS id_with
FROM messages
WHERE message_target = '$user1_id'
UNION ALL
SELECT message_id, message_target AS id_with
FROM messages
WHERE message_creator= '$user1_id') t
GROUP BY id_with)";

messages between users: show conversations

I am coding a message system for user communication. In the inbox I do not want to show the user the messages he/she received. I just want to show the conversations. So as an example, if one user send or receive more than one message then in the inbox there should only be the conversation (which includes the newest message, either written or received) with the user and when the user clicks on the conversation he/she can see all past messages.
The table structure (simplified) of 'messages' is as followed:
message_id
user_id_sender
user_id_recipient
message
Now the problem is that the messages are saved in a database where each row is one message, so I have to group these messages in a certain way.
The select statement I came up with is the following:
SELECT * FROM messages
WHERE user_id_sender = 1 OR user_id_recipient = 1
GROUP BY user_id_sender
But now I obviously get two messages because one which has been written by user '1' and one that he has received..
Does anybody have an idea how to solve this?
I've solved this problem some month ago. I suppose you have also a date field. This query give you a well structured results with date of last message and last message.
$qry = 'SELECT
CONCAT(GREATEST(user_id_sender,user_id_recipient)," ",LEAST(user_id_sender,user_id_recipient)) AS thread,
MAX(CONCAT(date,"|",message)) as date_message,
MAX(date) AS last_message,
messages.*
FROM messages
WHERE user_id_sender= ? || user_id_recipient=? GROUP BY thread ORDER BY last_message DESC';
$rows = $db->fetchAll($qry, Array($current_user_id,$current_user_id));
Assuming message_id is ascending (i.e. higher ids are for later messages). #user_id is just a placeholder for the user_id of the inbox you are looking at. I've used Andrea's trick for getting the other_recipient_id concisely.
Select
mm.other_recipient_id,
m.*
From (
Select
user_id_sender + user_id_recipient - #user_id as other_recipient_id,
Max(message_id) as message_id
From
messages
Where
user_id_sender = #user_id Or
user_id_recipient = #user_id
Group By
user_id_sender + user_id_recipient - #user_id
) mm
Inner Join
messages m
On
mm.message_id = m.message_id
Example fiddle http://sqlfiddle.com/#!2/05d191/3/0
You get only one message, as long as the messages (message_id) are unique. You will get multiple messages, of course, if there is a longer communication going on.
N.B.: You don't need group by here, because this is used only for aggregating columns, e.g.:
SELECT user_id_sender, sum(*) FROM messages
GROUP BY user_id_sender;
where you get the sum of messages each user has sent.
So, if you want to see the communication between two users:
SELECT * FROM messages
WHERE user_id_sender = 1 OR user_id_recipient = 1;
You can restrict this further, if you store the timestamps as well, message_time for example or limit the number of messages displayed:
select * from ... limit 10;
Assuming we want to see conversations of user with certain _ ID _, this query can be useful:
SELECT (user_id_sender + user_id_recipient) - _ID_ AS correspondent, COUNT(*) AS total
FROM messages
WHERE user_id_sender = _ID_ OR user_id_recipient = _ID_
GROUP BY (user_id_sender + user_id_recipient)
The resulting query returns the ID of the other user of the conversation ("correspondent") and the number of messages between the two users ("total").
Regards

How do I structure this kind of sql query

I have the following database table below
I want to display notification to the user with owner_id 1. The notification show tell the owner that two users (with ids 17 and 2) commented on his/her post with post_id 1.
I've tried the following query but it returns 2 rows instead. How can I can structure the query to return one row, because I want the notifications for one post to be returned together? Thank you.
SELECT DISTINCT commenters_id, id, owner_id,
post_id, type, UNIX_TIMESTAMP(date_done) AS date
FROM notification
GROUP BY commenters_id
HAVING owner_id = '$user_id' AND commenters_id != '$user_id'
ORDER BY date_done DESC
This code will not give you the commenters_id, but instead a count of how many people have replied to each post. The date will be the last time someone replied to that specific post:
SELECT
COUNT(DISTINCT commenters_id) AS commenter_count,
owner_id,
post_id,
type,
UNIX_TIMESTAMP(MAX(date_done)) AS date
FROM notification
WHERE owner_id = '$user_id' AND commenters_id != '$user_id'
GROUP BY owner_id, post_id, type
ORDER BY UNIX_TIMESTAMP(MAX(date_done)) DESC
You can use GROUP_CONCAT(commenters_id) to get a comma-separated list of commenter IDs for the post. But then you have to group by post rather than commentor, so the query would look something like:
SELECT DISTINCT GROUP_CONCAT(commenters_id), id, owner_id,
post_id, type, UNIX_TIMESTAMP(date_done) AS date
FROM notification
GROUP BY post_id
HAVING owner_id = '$user_id' AND commenters_id != '$user_id'
ORDER BY date_done DESC
GROUP_CONCAT allows some processing of the data if you need it, like returning only distinct values or sorting the values. See the full documentation here: http://dev.mysql.com/doc/refman/5.5/en/group-by-functions.html#function_group-concat
your problem is that you select the date_done column, too, which makes the rows unique.
Neither DISTINCT nor GROUP BY are allowed to group the result set under this circumstances.
Leave the column out of this query or use something like MAX(date_done)
EDIT:
wait...you already get only two rows, not four ? Isn't that the result that you wanted? maybe I missunderstood the question

PHP, MySQL: How to get the user's first and last name

I got stucked with this query in the past couple of hours, and I badly need someone to help me figure it out. I'm try to finish my private message system, but I lost myself in the database tables. I've created three tables for the system, and they are as follows:
CONVERSATION(**conversation_id**, subject)
CONVERSATION_MEMBER(**conversation_id**, **user_id**, conversation_last_view, conversation_deleted)
CONVERSATION_MESSAGE(**message_id**, conversation_id, user_id, message_date, message_text)
Ok, so in my function to get all conversations of a specific user, I'm fetching the subject, the date, and at last I want to show the user who is he/she talking to. I wrote the following query:
SELECT
c.conversation_id,
c.conversation_subject,
MAX(cmes.message_date) AS conversation_last_reply,
FROM conversation c, conversation_member cmem, conversation_message cmes
WHERE c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
And, at last I'm tryin to get the user's first and last name (the other user in the conversation), but I lost myself on how to do that. I've tried to create another function that will get the conversation id, while looping through the results of the first query, and return the user's first and last name, but it didn't work out.
Btw, for the users I have another table... I guess I don't have to tell you. Ok, thank you.
$sql = "SELECT (user.forename, user.surname, other_fields...)
FROM conversation
INNER JOIN conversation_member
ON conversation.conversation_id = conversation_member.conversation_id
INNER JOIN conversation_message
ON conversation.conversation_id = conversation_message.conversation_id
INNER JOIN users_table /* replace this with the name of your user table */ AS user
ON user.user_id = conversation_member.user_id
WHERE user.user_id = :userid
AND conversation_member.conversation_deleted = 0
GROUP BY conversation.conversation_id;"
$query = $db->prepare($sql);
$query->bindParam(':userid', $userid, PDO::PARAM_INT);
$query->execute();
$results = $query->fetchAll();
$user = $results[0]["user"]; //stores array of user fields (forename, surname, etc)
SELECT
c.conversation_id,
c.conversation_subject,
user.firstname,
user.lastname,
MAX(cmes.message_date) AS conversation_last_reply,
FROM conversation c, conversation_member cmem, conversation_message cmes, user_tablename user
WHERE c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
AND user.user_id_column = whatever.you_used_as_foriegn_key
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
Assuming the columns names are like that
You need to join with the "conversation_member" table again, this time, selecting the other user's id where the same conversation_id and message_id applies:
SELECT
c.conversation_id,
c.conversation_subject,
MAX(cmes.message_date) AS conversation_last_reply,
cmem2.user_id AS other_user_id
CONCAT(usr_tbl.firstname, ' ', usr_tbl.lastname) AS other_user_name
FROM conversation c
JOIN conversation_member cmem
JOIN conversation_message cmes
JOIN conversation_member cmem2
JOIN users_table usr_tbl
ON c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
AND c.conversation_id = cmem2.conversation_id
AND cmem2.user_id = {$_SESSION['other_user_id']}
AND cmem2.conversation_deleted = 0
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
I don't see why you're having trouble getting the user's first and last name if you were able to construct the rest of that query by yourself?
Try something along the lines of, :
SELECT cmem.user_id, u.first_name, u.last_name, c.conversation_id, c.conversation_subject, MAX(cmes.message_date) AS conversation_last_reply
FROM conversation c
INNER JOIN conversation_member cmem on c.conversation_id=cmem.conversation_id
INNER JOIN conversation_message cmes on c.conversation_id=cmes.conversation_id
INNER JOIN users u on u.user_id = cmem.user_id
WHERE cmem.user_id = {$_SESSION['user_id']} AND cmem.conversation_deleted = 0
GROUP BY cmem.user_id, u.first_name, u.last_name, c.conversation_id, c.conversation_subject
Now, I think you should also be reconsidering your database structure so that all these joins are not necessary. I see several problems. One, your database seems to be over-normalized. Why do you have a separate "Conversations" table that has only two fields, conversation id and subject? In any message system I've ever seen, the subject is always visible so you would always have to join the conversation table just to get the subject field. The conversation_id is in every other table anyway. Just add the subject field to the message table and eliminate the conversation table if that's all it's holding, normalization isn't always a good thing.
Second, why do you set a flag for deleted messages instead of just deleting them? I've also never seen a message system that lets me restore messages I've deleted. At the very least, if you want to retain them for whatever reason you should move them to an archive table so that the primary table you're running selects off of doesn't have to deal with the performance hit of parsing through meaningless "deleted" entries.
Lastly, what is the conversation_member table anyway? Based on my interpretation, it's supposed to represent a member of the conversation since it has a user_id. Why would the conversation delete flag be present for a single member of a conversation? If anything it should be in the conversation table. With that improvement, the only field left in it is conversation_last_view, which really no one cares about. The more important thing is conversation_last_post, which can be easily derived from the timestamp of the last message posted in the thread.
Ultimately, if you just want to see the first and last names appended to your query it's as simple as joining the users table and displaying those two entries. The SQL query I provided should get you close if it doesn't work straight out, I'm too lazy to copy your database and try it myself. However, I think you should really consider the overall design of your database as well so you don't run into needless performance issues down the road.
To answer the question of: Finding all users in a conversation, MINUS the current user:
SELECT (users.forename, users.surname)
FROM conversation_members AS members
INNER JOIN users_table AS users
ON members.user_id = users.user_id
WHERE members.conversation_id = :conversationid
AND NOT users.user_id = :userid
Where :userid is the current user and :conversationid is the conversation in question.

Categories