mysql select distinct but latest row - mysql

How can I select the latest 'score' from a table for each distinct 'site' that exists?
For example:
site | date | score
a | 20140101 | 10
a | 20140102 | 8
b | 20140103 | 11
b | 20140202 | 9
I'd like to return one result for a and b, but only their latest entry (by date).
a | 20140102 | 8
b | 20140202 | 9
I know how to group by site, or select distinct site, but not sure how to narrow it down to their latest dates.
edit: this should be dynamic for thousands of distinct sites.

You can do it with a union
(SELECT
site,date,score
FROM
`table`
WHERE
site = 'a'
ORDER BY date DESC
LIMIT 0,1)
UNION
(SELECT
site,date,score
FROM
`table`
WHERE
site = 'b'
ORDER BY date DESC
LIMIT 0,1)
A little more convoluted if you don't want to write a UNION
SELECT
t.site, t.date, t.score
FROM
`table` t
JOIN (
SELECT ti.site,MAX(ti.date) AS dt
FROM `table` ti
GROUP BY ti.site
) t2 ON t2.site = t.site and t2.dt = t.date

Related

Group by ordered date

I have the following simple table:
id | patient_id | case_number | created_at
1 | 1 | x | 2021-02-25 10:57:24
2 | 1 | y | 2021-02-25 10:59:24
3 | 2 | z | 2021-02-25 10:57:14
4 | 2 | w | 2021-02-25 10:57:29
I want to get for each patient_id, its most recent case_number.
Meaning, final result of sql query I want is:
patient_id | case_number
1 | y
2 | w
This is what I've been trying:
SELECT *
FROM (SELECT patient_id, case_number FROM my_table ORDER BY created_at DESC) AS TEMP
GROUP BY patient_id
But this state returns:
patient_id | case_number
1 | x
2 | z
How to fix it?
If your mysql version didn't support Row_number window function, You can try to use self join
SELECT t1.patient_id ,t2.case_number
FROM (
SELECT MAX(created_at) latestDate,
patient_id
FROM my_table
GROUP BY patient_id
) t1 INNER JOIN my_table t2
ON t1.patient_id = t2.patient_id AND t1.latestDate = t2.created_at
From your comment, your MySQL version might be supporting ROW_NUMBER window function, you can use that to get the number which is the most recent date.
SELECT t1.patient_id,t1.case_number
FROM (
SELECT patient_id,
case_number,
ROW_NUMBER() OVER(PARTITION BY patient_id ORDER BY created_at DESC) rn
FROM my_table
) t1
WHERE rn = 1
Use window function FIRST_VALUE():
SELECT DISTINT patient_id,
FIRST_VALUE(case_number) OVER (PARTITION BY patient_id ORDER BY created_at DESC) case_number
FROM my_table
try this instead but still need to select created_time:
select distinct patient_id,case_number,time(created_time) from patients order by time(created_time) desc limit 2;

Get DateTime corresponding to the lowest Time

