how to get a single row for each user? - mysql

sorry for the title, i don't know how to explain it better...
i have a forum and i want to make a sort of achievement system in php
i want to know when users with posts>10 posted their 10th message...
the post table is like
post_id | post_date | userid | post_message | ...
i can get this result for each user with
select userid, post_date from posts where userid=1 order by post_date limit 9,1
but i need a resultset like
id | date
id | date
id | date
it can only be done with procedures?

Try this query
select
*
from (
select
#rn:=if(#prv=userid, #rn+1, 1) as rid,
#prv:=userid as userid,
post_message
from
tbl
join
(select #rn:=0, #prv:=0) tmp
order by
userid,
post_date) tmp
where
rid=10
SQL FIDDLE
| RID | USERID | POST_MESSAGE |
-------------------------------
| 10 | 1 | asdasd |
| 10 | 2 | asdasd |

try this one:
SELECT userid
, SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(post_date ORDER BY post_date), ',', 10), ',', -1) AS PostDate
FROM posts
GROUP BY userid
HAVING PostDate <> '' OR PostDate IS NOT NULL
But you need to pay attention with the maximum length that the GROUP_CONCAT can hold.

Related

Select all columns, only taking into account the highest scores per user

It's been asked before, but I can't get it to work properly. The selected answer doesn't work with duplicate values. The second answer should be able to handle duplicates according to the poster, but it's not functioning correctly with my data.
What I want to achieve is pretty simple:
I have a database containing all scores of all users. I want to build a highscore table, so I want to select all highscore rows of each user. With highscore row I mean the row for that user where his score is the highest.
Here's a demo I made based on the answer I mentioned at the top:
CREATE TABLE test(
score INTEGER,
user_id INTEGER,
info INTEGER
);
insert into test(score, user_id, info)
values
(1000, 1, 1),
(1000, 1, 2),
(2000, 2, 3),
(2001, 2, 1);
--
SELECT t.*
FROM test t
JOIN (SELECT test.user_id, max(score) as mi FROM test GROUP BY user_id) j ON
t.score = j.mi AND
t.user_id = j.user_id
ORDER BY score DESC, info ASC;
Expected output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
+-------+---------+------+
--> every user_id is present with the row where the user had the highest score value.
Real output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
| 1000 | 1 | 2 |
+-------+---------+------+
--> when there are duplicate values, user show up multiple times.
Anyone who can point me in the right direction?
I assume when there are duplicate scores you want the lowest info just like your expected output.
With NOT EXISTS:
select t.* from test t
where not exists (
select 1 from test
where user_id = t.user_id and (
score > t.score or (score = t.score and info < t.info)
)
);
See the demo.
For MySql 8.0+ you can use ROW_NUMBER():
select t.score, t.user_id, t.info
from (
select *, row_number() over (partition by user_id order by score desc, info asc) rn
from test
) t
where t.rn = 1
See the demo.
Results:
| score | user_id | info |
| ----- | ------- | ---- |
| 1000 | 1 | 1 |
| 2001 | 2 | 1 |
If the combination of (user_id, info) is UNIQUE and NOT NULL (or PRIMARY KEY), then you can use a LIMIT 1 subquery in the WHERE clause:
SELECT t.*
FROM test t
WHERE (t.score, t.info) = (
SELECT t2.score, t2.info
FROM test t2
WHERE t2.user_id = t.user_id
ORDER BY t2.score DESC, t2.info ASC
LIMIT 1
)
ORDER BY t.score DESC, t.info ASC;
The result will be:
| score | user_id | info |
|-------|---------|------|
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
demo on sqlfiddle
SELECT info FROM test HAVING MAX(score) was used to keep the info field relevant with the row containing the MAX(score).
SELECT MAX(score) score, user_id, (SELECT info FROM test HAVING MAX(score)) AS info FROM test GROUP BY user_id ORDER BY score DESC;

How to order MySQL results by another table's count?

