Most recent related entries from another table - mysql

I've got a table of users (1,000s) and a table of user messages (100,000s). I want a fast way of getting all users and their most recent message.
What I'm currently using is something like...
SELECT
u.id, u.name,
(
SELECT note FROM msgs
WHERE msgs.uID=u.id
ORDER BY created_date DESC
LIMIT 1
) as note
FROM users u
Right now if I limit that to 20 users, it takes 2.5s ... 200 users takes 45s.
(I already have an INDEX on msgs.uID and msgs.created_date.)
What am I doing wrong? I need a much faster query.

I searched before posting (with no luck), but found this solution in the "related" sidebar just after posting.
SELECT u.id, u.first_name, msgs.note
FROM users u
JOIN (
SELECT MAX(created_date) max_date, user_id
FROM msgs
GROUP BY user_id
) msgs_max ON (msgs_max.user_id = u.id)
JOIN msgs m ON (msgs.created_date = msgs_max.max_date AND u.id = msgs.user_id)
Considerably better, but still ~1.3s on my tables. Can't MySQL do this much faster?

select users.*, msgs2.* from users
inner join
(
select msgs.* from msgs
inner join
(
select max(created_date) as dt, user_id from msgs
group by user_id
) last_dt
on
msgs.user_id = last_dt.user_id
and
msgs.created_date = last_dt.dt
) msgs2
on
users.id = msgs2.user_id
Try this, I am sorry if this may be has some syntax error, or may be also works slow, I write this query directly, without any test.
Just try.

Solution with just two joins
SELECT u.id,u.name,msgs_max.note FROM users u JOIN
(SELECT m1.uID, m1.note FROM msgs m1 LEFT JOIN msgs m2
ON (m1.created_date < m2.created_date
AND m1.uID = m2.uID)
WHERE m2.id IS NULL) msgs_max
ON u.uID=msg_max.uID
Reference: https://stackoverflow.com/a/123481/2180715

Related

Left join sql query

I want to get all the data from the users table & the last record associated with him from my connection_history table , it's working only when i don't add at the end of my query
ORDER BY contributions DESC
( When i add it , i have only the record wich come from users and not the last connection_history record)
My question is : how i can get the entires data ordered by contributions DESC
SELECT * FROM users LEFT JOIN connections_history ch ON users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date)
The order by should not affect the results that are returned. It only changes the ordering. You are probably getting what you want, just in an unexpected order. For instance, your query interface might be returning a fixed number of rows. Changing the order of the rows could make it look like the result set is different.
I will say that I find = to be more intuitive than EXISTS for this purpose:
SELECT *
FROM users u LEFT JOIN
connections_history ch
ON u.id = ch.guid AND
ch.date = (SELECT Max(ch1.date)
FROM connections_history ch1
WHERE ch.guid = ch1.guid
)
ORDER BY contributions DESC;
The reason is that the = is directly in the ON clause, so it is clear what the relationship between the tables is.
For your casual consideration, a different formatting of the original code. Note in particular the indented AND suggests the clause is part of the LEFT JOIN, which it is.
SELECT * FROM users
LEFT JOIN connections_history ch ON
users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date
)
We can use nested queries to first check for max_date for a given user and pass the list of guid to the nested query assuming all the users has at least one record in the connection history table otherwise you could use Left Join instead.
select B.*,X.* from users B JOIN (
select A.* from connection_history A
where A.guid = B.guid and A.date = (
select max(date) from connection_history where guid = B.guid) )X on
X.guid = B.guid
order by B.contributions DESC;

sql counts wrong number of likes

I have written an sql statement that besides all the other columns should return the number of comments and the number of likes of a certain post. It works perfectly when I don't try to get the number of times it has been shared too. When I try to get the number of time it was shared instead it returns a wrong number of like that seems to be either the number of shares and likes or something like that. Here is the code:
SELECT
[...],
count(CS.commentId) as shares,
count(CL.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN
account A ON A.id = `AS`.accountId
INNER JOIN
comment C ON C.accountId = A.id
LEFT JOIN
commentLikes CL ON C.commentId = CL.commentId
LEFT JOIN
commentShares CS ON C.commentId = CS.commentId
GROUP BY
C.time
ORDER BY
year, month, hour, month
Could you also tell me if you think this is an efficient SQL statement or if you would do it differently? thank you!
Do this instead:
SELECT
[...],
(select count(*) from commentLikes CL where C.commentId = CL.commentId) as shares,
(select count(*) from commentShares CS where C.commentId = CS.commentId) as numberOfLikes
FROM
(SELECT *
FROM accountSpecifics
WHERE institutionId= '{$keyword['id']}') `AS`
INNER JOIN account A ON A.id = `AS`.accountId
INNER JOIN comment C ON C.accountId = A.id
GROUP BY C.time
ORDER BY year, month, hour, month
If you use JOINs, you're getting back one result set, and COUNT(any field) simply counts the rows and will always compute the same thing, and in this case the wrong thing. Subqueries are what you need here. Good luck!
EDIT: as posted below, count(distinct something) can also work, but it's making the database do more work than necessary for the answer you want to end up with.
Quick fix:
SELECT
[...],
count(DISTINCT CS.commentId) as shares,
count(DISTINCT CL.commentId) as numberOfLikes
Better approach:
SELECT [...]
, Coalesce(shares.numberOfShares, 0) As numberOfShares
, Coalesce(likes.numberOfLikes , 0) As numberOfLikes
FROM [...]
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfShares
FROM commentShares
GROUP
BY commentId
) As shares
ON shares.commentId = c.commentId
LEFT
JOIN (
SELECT commentId
, Count(*) As numberOfLikes
FROM commentLikes
GROUP
BY commentId
) As likes
ON likes.commentId = c.commentId

