Weird issue with a sql query [duplicate] - mysql

I am creating an SQL query in Hibernate for a messaging component. The idea is that I am querying to get conversations for a user, sorted on the date of the last message sent.
I have two tables:
conversations
messages
In my select query I am attempting something like this but the ordering never happens on the last message sent.
String sql =
"SELECT * FROM conversations " +
"JOIN messages ON messages.conversation_id = conversations.id "+
"WHERE (conversations.creator_id = :userId OR conversations.to_id = :userId)" +
"GROUP BY messages.conversation_id "+
"ORDER BY messages.created DESC";

The issue is due to a MySQL-specific extension to the behavior of the GROUP BY clause. Other databases would throw an error... something akin to on-aggregate in SELECT list". (We can get MySQL to throw a similar error, if we include ONLY_FULL_GROUP_BY in sql_mode.)
The issue with the expression messages.created is that refers to a value from an indeterminate row in the GROUP BY. The ORDER BY operation occurs much later in the processing, after the GROUP BY operation.
To get the "latest" created for each group, use an aggregate expression MAX(messages.created).
To get the other values from that same row, is a little more complicated.
Assuming that created is unique within a given conversation_id group (or, if there's no guaranteed that it's not unique, and you are okay with returning multiple rows with the same value for created...
To get the latest created for each conversation_id
SELECT lm.conversation_id
, MAX(lm.created) AS created
FROM conversation lc
JOIN message lm
ON lm.conversation_id = lc.id
WHERE (lc.creator_id = :userId OR lc.to_id = :userId)
GROUP BY lm.conversation_id
You can use that as an inline view, to get the whole row with that latest created
SELECT c.*
, m.*
FROM ( SELECT lm.conversation_id
, MAX(lm.created) AS created
FROM conversation lc
JOIN message lm
ON lm.conversation_id = lc.id
WHERE (lc.creator_id = :userId OR lc.to_id = :userId)
GROUP BY lm.conversation_id
) l
JOIN conversation c
ON c.id = l.conversation_id
JOIN messages m
ON m.conversation_id = l.conversation_id
AND m.created = l.created
WHERE (c.creator_id = :userId OR c.to_id = :userId)
NOTES:
You can add an ORDER BY clause to order the rows returned however you need.
The WHERE clause on the outer query is likely redundant, and unnecessary.
We prefer to avoid using SELECT *, and prefer to explicitly list the expressions to be returned.

Related

MySQL PDO - latest row for table grouped with joins

I apologize for the title description as I wasn't sure how to best describe this.
There are multiple computers, multiple users per computer, and multiple data logs per user. I want to get the latest data log per computer.
I realize now that the query below does not provide the correct data because the ORDER BY happens for the results and the GROUP BY does so in no particular order.
I did read up on this before posting this and it appears a sub query is needed and then the result of that joined again on the same table to get the rest of that rows column values (the one found to be the latest). The examples I have found though are pretty basic and do not involve other tables being joined. It also seems I have to do this twice since I want it grouped by computer... (once for data_logs grouped by user_id and get the max then another grouped by computer_id to get the max of the earlier result) or maybe I am wrong there.
Need some help grasping how to tackle this and the approach to do so.
$stmt = $db->prepare("
SELECT
computers.computer_name,
users.username,
data_logs.window_title,
data_logs.filename,
data_logs.capture_timestamp
FROM computers
INNER JOIN users
ON users.computer_id = computers.computer_id
INNER JOIN data_logs
ON data_logs.user_id = users.user_id AND data_logs.marked != 1
WHERE computers.account_id = :cw_account_id AND computers.status = 1
GROUP BY computers.computer_id
ORDER BY data_logs.capture_timestamp desc
");
$binding = array('cw_account_id' => 1721);
$stmt->execute($binding);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<pre>";
print_r($results);
echo "</pre>";
One solution is to use a correlated subquery. The principle is that the outer query is not aggregated, but it has a condition in the WHERE clause that selects the latest log entry for the current computer id, using an aggregated subquery.
I guess that you would need this query (without seeing sample data and expected output though, this might not be 100% accurate) :
SELECT
computers.computer_name,
users.username,
data_logs.window_title,
data_logs.filename,
data_logs.capture_timestamp
FROM computers
INNER JOIN users
ON users.computer_id = computers.computer_id
INNER JOIN data_logs
ON data_logs.user_id = users.user_id AND data_logs.marked != 1
WHERE
computers.account_id = :cw_account_id
AND computers.status = 1
AND data_logs.capture_timestamp = (
SELECT MAX(d.capture_timestamp)
FROM computers c
INNER JOIN users u ON u.computer_id = c.computer_id
INNER JOIN data_logs d ON d.user_id = u.user_id AND d.marked != 1
WHERE c.computer_id = computers.computer_id AND c.account_id = computers.account_id
)
ORDER BY data_logs.capture_timestamp desc

Working with SELECT and SUB SELECT in MySQL

I have a question about a SQL, I have never worked with the select sub and I ended up getting lost with it.
Meu SQL:
SELECT CLI.id, CLI.nome, CLI.senha, CLI.email, CLI.cpf, CLI.celular, CLI.data_nasc, CLI.genero, CLI.data_cadastro, CLI.status, CLI.id_socket, ATEN.mensagem, ARQ.nome AS foto, ATEN.data_mensagem
FROM ut_clientes AS CLI
LEFT JOIN ut_arquivos AS ARQ ON (ARQ.id_tipo = CLI.id AND ARQ.tipo = "ut_clientes")
INNER JOIN ut_atendimentos AS ATEN ON (ATEN.id_usuario_envio = CLI.id)
WHERE ATEN.id_usuario_envio != 59163
GROUP BY CLI.id
ORDER BY ATEN.data_mensagem
DESC
Well, what I would like to do is group the messages according to the customer ID and bring only the last message recorded in the database according to the data_mensagem.
I have tried in many ways but always the last one that is displayed is the first message inserted in DB.
If anyone can help me, I'll be grateful. Thank you guys!
This may help you... I am using a join to a pre-query (PQ alias). This query just goes to your messages and grabs the client ID and the most recent based on the MAX(). By doing the group by here, it will at most return 1 record per client. I also have the WHERE clause to exclude the one ID you listed.
From THAT result, you do a simple join to the rest of your query.
SELECT
CLI.id,
CLI.nome,
CLI.senha,
CLI.email,
CLI.cpf,
CLI.celular,
CLI.data_nasc,
CLI.genero,
CLI.data_cadastro,
CLI.status,
CLI.id_socket,
ATEN.mensagem,
ARQ.nome AS foto,
PQ.data_mensagem
FROM
ut_clientes AS CLI
LEFT JOIN ut_arquivos AS ARQ
ON CLI.id = ARQ.id_tipo
AND ARQ.tipo = "ut_clientes"
INNER JOIN
( select
ATEN.id_usuario_envio,
MAX( ATEN.data_mensagem ) as MostRecentMsg
from
ut_atendimentos AS ATEN
where
ATEN.id_usuario_envio != 59163
group by
ATEN.id_usuario_envio ) PQ
ON CLI.id = PQ.id_usuario_envio
GROUP BY
CLI.id
ORDER BY
PQ.data_mensagem DESC

Get unread count with join

I have a problem getting this query to work, so the basic idea is:
I have messages table, I want to track whether user has read a message or not. Note: multiple users can receive same message, so simply adding column read to message is not an option
Each message is in a thread (has a column thread_id)
I have another table user_read_message which adds record whenever somebody reads a message (user_id, message_id, read)
I want to get number of unread messages for a user in a specific thread. I was trying something along these lines but I couldn't get it to work:
SELECT m.thread_id, urm.user_id, urm.read
FROM sup_messages as m
LEFT OUTER JOIN user_read_message as urm ON m.id = urm.message_id
WHERE m.thread_id = 76852 AND urm.user_id = 1337;
Which would if it worked selected all messages in thread_id 76852 then joined user_read_message where user_id is 1337 and messages which he hasn't read will simply have null. I would then somehow count where read is 0 or NULL.
ps. If there is better idea how to model this please let me know!
I would do this. Add your WHERE clause related to the user_read_message table into the JOIN to that table. Since this is a LEFT JOIN, all of the fields returned from that table will be NULL if there is no match. Add a field from that table to your WHERE clause that is always populated and then check to see if it is NULL. That would mean there is no match.
SELECT m.thread_id, 1337 AS user_id, COUNT(*) unread_messages
FROM sup_messages as m
LEFT OUTER JOIN user_read_message as urm
ON m.id = urm.message_id
AND urm.user_id = 1337
WHERE m.thread_id = 76852 AND urm.message_id IS NULL;
SELECT COUNT(*)
FROM sup_messages
WHERE sup_messages.thread_id = 76852 AND
sup_messages.id IN (SELECT DISTINCT urm.message_id
FROM urm
WHERE urm.user_id = 1337 AND urm.read = 0)

SQL JOIN a table and GROUP BY to get correct row

Here is the situation, i for the tables forum_topics, forum_replies and users.
Where the _topics contains all the topics, the _replies contains all posts and the users contain all users.
I'm trying to list the topics as:
(Subject)__________________________________(Time of last reply)
(Topic created by username)__________________(Last reply by)
For now, the "subject" and "topic created by username" displays just fine, however, the time of the last post and user who posted it is wrong.
SQL:
SELECT
forum_topics.id,
forum_topics.category,
forum_topics.subject,
forum_topics.created AS topiccreate,
forum_topics.createdby AS topiccreatedby,
forum_replies.topic,
forum_replies.created AS repliecreated,
forum_replies.createdby AS repliecreatedby,
usertopic.firstname AS topicfirstname,
usertopic.lastname AS topiclastname,
userreplie.firstname AS repliefirstname,
userreplie.lastname AS replielastname,
usertopic.id as topicid,
userreplie.id
FROM forum_topics
JOIN forum_replies ON forum_replies.topic = forum_topics.id
JOIN users usertopic ON forum_topics.createdby = usertopic.id
JOIN users userreplie ON forum_replies.createdby = userreplie.id
WHERE forum_topics.category = '1'
GROUP BY forum_replies.topic
ORDER BY forum_replies.created DESC
How can i get the "Time of last reply" and "Last reply by" to display correct? I've tried removing the Group By, and then it retrieves all the posts, however i just want the very latest post-data for each topic.
As for now when using GROUP BY, it retrieves all the topics just once (correct) but the last reply by and time of the last reply is not displaying correct as it seems to retrieve the data for the first post of each topic.
Hope you understand my question! :/
You need one more condition to get the latest reply. Here is how you would do it in the join clause:
FROM forum_topics
JOIN forum_replies ON forum_replies.topic = forum_topics.id
JOIN users usertopic ON forum_topics.createdby = usertopic.id
JOIN users userreplie ON forum_replies.createdby = userreplie.id
JOIN (select topic, max(created) as maxcreated
from forum_replies
group by topic
) frmax on frmax.topic = forum_replies.topic and
frmax.maxcreated = forum_replies.created;
I don't think you will need the group by after doing this.
Try removing both group by and order by in your SQL query and check.

MySQL - Count on a join

I'm trying to count freelanceFeedback's and order by the count like this:
$sql = "SELECT authentication.*, (SELECT COUNT(*) FROM freelanceFeedback) as taskscount FROM authentication
LEFT JOIN freelanceFeedback
ON authentication.userId=freelanceFeedback.FK_freelanceWinnerUserId
WHERE `FK_freelanceProvider`=$what
ORDER BY taskscount DESC";
But I'm having multiple outputs if the user has multiple feedbacks and it's not ordering by the taskscount.
I can't figure out what the 'tweet' is wrong..
** UPDATE **
I think I've got it myself:
$sql = "SELECT DISTINCT authentication.*,
(SELECT COUNT(*) FROM freelanceFeedback
WHERE FK_freelanceWinnerUserId=userId
) as taskscount
FROM authentication
WHERE `FK_freelanceProvider`=$what
ORDER BY taskscount DESC";
This is only outputting 1 user and ORDERING by the amount of feedbacks.
When you use COUNT(), you also need to use GROUP BY:
SELECT authentication.userId,
COUNT(freelanceFeedback.id) AS taskscount
FROM authentication
LEFT JOIN freelanceFeedback
ON authentication.userId = freelanceFeedback.FK_freelanceWinnerUserId
WHERE `FK_freelanceProvider`= $what
GROUP BY authentication.userId
ORDER BY taskscount DESC
However, this will only work if you are not doing SELECT * (which is bad practice anyway). Everything that's not in the COUNT bit needs to go into GROUP BY. If this includes text fields, you'll not be able to do it, so you'll need to do a JOIN to a subquery. MySQL won't complain if you don't but it can seriously slow things down and other DBs will throw an error, so best to do it right:
SELECT authentication.userId,
authentication.textfield,
authentication.othertextfield,
subquery.taskscount
FROM authentication
LEFT JOIN (SELECT freelanceFeedback.FK_freelanceWinnerUserId,
COUNT(freelanceFeedback.FK_freelanceWinnerUserId) AS taskscount
FROM freelanceFeedback
GROUP BY FK_freelanceWinnerUserId) AS subquery
ON authentication.userId = subquery.FK_freelanceWinnerUserId
WHERE authentication.FK_freelanceProvider = $what
ORDER BY subquery.taskscount DESC
It's not clear what table the FK_freelanceProvider is part of so I've assumed it's authentication.