I'd like to get the Date & ID which corresponds to the lowest and Largest Time, respectively the extreme rows in the table below with ID 5 & 4.
Please note the following:
Dates are stored as values in ms
The ID reflects the Order By Date ASC
Below I have split the Time to make it clear
* indicates the two rows to return.
Values should be returns as columns, i.e: SELECT minID, minDate, maxID, maxDate FROM myTable
| ID | Date | TimeOnly |
|----|---------------------|-----------|
| 5 | 14/11/2019 10:01:29 | 10:01:29* |
| 10 | 15/11/2019 10:01:29 | 10:01:29 |
| 6 | 14/11/2019 10:03:41 | 10:03:41 |
| 7 | 14/11/2019 10:07:09 | 10:07:09 |
| 11 | 15/11/2019 12:01:43 | 12:01:43 |
| 8 | 14/11/2019 14:37:16 | 14:37:16 |
| 1 | 12/11/2019 15:04:50 | 15:04:50 |
| 9 | 14/11/2019 15:04:50 | 15:04:50 |
| 2 | 13/11/2019 18:10:41 | 18:10:41 |
| 3 | 13/11/2019 18:10:56 | 18:10:56 |
| 4 | 13/11/2019 18:11:03 | 18:11:03* |
In earlier versions of MySQL, you can use couple of inline queries. This is a straight-forward option that could be quite efficient here:
select
(select ID from mytable order by TimeOnlylimit 1) minID,
(select Date from mytable order by TimeOnly limit 1) minDate,
(select ID from mytable order by TimeOnly desc limit 1) maxID,
(select Date from mytable order by TimeOnly desc limit 1) maxDate
One option for MySQL 8+, using ROW_NUMBER with pivoting logic:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY TimeOnly) rn_min,
ROW_NUMBER() OVER (ORDER BY Date TimeOnly) rn_max
FROM yourTable
)
SELECT
MAX(CASE WHEN rn_min = 1 THEN ID END) AS minID,
MAX(CASE WHEN rn_min = 1 THEN Date END) AS minDate
MAX(CASE WHEN rn_max = 1 THEN ID END) AS maxID,
MAX(CASE WHEN rn_max = 1 THEN Date END) AS maxDate
FROM cte;
Here is an option for MySQL 5.7 or earlier:
SELECT
MAX(CASE WHEN pos = 1 THEN ID END) AS minID,
MAX(CASE WHEN pos = 1 THEN Date END) AS minDate
MAX(CASE WHEN pos = 2 THEN ID END) AS maxID,
MAX(CASE WHEN pos = 2 THEN Date END) AS maxDate
FROM
(
SELECT ID, Date, 1 AS pos FROM yourTable
WHERE TimeOnly = (SELECT MIN(TimeOnly) FROM yourTable)
UNION ALL
SELECT ID, Date, 2 FROM yourTable
WHERE TimeOnly = (SELECT MAX(TimeOnly) FROM yourTable)
) t;
This second 5.7 option uses similar pivoting logic, but instead of ROW_NUMBER is uses subqueries to identify the min and max records. These records are brought together using a union, along with an identifier to keep track of which record be min/max.
You could simply do this:
SELECT minval.ID, minval.Date, maxval.ID, maxval.Date
FROM (
SELECT ID, Date
FROM t
ORDER BY CAST(Date AS TIME)
LIMIT 1
) AS minval
CROSS JOIN (
SELECT ID, Date
FROM t
ORDER BY CAST(Date AS TIME) DESC
LIMIT 1
) AS maxval
If you want two rows then change CROSS JOIN query to a UNION ALL query.
Demo on db<>fiddle

Get Previous and Next record from database and loop them

I have a table with IDs from 1 to 8. I want something like this
If i'm on 4, I should get 3,5
If i'm in on 1, I should get 8,2
If in on 8, I should get 7, 1
Basically looping through the table records
This is my current code
-- previous or last, if there is no previous
SELECT *
FROM news
WHERE id < 1 OR id = MAX(id)
ORDER BY id DESC
LIMIT 1
-- next or first, if there is no next
SELECT *
FROM news
WHERE id > 1 OR id = MIN(id)
ORDER BY id ASC
LIMIT 1
But it says Invalid use of group function. Any help?
If id is sequential you can do this:
SQL DEMO
SELECT o.id,
COALESCE(b.id, (SELECT MAX(ID) FROM Table1)) as before_id,
COALESCE(a.id, (SELECT MIN(ID) FROM Table1)) as after_id
FROM Table1 o
LEFT JOIN Table1 b
ON o.id = b.id + 1
LEFT JOIN Table1 a
ON o.id = a.id - 1
ORDER BY o.id
OUTPUT
| id | before_id | after_id |
|----|-----------|----------|
| 1 | 8 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 4 |
| 4 | 3 | 5 |
| 5 | 4 | 6 |
| 6 | 5 | 7 |
| 7 | 6 | 8 |
| 8 | 7 | 1 |
If ids are not sequential you need use row_number() (mysql ver 8+) or session variables to create a sequence.
I guess that you want to show "prev" and "next" buttons, when the user views a news article. I would get the previous and the next ID in the main query, when you fetch the article data:
select n.*, -- select columns you need
coalesce(
(select max(n1.id) from news n1 where n1.id < n.id ),
(select max(id) from news)
) as prev_id,
coalesce(
(select min(n1.id) from news n1 where n1.id > n.id ),
(select min(id) from news)
) as next_id
from news n
where n.id = ?
db-fiddle demo
Now you can use prev_id and next_id for your buttons, or prefetch the corresponding articles with a simple select * from news where id = ? query.
You can remove the filtering in your approach and add logic to the ORDER BY:
(SELECT n.*
FROM news
ORDER BY (id < 1), id DESC
LIMIT 1
) UNION ALL
(SELECT n.*
FROM news
ORDER BY (id > 1), id ASC
LIMIT 1
) ;
If you want the id values in one row, you can use aggregation:
select coalesce(max(case when id < 1 then id end), max(id)) as prev_id,
coalesce(min(case when id > 1 then id end), min(id)) as next_id
from news n;
In both cases, 1 is a sample id and the "1" can be replaced with any value.