Extract only 1 message from all senders

I am building a messaging system and need to extract only the last message from each sender to a specified recipient. So, if 3 people each sent 5 messages(total of15 messages) to the recipient, I need to get 3 entries; the last message from each sender.
Here is my current SQL:
SELECT
messages.*,
user_accounts.uacc_id,
user_accounts.uacc_username,
user_profiles.upro_image_name
FROM messages
LEFT JOIN user_accounts
ON messages.msg_from_uacc_fk = user_accounts.uacc_id
LEFT JOIN user_profiles
ON user_profiles.upro_uacc_fk = user_accounts.uacc_id
WHERE
messages.msg_to_uacc_fk = ?
ORDER BY
msg_id
DESC
I tried adding 'MAX(1)' to the SELECT as well as 'LIMIT = 1' to after the DESC, but, of course, this just returned a total of 1 message.
It is sometimes hard to guess how the tables are designed but this query below uses a subquery to get the latest message for each user.
SELECT a.*,
c.uacc_id,
c.uacc_username,
d.upro_image_name
FROM messages a
INNER JOIN
(
SELECT msg_from_uacc_fk, MAX(msg_id) max_id
FROM messages
GROUP BY msg_from_uacc_fk
) b ON a.msg_from_uacc_fk = b.msg_from_uacc_fk AND
a.msg_id = b.max_id
INNER JOIN user_accounts c
ON a.msg_from_uacc_fk = c.uacc_id
INNER JOIN user_profiles d
ON d.upro_uacc_fk = c.uacc_id
WHERE a.msg_to_uacc_fk = ?
If this doesn't solve the problem, please add sample records along with your question :)
Can you not simply use a group by?
SELECT u.uacc_username, max(m.msg_id) as LatestMsg
FROM messages m JOIN user_accounts u on m.msg_from_uacc_fk = u.uacc_id
WHERE m.msg_to_uacc_fk = ?
GROUP BY u.uacc_username

MySQL reverse GROUP BY results

I'm not good at Mysql expressions, so here is a quick question that I'm trying to get my head around. I would like to reverse the order of the GROUP BY expression so that I can get the last entry by the user.
I managed to find good examples around the internet, but I couldn't implement them with my expression without receiving an error. Thanks guys!
SELECT messages.conv, messages.from_user, messages.to_user,
messages.content, messages.date_posted, messages.note_read,
messages.active, users.thumb, users.name, users.id
FROM `messages`
INNER JOIN `users`
ON messages.from_user = users.id
WHERE messages.from_user = 1
OR messages.to_user = 1
GROUP BY messages.conv
ORDER BY date_posted ASC
EDIT: I apparently misunderstood your requirement, your current query will return a pseudo-random row due to your GROUP BY not having any aggregates, what you want is the last row for each conversation which would be something more like (assuming messages, like users, has an autoincrementing primary key called id);
SELECT messages.conv, messages.from_user, messages.to_user,
messages.content, messages.date_posted,
messages.note_read, messages.active, users.thumb,
users.name, users.id FROM `messages`
INNER JOIN `users`
ON messages.from_user = users.id
WHERE messages.id IN
(SELECT MAX(id) FROM messages
WHERE messages.from_user = 1 OR messages.to_user = 1
GROUP BY conv);
SQLFiddle here.

Simple MySQL Join problem

Im stumped by this simple query because its one I have not tried before.
Ive got a User table, User_Widget table and a Widget table.
A simple inner join shows me what widgets they have by joining user_widget.user_id = user.user_id.
How would I show the widgets in the Widget table that they dont have?
Look up WHERE NOT EXISTS with a subselect in your documentation..
Use a CROSS JOIN and a LEFT OUTER JOIN ( this is from my MS SQL experience, but the concept should hold ).
It works like this. The sub-query gets all possible combinations of user and widget.
The LEFT OUTER JOIN brings your User_Widgets associations into play.
The IS NULL part of the WHERE CLAUSE will exclude widgets that the user does have, giving you only the ones that don't.
SELECT allpossible.User_ID, allpossible.Widget_ID FROM
(
SELECT User_ID, Widget_ID FROM
Users
CROSS JOIN
Widgets
) allpossible
LEFT OUTER JOIN
User_Widgets uw
ON
allpossible.User_ID = uw.User_ID
AND allpossible.Widget_ID = uw.Widget_ID
WHERE
uw.UserID IS NULL
SELECT * FROM widgets WHERE id NOT IN
(
SELECT widget_id FROM user_widgets WHERE user_id = 1
)
(where 1 is the id of the user you're interested in)
This is a guess, (I haven't tried it), but try This:
Select Distinct u.*, Z.*
From User u
Left Join
(Select u.UserId, w.*
From Widget w
Where Not Exists
(Select * From User_Widget
Where userId = u.UserId
And widgetId = w.WidgetId)) Z
On Z.userId = u.UserId
Thanks to Bart Janson I got the query down to:
SELECT * FROM widgets
WHERE NOT EXISTS (
SELECT * FROM widget_user
WHERE widgets.widget_id = widget_user.widget_id
AND user_id = "ID NUMBER OF PERSON YOU WANT"
)
ORDER BY RAND()
LIMIT 10
Cheers guys
SELECT *
FROM widgets w
LEFT OUTER JOIN user_widget uw
ON w.id = uw.widget_id AND uw.user_id = 1 // or whatever user u want
WHERE uw.widget_id IS NULL;