SQL LIMIT expression - mysql

Users:
UserID | UserNick
3 | Joe
23 | Peter
4 | Mary
Messages:
FromUserID | theMSG
3 | Hi
3 | What' up?
23 | asdfg
23 | OK...
4 | Hi, this is Mary
I have a query that gives the following result:
UserID | Message
1 | Hello
1 | How are ya?
2 | yadda yadda
5 | Cool.
5 | I didn't know that.
I now want to limit the result. Not by the number of rows I get back, by the number of different users from whom I want to see the messages.
"Give me three messages of the first 2 users"
UserID | Message
1 | Hello
1 | How are ya?
2 | yadda yadda
But if I write LIMIT 2, it will give me only
UserID | Message
1 | Hello
1 | How are ya?
How do I achieve what I want? What's the keyword I need?

I think you need to use a nested query like this:
SELECT * FROM messages
WHERE UserID IN
(SELECT DISTINCT UserID FROM messages ORDER BY UserID LIMIT 2)
but without knowing the table structure it is difficult to say more.
The idea is:
get the users you want in nested query
get their messages with outer query

You could use the following query. It gives you options to select first n unique users or last n unique users. Use ASC for first or DESC for last n users.
SELECT *
FROM messages
WHERE UserID IN (
SELECT DISTINCT UserID
FROM messages
ORDER BY UserID ASC/DESC
LIMIT 2
)
LIMIT 2
Inner Limit defines the number of unique users you want to select.
Outer Limit definesthe number of messages you wish to see from users

If I understand your question, you can get those results with:
SELECT * FROM table WHERE UserID IN(1, 2)
or
SELECT * FROM table WHERE UserID BETWEEN 1 AND 2

Related

How do I combine two queries on the same table to get a single result set in MySQL

I am not very good at sql but I am getting there. I have searched stackoverflow but I can't seem to find the solution and I hope someone out there can help me. I have a table (users) with data like the following. The book_id column is a key to another table that contains a book the user is subscribed to.
|--------|---------------------|------------------|
| id | book_id | name |
|--------|---------------------|------------------|
| 1 | 1 | jim |
| 2 | 1 | joyce |
| 3 | 1 | mike |
| 4 | 1 | eleven |
| 5 | 2 | max |
| 6 | 2 | dustin |
| 7 | 2 | lucas |
|--------|---------------------|------------------|
I have a function in my PHP code that returns two random users from a specific book id (either 1 or 2). Query one returns the result in column 1 and result two returns the results in column 2 like:
|---------------------|------------------|
| 1 | 2 |
|---------------------|------------------|
| jim | max |
| joyce | dustin |
|---------------------|------------------|
I have achieved this by running two separate queries as seen below. I want to know if it's possible to achieve this functionality with one query and how.
$random_users_with_book_id_1 = SELECT name FROM users WHERE book_id=1 LIMIT 2
$random_users_with_book_id_2 = SELECT name FROM users WHERE book_id=2 LIMIT 2
Again, I apologise if it's too specific. The query below has been closest to what I was trying to achieve.:
SELECT a.name AS book_id_1, b.name AS book_id_2
FROM users a, users b
WHERE a.book_id=1 AND b.book_id = 2
LIMIT 2
EDIT: I have created a fiddle to play around with his. I appreciate any help! Thank you!! http://sqlfiddle.com/#!9/7fcbca/1
It is easy actually :)
you can use UNION like this:
SELECT * FROM (
(SELECT * FROM user WHERE n_id=1 LIMIT 2)
UNION
(SELECT * FROM user WHERE n_id=2 LIMIT 2))
collection;
if you read this article about the documentation you can use the () to group the individual queries and the apply the union in the middle. Without the parenthesis it would still LIMIT 2 and show only the two first. Ref. "To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT:"
If you want to combine the queries in MySQL, you can just use parentheses:
(SELECT name
FROM users
WHERE n_id = 1
LIMIT 2
) UNION ALL
(SELECT name
FROM users
WHERE n_id = 2
LIMIT 2
);
First, only use UNION if you specifically want to incur the overhead of removing duplicates. Otherwise, use UNION ALL.
Second, this does not return random rows. This returns arbitrary rows. In many cases, this might be two rows near the beginning of the data. If you want random rows, then use ORDER BY rand():
(SELECT name
FROM users
WHERE n_id = 1
ORDER by rand()
LIMIT 2
) UNION ALL
(SELECT name
FROM users
WHERE n_id = 2
ORDER BY rand()
LIMIT 2
);
There are other methods that are more efficient, but this should be fine for up to a few thousand rows.