MySQL count rows within the same intervals to eachother

I have a table where one column is the date:
+----------+---------------------+
| id | date |
+----------+---------------------+
| 5 | 2012-12-10 10:12:37 |
+----------+---------------------+
| 4 | 2012-12-10 09:09:55 |
+----------+---------------------+
| 3 | 2012-12-09 21:12:35 |
+----------+---------------------+
| 2 | 2012-12-09 20:15:07 |
+----------+---------------------+
| 1 | 2012-12-09 20:01:42 |
+----------+---------------------+
What I need, is to count the rows which are for example whitin 3 hours to each other. In this example I want to join the upper row with the 2nd row, and the 3rd row with the 4th and 5th rows. So my output should be like this:
+----------+---------------------+---------+
| id | date | count |
+----------+---------------------+---------+
| 5 | 2012-12-10 10:12:37 | 2 |
+----------+---------------------+---------+
| 3 | 2012-12-09 21:12:35 | 3 |
+----------+---------------------+---------+
How could I do this?
I think you need a self-join for this:
select t.id, t.date, COUNT(t2.id)
from t left outer join
t t2
on t.date between t2.date - interval 3 hour and t2.date + interval 3 hour
group by t.id, t.date
(This is untested code so it might have a syntax error.)
If you are trying to divide everything into 3-hour intervals, you can do something like:
select max(t.date), t.id, count(*)
from (select t.*,
(date(date)*100 + floor(hour(date)/3)*3) as interval
from t
) t
group by interval
I am not sure how to do this with My SQL but i am able to build a set of queries in SQL Server 2005 which will provide the intended results. Here is the working sample, its very complex and may be overly complex but that's how i was able to get the desired result:
WITH BaseData AS
(
SELECT 5 AS ID, '2012-12-10 10:12:37' AS Date
UNION ALL
SELECT 4 AS ID, '2012-12-10 09:09:55' AS Date
UNION ALL
SELECT 3 AS ID, '2012-12-09 21:12:35' AS Date
UNION ALL
SELECT 2 AS ID, '2012-12-09 20:15:07' AS Date
UNION ALL
SELECT 1 AS ID, '2012-12-09 20:01:42' AS Date
),
BaseDataWithRowNum AS
(
SELECT ID,DATE, ROW_NUMBER() OVER (ORDER BY Date DESC) AS RowNum
FROM BaseData
),
InterRelatedDates AS
(
SELECT B1.RowNum AS RowNum1,B2.RowNum AS RowNum2
FROM BaseDataWithRowNum B1
INNER JOIN BaseDataWithRowNum B2
ON B1.Date BETWEEN B2.Date AND DATEADD(hh,3,B2.Date)
AND B1.RowNum < B2.RowNum
AND B1.ID != B2.ID
),
InterRelatedDatesWithinMultipleGroups AS
(
SELECT G1.RowNum1,G2.RowNum2
FROM InterRelatedDates G1
LEFT JOIN InterRelatedDates G2
ON G1.RowNum2 = G2.RowNum2
AND G1.RowNum1 != G2.RowNum1
)
SELECT BN.ID,
BN.Date,
CountExcludingOriginalGrouppingRecord +1 AS C
FROM
(
SELECT RowNum1 AS RowNum,COUNT(1) AS CountExcludingOriginalGrouppingRecord
FROM
(
-- If a row was used in only one group then it is ok. use as it is
SELECT D1.RowNum1
FROM InterRelatedDatesWithinMultipleGroups AS D1
WHERE D1.RowNum2 IS NULL
UNION ALL
-- In case a row was selected in two groups, choose the one with higher date
SELECT Min(D1.RowNum1)
FROM InterRelatedDatesWithinMultipleGroups AS D1
WHERE D1.RowNum2 IS NOT NULL
GROUP BY D1.RowNum2
) T
GROUP BY RowNum1
) T2
INNER JOIN BaseDataWithRowNum BN
ON BN.RowNum = T2.RowNum