I have a database table containing game entries:
--------------------------------------------------------
| game_id | title | description | entry_time |
--------------------------------------------------------
| 1 | Game 1 | Descript... | yyyy-mm-dd hh:mm:ss |
--------------------------------------------------------
| 2 | Game 2 | Descript... | yyyy-mm-dd hh:mm:ss |
--------------------------------------------------------
And another containing game play history:
-----------------------------------------------
| game_id | user_id | entry_time |
-----------------------------------------------
| 1 | 0da89sadf89 | yyyy-mm-dd hh:mm:ss |
-----------------------------------------------
| 1 | f8jsf89vjs9 | yyyy-mm-dd hh:mm:ss |
-----------------------------------------------
| 2 | f8jsf89vjs9 | yyyy-mm-dd hh:mm:ss |
-----------------------------------------------
I am trying to select results from the first table, based on game popularity.
SELECT games.game_id, games.title, games.description
FROM `games`
JOIN foo_db.game_plays game_plays
ON game_plays.game_id LIKE games.game_id
WHERE games.title LIKE "%game%"
ORDER BY COUNT(game_plays.game_id) DESC, games.entry_time DESC
LIMIT 10
But for some reason, only one result is returned ("Game 1").
When I remove JOIN, and just order the results by entry_time, both results are returned as expected.
I made this query. Could you please try this.
SELECT a.game_id, a.title, a.description, b.total
from games a
JOIN (SELECT game_id, count(user_id) as total from
game_play group by game_id) b
ON a.game_id = b.game_id
AND a.title LIKE '%Game%'
ORDER BY b.total DESC, a.entry time DESC
OUTPUT
Hope it will help.
Because MySQL is sometimes really slow when mixing JOIN and GROUP BY, a corelated subquery might be a good alternative:
SELECT games.game_id, games.title, games.description
FROM `games`
WHERE games.title LIKE "%game%"
ORDER BY (
SELECT COUNT(game_plays.game_id)
FROM foo_db.game_plays
WHERE game_plays.game_id = games.game_id
) DESC, games.entry_time DESC
LIMIT 10

MAX function in MySQL does not return proper key value

I have a table called tbl_user_sal:
| id | user_id | salary | date |
| 1 | 1 | 1000 | 2014-12-01 |
| 2 | 1 | 2000 | 2014-12-02 |
Now I want to get the id of the maximum date. I used the following query:
SELECT MAX(date) AS from_date, id, user_id, salary
FROM tbl_user_sal
WHERE user_id = 1
But it gave me this output:
| id | user_id | salary | from_date |
| 1 | 1 | 2000 | 2014-12-02 |
Which is correct as far as the max date being 2014-12-02, but the corresponding id is not correct. This happens for other records as well. I used order by to check but that was not successful either. Can anyone shed some light on this?
Note: Its not necessary that max date will have max id, according to my needs. Records can have max date but id may be older.
If you only want to retrieve that information for a single user, which you seem to, because of your WHERE clause, just use ORDER BY and LIMIT:
SELECT *
FROM tbl_user_sal
WHERE user_id = 1
ORDER BY date DESC
LIMIT 1
If you want to do that for every user, however, you will have to get a little bit fancier. Something like that should do it:
SELECT t2.id, user_id, date
--find max date for each user_id
FROM (SELECT user_id, MAX(date) AS date
FROM tbl_user_sal
GROUP BY user_id) AS t1
--join ids for each max date/user_id combo
JOIN tbl_user_sal AS t2
USING (user_id, date)
--limit to 1 id for every user_id
GROUP BY
user_id
You are missing group by clause Try this:
select max(awrd_date) as from_date,awrd_id
from tbl_user_sal
where awrd_user_id = 106
group by awrd_id
What I believe you should do here is have a subquery that pulls the max date, and your outer query looks for the row with that date.
It looks like this:
SELECT *
FROM myTable
WHERE date = (SELECT MAX(date) FROM myTable);
Additional things may need to be added if you want to search for a specific user_id, or get the largest date for each user_id, but this gives your expected results for this example here.
Here is the SQL Fiddle.

MySQL: Is there a way to eliminates duplicate entries from the result