Querying conversations from messages table

I have the messages table formatted as follows:
+------------------------------------------------------------------+
| id|sender_id| recipient_id | message_text |created_at |
+------------------------------------------------------------------+
| 1 | 2 | 10 | "test1" |"2017-04-10 09:05:45" |
| 2 | 10 | 2 | "test2" |"2017-04-10 09:05:47" |
| 3 | 2 | 4 | "test3" |"2017-04-10 09:05:49" |
| 4 | 10 | 4 | "test4" |"2017-04-10 09:05:51" |
| 5 | 4 | 2 | "test5" |"2017-04-10 09:05:53" |
| 6 | 2 | 10 | "test6" |"2017-04-10 09:05:58" |
+------------------------------------------------------------------+
What I'm trying to do is get all the "conversations" of a logged in user (say user with id 2), along with the last messages for that conversation.
I managed to pull out the id's of users user2 has messages with using this query:
select distinct users.id
from messages, users where
(recipient_id = 2 and users.id = sender_id)
or
(sender_id = 2 and users.id = recipient_id);
What this yields is
4
10
as user2 has either sent and/or received messages from these two people (test1, test2, test6 for 10, and test3, test5 for 4).
What I can't do is modify this query so it also yields the last message sent to or received by the yielded id - for example
4 | test5
10 | test6
If I understand your requirement correctly, you want to obtain the most recent message date for every conversation involving a certain user. In this case, we can aggregate over conversations for a given user and retain the most recent message date.
SELECT m1.*
FROM messages m1
INNER JOIN
(
SELECT LEAST(sender_id, recipient_id) AS first,
GREATEST(sender_id, recipient_id) AS second,
MAX(created_at) AS recent_date
FROM messages
WHERE sender_id = 2 OR recipient_id = 2
GROUP BY LEAST(sender_id, recipient_id),
GREATEST(sender_id, recipient_id)
) m2
ON LEAST(m1.sender_id, m1.recipient_id) = m2.first AND
GREATEST(m1.sender_id, m1.recipient_id) = m2.second AND
m1.created_at = m2.recent_date
Output:
Explanation:
The challenge in this query is to find a way to group conversations between two users together. I used the LEAST/GREATEST trick, which is a way that we can treat a 2 -> 4 and 4 -> 2 conversation as being logically the same thing. Then, using GROUP BY, we can identify the most recent conversation date for that pair of conversing users. So the subquery in my answer above finds, for each pair of users, without regard of any order, that pair along with its most recent conversation date. We then join this result back to the messages table to bring in the actual latest message text.
Demo here:
Rextester
Use an order_by created_at desc statement at the end of your query to get the most recent messages.

Mysql order by top two then id