Mysql Distinct gives a syntax error - remove duplicate results

With some help from someone here on Stackoverflow i almost got to the point where the query is perfect. I need some little help adjusting the query to fit my needs, and i hope someone could help me out again.
Here's the case, i have the following 2 tables :
Tweet
and
Tweet_tags
Every tweet has a tag, and a tweet can have more of the same tags. I want to count how many tweets have the same tag within a time span of week since the tweet was posted.
Here is the query as it is now :
SELECT t.id
, s.tag
, ( SELECT COUNT(1)
FROM twitter.tweet_tags r
JOIN twitter.tweet q
ON q.id = r.tweet_id
WHERE r.tag = s.tag
AND q.date >= t.date
AND q.date <= t.date + INTERVAL 7 DAY
) AS cnt
FROM twitter.tweet t
JOIN twitter.tweet_tags s
ON s.tweet_id = t.id
ORDER
BY cnt DESC
The results of this query are :
| ID | Tag | Cnt |
-------------------------------
| 1 | Testtag | 2 |
| 2 | Testtag | 1 |
| 3 | tweettag3 | 1 |
| 4 | tweettag2 | 1 |
I have the testtag 2 times in my database, so the first result is correct, tweettag3 and tweettag2 are in my database 1 time so thats good as well, but these will probably also show multiple results when i add them again. I tried using DISTINCT on s.tag to get rid of the duplicate result, however this gives me a syntax error.
So how i want it to be is :
| ID | Tag | Cnt |
-------------------------------
| 1 | Testtag | 2 |
| 2 | tweettag3 | 1 |
| 3 | tweettag2 | 1 |
Could someone please help me out on this? If you need more information please say so!
Thanks !!
Edit :
This is how the tables look:
Tweet
---------------
ID
Message
users_id
Date
Tweet_tags
---------------
id
tag
tweet_id
You can just use select distinct, so the query would be:
SELECT distinct s.tag
, ( SELECT COUNT(1)
FROM twitter.tweet_tags r
JOIN twitter.tweet q
ON q.id = r.tweet_id
WHERE r.tag = s.tag
AND q.date >= t.date
AND q.date <= t.date + INTERVAL 7 DAY
) AS cnt
FROM twitter.tweet t
JOIN twitter.tweet_tags s
ON s.tweet_id = t.id
ORDER
BY cnt DESC
Just remove the id field from the select.
If you want an id, then you can use the MySQL feature of hidden columns to have:
select t.id, s.tag, . . .
group by tag
order by cnt desc
Try this query
SELECT
t.id ,
s.tag ,
s.count
FROM twitter.tweet t
JOIN (
SELECT
COUNT(id) as count,
tweet_id,
tag
FROM tweet_tags
GROUP BY tweet_id
) as s ON s.tweet_id = t.id
AND t.tag = s.tag
ORDER BY cnt DESC