I have a table named "message" that stores messages from one user to another user. I want to make a message box that contains both incoming and outcoming messages for particular user. This message box should be contain the last message between two users. So, I have to eliminate duplicate messages between two users. I tried group by and it eliminates duplicate messages but I don't pick the most recent message because order by works after group by. I tried distinct function to eliminates duplicate messages. It works well, but I have to select all colums which isn't possible with distinct
My message table:
+-------+---------+------+-----+------------+
| id | from_id | to_id| text| created_at |
+-------+---------+------+-----+------------+
| 1 | 1 | 2 | mes | 2014-01-16 |
| 2 | 2 | 1 | mes | 2014-01-17 |
| 3 | 1 | 3 | mes | 2014-01-18 |
| 4 | 3 | 1 | mes | 2014-01-19 |
+-------+---------+------+-----+------------+
My Group By SQL
SELECT * FROM message WHERE (from_id = 1 OR to_id = 1) GROUP BY(from_id + to_id) ORDER BY created_at DESC;
And Distinct
SELECT DISTINCT(from_id + to_id) FROM message WHERE (from_id = 1 OR to_id = 1)
In the above example, I want to select second and fourth message.
Is there a way to eliminate duplicate messages between two user from the result?
EDIT: I've improved the example
I tried group by and it eliminates duplicate messages but I don't pick the most recent message because order by works after group by
So you can order it before:
SELECT *
FROM (SELECT * FROM message ORDER BY created_at DESC)
WHERE (from_id = 1 OR to_id = 1) GROUP BY(from_id + to_id);
If I understand correctly what you're trying to achieve, you can leverage LEAST(), GREATEST() functions and non-standard GROUP BY extension behavior in MySQL like this
SELECT id, from_id, to_id, text, created_at
FROM
(
SELECT id, from_id, to_id, text, created_at
FROM message
ORDER BY LEAST(from_id, to_id), GREATEST(from_id, to_id), created_at DESC
) q
GROUP BY LEAST(from_id, to_id), GREATEST(from_id, to_id)
That will give you the last message row for each pair of users.
Output:
+------+---------+-------+------+------------+
| id | from_id | to_id | text | created_at |
+------+---------+-------+------+------------+
| 2 | 2 | 1 | mes | 2014-01-17 |
| 4 | 3 | 1 | mes | 2014-01-19 |
+------+---------+-------+------+------------+
Here is SQLFiddle demo
You can use:
ORDER BY id DESC LIMIT 1
or by timestamp (assuming it contains data AND time):
ORDER BY create_at DESC LIMIT 1
This will sort all results in a descending order and only give you the last row.
Hope this helps!
Just use a simple select. There is no reason for duplicates to be created.
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
Here ? represents one id and ?? the other.
There would be no duplicates here. Ordering can be achieved in a few ways:
Order by most recent message regardless of sender:
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
ORDER BY created_at DESC
Order all sender message first (then by created_at)
SELECT from_id, to_id, text, created_at
FROM message
WHERE
(from_id = ? AND to_id = ??)
OR (from_id = ?? AND to_id = ?)
ORDER BY from_id = ? DESC, created_at DESC
Try adding a HAVING clause after your GROUP BY: HAVING COUNT(*) > 1
or
SELECT
columns names, COUNT(*)
FROM
(SELECT DISTINCT
column names
FROM
message
)
message
GROUP BY
column names
HAVING COUNT(*) > 1
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE message
(`id` int, `from_id` int, `to_id` int, `text` varchar(3), `created_at` datetime)
;
INSERT INTO message
(`id`, `from_id`, `to_id`, `text`, `created_at`)
VALUES
(1, 1, 2, 'mes', '2014-01-16 00:00:00'),
(2, 2, 1, 'MUL', '2014-01-17 00:00:00')
;
Query 1:
SELECT *
FROM message
WHERE from_id = 1 OR to_id = 1
ORDER BY created_at DESC
limit 1
Results:
| ID | FROM_ID | TO_ID | TEXT | CREATED_AT |
|----|---------|-------|------|--------------------------------|
| 2 | 2 | 1 | MUL | January, 17 2014 00:00:00+0000 |

Sort data before using GROUP BY?

I have read that grouping happens before ordering, is there any way that I can order first before grouping without having to wrap my whole query around another query just to do this?
Let's say I have this data:
id | user_id | date_recorded
1 | 1 | 2011-11-07
2 | 1 | 2011-11-05
3 | 1 | 2011-11-06
4 | 2 | 2011-11-03
5 | 2 | 2011-11-06
Normally, I'd have to do this query in order to get what I want:
SELECT
*
FROM (
SELECT * FROM table ORDER BY date_recorded DESC
) t1
GROUP BY t1.user_id
But I'm wondering if there's a better solution.
Your question is somewhat unclear but I have a suspicion what you really want is not any GROUP aggregates at all, but rather ordering by date first, then user ID:
SELECT
id,
user_id,
date_recorded
FROM tbl
ORDER BY date_recorded DESC, user_id ASC
Here would be the result. Note reordering by date_recorded from your original example
id | user_id | date_recorded
1 | 1 | 2011-11-07
3 | 1 | 2011-11-06
2 | 1 | 2011-11-05
5 | 2 | 2011-11-06
4 | 2 | 2011-11-03
Update
To retrieve the full latest record per user_id, a JOIN is needed. The subquery (mx) locates the latest date_recorded per user_id, and that result is joined to the full table to retrieve the remaining columns.
SELECT
mx.user_id,
mx.maxdate,
t.id
FROM (
SELECT
user_id,
MAX(date_recorded) AS maxdate
FROM tbl
GROUP BY user_id
) mx JOIN tbl t ON mx.user_id = t.user_id AND mx.date_recorded = t.date_recorded
Iam just using the technique
"Using order clause before group by inserting it in group_concat clause"
SELECT SUBSTRING_INDEX(group_concat(cast(id as char)
ORDER BY date_recorded desc),',',1),
user_id,
SUBSTRING_INDEX(group_concat(cast(`date_recorded` as char)
ORDER BY `date_recorded` desc),',',1)
FROM data
GROUP BY user_id