I want to show first two top voted Posts then others sorted by id
This is table
+----+-------+--------------+--------+
| Id | Name | Post | Votes |
+====+=======+==============+========+
| 1 | John | John's msg | -6 |
| 2 |Joseph |Joseph's msg | 8 |
| 3 | Ivan | Ivan's msg | 3 |
| 4 |Natalie|Natalie's msg | 10 |
+----+-------+--------------+--------+
After query result should be:
+----+-------+--------------+--------+
| Id | Name | Post | Votes |
+====+=======+==============+========+
| 4 |Natalie|Natalie's msg | 10 |
| 2 |Joseph |Joseph's msg | 8 |
-----------------------------------------------
| 1 | John | John's msg | -6 |
| 3 | Ivan | Ivan's msg | 3 |
+----+-------+--------------+--------+
I have 1 solution but i feel like there is better and faster way to do it.
I run 2 queries, one to get top 2, then second to get others:
SELECT * FROM table order by Votes desc LIMIT 2
SELECT * FROM table order by Id desc
And then in PHP i make sure that i show 1st query as it is, and on displaying 2nd query i remove entry's that are in 1st query so they don't double.
Can this be done in single query to select first two top voted, then others?
You would have to use subqueries or union - meaning you have a single outer query, which contains multiple queries inside. I would simply retrieve the IDs from the first query and add a id not in (...) criterion to the where clause of the 2nd query - thus filtering out the posts retrieved in the first query:
SELECT * FROM table WHERE Id NOT IN (...) ORDER BY Id DESC
With union the query would look like as follows:
(SELECT table.*, 1 as o FROM table order by Votes desc LIMIT 2)
UNION
(SELECT table.*, 0 FROM table
WHERE Id NOT IN (SELECT Id FROM table order by Votes desc LIMIT 2))
ORDER BY o DESC, if(o=1,Votes,Id) DESC
As you can see, it wraps 3 queries into one and has a more complicated ordering as well because in union the order of the records retrieved is not guaranteed.
Two simple queries seem to be a lot more efficient to me in this particular case.
There could be different ways to write a query that returns the rows in the order you want. My solution is this:
select
table.*
from
table left join (select id from table order by votes desc limit 2) l
on table.id = l.id
order by
case when l.id is not null then votes end desc,
tp.id
the subquery will return the first two id ordered by votes desc, the join will succeed whenever the row is one of the first two otherwise l.id will be null instead.
The order by will order by number of votes desc whenever the row is the first or the second (=l.id is not null), when l.id is null it will put the rows at the bottom and order by id instead.

SUM of Multiple COUNTs on Different Tables

This topic appears to be a popular one and definitely saturated in terms of the number of related posts, however, I've been working on this for 3 days and I cannot get this figured out.
I've been scouring this site and many others with potential solutions to this and some are executing, but I am not getting the expected results.
Here's what I'm trying to do...
SELECT and COUNT the number of reviews a user has submitted in the reviews table.
SELECT and COUNT the number of up-votes a user has in the reviewVotes table.
GROUP BY username (which is a key in both tables - usernames are unique, but exist in multiple rows).
Order the result set by the SUM of those COUNTs DESC. (This is something I keep trying, but can't get to even execute, so I am ordering by userReviewNum DESC right now.)
LIMIT the result set to the first 10.
The result set should give me the top 10 reviewers which is calculated by the number of reviews plus (+) the number of up-votes.
Here is my latest attempt which executes, but appears to be multiplying userReviewNum * reviewVotesNum and I need it to add them (but I have been extremely unsuccessful at any attempt to include the SUM command - so bad in fact that I am embarrassed to even show my attempts).
SELECT
reviews.username,
count(reviews.username) userReviewNum,
count(reviewVotes.username) reviewVotesNum
FROM reviews
LEFT JOIN reviewVotes ON reviews.username = reviewVotes.username
GROUP by reviews.username
ORDER BY userReviewNum DESC
LIMIT 0, 10
I've tried using a JOIN and a UNION and I can't seem to get either of them to work.
Any help anyone can provide is greatly appreciated!
UPDATE:
Here is the structure and some sample data.
Reviews Table (there are other fields, but these are the important ones):
| username | comment | rating | productID |
| foo | this is awesome! | 5 | xxxx |
| bar | i don't like this | 1 | xxxx |
| foo2 | it's ok | 3 | xxxx |
| foo | bleh - nasty | 1 | xxxx |
reviewVotes Table (again, more fields than this, but these are the important ones):
| username | voterUsername | productID |
| foo | foo2 | xxxx |
| foo2 | foo | xxxx | (the simple idea here is one user is up-voting another user's post)
So I need to count the number of reviews a user has in the Reviews table, then count the number of upvotes a user has in the reviewVotes table, and then order by the sum of those two numbers.
Additional UPDATE:
In the example above, here are the expected results:
Username | # Reviews
foo | 2
bar | 1
foo2 | 1
Username | # Up-Votes
foo | 1
foo2 | 1
Username | Total Sum
foo | 3
bar | 1
foo2 | 2
Try counting distinct reviews and votes like this:
SELECT
reviews.username,
COUNT(DISTINCT reviews.id) AS userReviewNum,
COUNT(DICTINCT reviewVotes.id) AS reviewVotesNum,
COUNT(DISTINCT reviews.id) + COUNT(DICTINCT reviewVotes.id) AS userRating
FROM
reviews
LEFT JOIN reviewVotes ON reviews.username = reviewVotes.username
GROUP by reviews.username
ORDER BY userRating DESC
LIMIT 10
Try this:
SELECT username, SUM(userReviewNum + reviewVotesNum) AS userRank
FROM (
SELECT
reviews.username,
count(reviews.username) userReviewNum,
count(reviewVotes.username) reviewVotesNum
FROM reviews
LEFT JOIN reviewVotes ON reviews.username = reviewVotes.username
GROUP by reviews.username
ORDER BY userReviewNum DESC
LIMIT 0, 10)
AS result_set
GROUP BY username
The group by there is, I think, required for the SUM to work.
Try this:
SELECT Res1.*, SUM(IF(reviewVotes.Username IS NULL, 0, 1)) AS UpVotes,
userReviewNum + SUM(IF(reviewVotes.Username IS NULL, 0, 1)) AS TotalSum FROM (
SELECT username, Count(*) AS userReviewNum
FROM reviews
GROUP BY username) AS Res1
LEFT OUTER JOIN reviewVotes ON res1.username = reviewVotes.username
GROUP BY Res1.username
ORDER BY TotalSum DESC
There result would be this:
foo 2 1 3
foo2 1 1 2
bar 1 0 1

Understanding GROUP BY with a LIMIT

I've got a simple SQL query to give me information about all of the users who have commented on an image like this...
SELECT user.id, user.userName, user.displayName, user.currentDefault, user.discipline
FROM user
INNER JOIN imageComment ON user.id = imageComment.commentAuthorId
WHERE imageComment.imageAlbumJunctionId = 37
GROUP BY user.id
LIMIT 2
I have the LIMIT 2 on there because I know there are only two comments for this image. And I have the GROUP BY user.id on there because I only want to show information about a user once, even if they comment multiple times.
So lets say that "Mike" commented on a photo twice. My question is, does this query...
Only search for 2 comments because of the LIMIT, and then perform the GROUP BY user.id
Perform the GROUP BY user.id and then search the full table for a second unique user
I am hoping that this query does #1 because if it does #2 that would cause it to search the entire table looking for a second user when "Mike" was actually the one that did both comments. And I did try an EXPLAIN but I don't really understand it because it gives the same output whether there is a GROUP BY or LIMIT. Thank you for reading.
The query finds the first two users who have commented an Image, so it's #2.
I'd suggest:
select ...
from user
where exists
(
select * from imageComment
where imageComment.commentAuthorId = user.id
and imageComment.imageAlbumJunctionId = 37
)
where exists is faster than a inner join because it can stop after the first. Good indices should be set.
LIMIT is applied after the GROUP BY user.id. So in this case, #2 is happening. But the WHERE clause will filter the table first, so it's not searching the whole table. Your query will give you correct results, but I think this should be better:
SELECT DISTINCT user.id, user.userName, user.displayName, user.currentDefault, user.discipline
FROM user
INNER JOIN imageComment ON user.id = imageComment.commentAuthorId
WHERE imageComment.imageAlbumJunctionId = 37
LIMIT 2
Sample Tables
User Table imageComment Table
id | userName id | commentAuthorId | imageAlbumJunctionId
--------------- -------------------------------------------
1 | Mike 1 | 1 | 37
2 | John 2 | 1 | 37
3 | Carla 3 | 2 | 37
4 | 3 | 37
Split explanation about what going on
SELECT user.id, user.userName
FROM user
INNER JOIN imageComment ON user.id = imageComment.commentAuthorId
WHERE imageComment.imageAlbumJunctionId = 37
this will return
1 | Mike
1 | Mike
2 | John
3 | Carla
after your GROUP BY user.id you will now get
1 | Mike
2 | John
3 | Carla
now we use the LIMIT 2 and get
1 | Mike
2 | John