Group by ordered date - mysql

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;

Related

How to find recent N consecutive records in a table using SQL

I have following table definition with table name "JOBS" and sample data as below:
| job_name | created_on | result |
| -------- | ------------- |--------
| x | 2021-01-20 | fail |<-
| y | 2021-01-01 | success|
| y | 2021-01-02 | success|
| x | 2021-01-22 | fail |<-
| z | 2021-03-15 | fail |<-
| z | 2021-03-11 | success|
| z | 2021-03-16 | fail |<-
| x | 2021-01-18 | fail |
| z | 2021-01-03 | fail |
| x | 2021-01-19 | success|
I need to find recent consecutive jobs which are failing back to back N times sorted by descending order of date of creation. This is applicable for each job group in the table.
E.g. if N=3, I need to find job names with three recent back to back failures with date in descending order(recent dates).
For above table, the result should be
| job_name | created_on | result|
| -------- | -------------- |
| x | 2021-01-22 | fail |
| x | 2021-01-20 | fail |
| z | 2021-03-16 | fail |
| z | 2021-03-15 | fail |
In Above case, N=2(i.e number of consecutive job failures for each job group)
I tried using lag, lead, partition by, but couldn't find the exact solution.
I tried below query for N=2.
select distinct(m.job_name)
from (select t.*,
lag(result) over (partition by job_name order by created_on) as prev_result,
lead(result) over (partition by job_name order by created_on) as next_result
from JOBS AS t
order by created_on DESC
) m
where result = 'fail' and
'fail' in (prev_result, next_result)
order by m.job_name;
The problem could be divided into three parts and could be solved progressively.
1 - Get most recent success result dates for each job_name, so that jobs after that dates could be fetched.
SELECT j2.job_name, max(j2.created_on) mdate
FROM jobs j2
WHERE j2.result = 'success'
GROUP BY j2.job_name
2 - Get all rows that have failed status and whose created_by is after last successful job.
WITH last_success_date AS (
SELECT j2.job_name, max(j2.created_on) mdate FROM jobs j2
WHERE j2.result = 'success'
GROUP BY j2.job_name
)
SELECT j.* FROM jobs j
LEFT JOIN last_success_date lsd ON j.job_name = lsd.job_name
WHERE j.result = 'fail' AND j.created_on > lsd.mdate
3- Get only results that have more than n successive failed results
WITH failed_jobs AS (
WITH last_success_date AS (
SELECT j2.job_name, max(j2.created_on) mdate FROM jobs j2
WHERE j2.result = 'success'
GROUP BY j2.job_name
)
SELECT j.* FROM jobs j
LEFT JOIN last_success_date lsd ON j.job_name = lsd.job_name
WHERE j.result = 'fail' AND j.created_on > lsd.mdate
)
SELECT fj.* FROM failed_jobs fj
LEFT JOIN (
SELECT fj2.job_name, count(fj2.id) fail_count FROM failed_jobs fj2
GROUP BY fj2.job_name
) AS with_counts on with_counts.job_name = fj.job_name
WHERE fail_count >= 2 ORDER BY fj.created_on DESC
I used n=2 in above example.
db fiddle link
You can do this with a correlated subquery. Assuming success and fail are the only values:
select t.*
from t
where t.created_on > all (select t2.created_on
from t t2
where t2.job_name = t.job_name and
t2.result <> 'fail'
);
An alternative using window functions gets the maximum created_on for a "success" and uses that for filtering:
select t.*
from (select t.*,
max(case when result = 'success' then created_on end) over (partition by job_name) as max_success_created_on
from t
) t
where created_on > max_success_created_on or max_success_created_on is null;
Here is a db<>fiddle.
WITH
cte1 AS ( SELECT *,
SUM(result = 'success') OVER (PARTITION BY job_name ORDER BY created_on) grp
FROM jobs ),
cte2 AS ( SELECT job_name, grp, COUNT(*) cnt
FROM cte1
WHERE result = 'fail'
GROUP BY job_name, grp
HAVING cnt >= #N )
SELECT cte1.*
FROM cte1
JOIN cte2 USING (job_name, grp)
WHERE cte1.result = 'fail';
fiddle with some explanations.
This query return only the last two consecutive result "fail" rows for each job:
In first query "jobs_fail_groups" I mark rows of consecutive jobs with the same result with a number (each group with a number calculated).
Then in second query "jobs_fail_groups_selected" I GROUP BY job_name, grupo selecting only rows with result 'fail' and groups with count(*) >= 2.
In third query "result" I JOIN previous queries USING job_name and grupo and calculate row number for each job.
Finally I get the first two rows of each group.
WITH jobs_fail_groups AS (SELECT
*,
ROW_NUMBER() OVER (ORDER BY job_name, created_on DESC) -
ROW_NUMBER() OVER (PARTITION BY job_name, result ORDER BY job_name, created_on DESC) AS grupo
FROM jobs
ORDER BY job_name, created_on DESC),
jobs_fail_groups_selected AS (
SELECT job_name, grupo, count(*)
FROM jobs_fail_groups
WHERE result = 'fail'
GROUP BY job_name, grupo
HAVING count(*) >= 2),
result AS (SELECT
ROW_NUMBER() OVER (PARTITION BY job_name ORDER BY job_name, created_on DESC) AS `order`,
jfg.*
FROM jobs_fail_groups jfg
INNER JOIN jobs_fail_groups_selected jfgs USING (job_name, grupo))
SELECT r.job_name, r.created_on, r.result
FROM result r
WHERE `order` <= 2;
This query has some similarities with some of the other queries, but It is not completely the same.

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

How to select the latest price for product? [duplicate]

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 4 years ago.
Here is my table:
+----+------------+-----------+---------------+
| id | product_id | price | date |
+----+------------+-----------+---------------+
| 1 | 4 | 2000 | 2019-02-10 |
| 2 | 5 | 1600 | 2019-02-11 |
| 3 | 4 | 850 | 2019-02-11 |
| 4 | 5 | 1500 | 2019-02-13 |
+----+------------+-----------+---------------+
I need to get a list of unique product ids that are the latest (newest, in other word, bigger date) ones. So this is the expected result:
+------------+-----------+---------------+
| product_id | price | date |
+------------+-----------+---------------+
| 4 | 850 | 2019-02-11 |
| 5 | 1500 | 2019-02-13 |
+------------+-----------+---------------+
Any idea how can I achieve that?
Here is my query:
SELECT id, product_id, price, MAX(date)
FROM tbl
GROUP BY product_id
-- ot selects the max `date` with random price like this:
+------------+-----------+---------------+
| product_id | price | date |
+------------+-----------+---------------+
| 4 | 2000 | 2019-02-11 |
| 5 | 1600 | 2019-02-13 |
+------------+-----------+---------------+
-- See? Prices are wrong
You could use a correlated subquery
select t1.* from table t1
where t1.date=( select max(date) from table t2
where t1.product_id=t2.product_id
)
Select *from
table1 t1
where (t1.product_id, t1.date) in
(select t2.product_id, max(t2.date)
from table1 t2
where t1.product_id = t2.product_id
)
Don't use a GROUP BY. Use a filter:
SELECT id, product_id, price, MAX(date)
FROM tbl
WHERE tbl.date = (SELECT MAX(t2.date)
FROM tbl t2
WHERE t2.product_id = tbl.product_id
);
With an index on (product_id, date), this is probably the fastest method.
If you can have duplicates on a given date, you can resolve them with:
SELECT id, product_id, price, MAX(date)
FROM tbl
WHERE tbl.id = (SELECT t2.id
FROM tbl t2
WHERE t2.product_id = tbl.product_id
ORDER BY t2.date DESC
LIMIT 1
);
My solution is with the analytic function first_value
SELECT distinct product_id,
first_value(price) over (partition by product_id order by date desc) last_price,
first_value(date) over (partition by product_id order by date desc) last_date
FROM tbl
Assuming that you are using a modern version of MySQL (8.0), you can use this:
select *
from (
SELECT id
, product_id
, price
, date
, row_number() over (partition by product_id order by date desc) rn
FROM tbl
) a
where rn = 1

count number of rows and get only the last row from a table

I have a table called employeexam which structure and data are like this:
--------------------------------------------------------
| id | course_id | employee_id | degree | date
--------------------------------------------------------
| 1 | 1 | 3 | 8 | 2013-01-14
| 2 | 2 | 4 | 15 | 2013-01-14
| 3 | 2 | 4 | 17 | 2013-01-15
--------------------------------------------------------
Desired result would be:
---------------------------------------------------------------------------
| id | course_id | employee_id | degree | date | numOfTakingExams
---------------------------------------------------------------------------
| 1 | 1 | 3 | 8 | 2013-01-14 | 1
| 3 | 2 | 4 | 17 | 2013-01-15 | 2
---------------------------------------------------------------------------
My MySQL query:
SELECT DISTINCT(employeexam.employee_id) as employeeid,
employeexam.*,
exam.numOfTakingExams
FROM employeexam
JOIN (
SELECT employee_id , COUNT(employee_id ) as numOfTakingExams
FROM employeexam
GROUP BY employee_id
) exam
ON exam.employee_id = employeexam.employee_id
ORDER BY employeexam.id DESC
This outputs numOfTakingExams value correctly, but i can't select only the data of the last time he entered an exam. Any help?
SELECT a.*, b.numOfTakingExams
FROM employeeExam a
INNER JOIN
(
SELECT employee_id,
MAX(date) max_Date,
COUNT(*) numOfTakingExams
FROM employeeExam
GROUP BY course_ID, employee_id
) b ON a.employee_id = b.employee_id AND
a.date = b.max_Date
SQLFiddle Demo
you can also get the latest record by the maximum ID if it is set as AUTO_INCREMENT, this query below yields the same result from the query above,
SELECT a.*, b.numOfTakingExams
FROM employeeExam a
INNER JOIN
(
SELECT employee_id,
MAX(id) max_Date,
COUNT(*) numOfTakingExams
FROM employeeExam
GROUP BY course_ID, employee_id
) b ON a.employee_id = b.employee_id AND
a.id = b.max_Date
SQLFiddle Demo
Try this query -
SELECT
t1.id, t1.course_id, t1.employee_id, t1.degree, t1.date, t2.numOfTakingExams
FROM
mployeexam t1
JOIN (
SELECT employee_id, MAX(date) date, COUNT(*) numOfTakingExams
FROM mployeexam
GROUP BY employee_id
) t2
ON t1.employee_id = t2.employee_id AND t1.date = t2.date
Have you tried a join with itself? In the first you select on the IDs containing the "last exams" and in the second you join the stuff that you need. Something along the lines of:
select A.* FROM
employeexam A INNER JOIN (
SELECT EMPLOYEE_ID, MAX(DATE)
FROM EMPLOYEEXAM
GROUP BY EMPLOYEE_ID
) B
ON A.EMPLOYEE_ID = B.EMPLOYEE_ID AND
A.DATE = B.DATE
Assuming of course the dates per Employee_id are unique.
SELECT x.*
, y.ttl
FROM employeexam x
JOIN
( SELECT course_id
, employee_id
, MAX(date) max_date
, COUNT(*) ttl
FROM employeexam
GROUP
BY course_id
,employee_id
) y
ON y.course_id = x.course_id
AND y.employee_id = x.employee_id
AND y.max_date = x.date;
SELECT ee.*, num_exams
FROM (
SELECT employee_id, COUNT(*) AS num_exams
FROM employeexam
GROUP BY
employee_id
) eed
JOIN employeeexam ee
ON ee.id =
(
SELECT id
FROM employeeexam eei
WHERE eei.employee_id = eed.employee_id
ORDER BY
eei.employee_id DESC, eei.date DESC, eei.id DESC
LIMIT 1
)
This will handle the case of multiple exams taken on one date correctly.

filter by row number id of specific item id

From MySQL - Get row number on select
I know how to get the row number / rank using this mysql query:
SELECT #rn:=#rn+1 AS rank, itemID
FROM (
SELECT itemID
FROM orders
ORDER BY somecriteria DESC
) t1, (SELECT #rn:=0) t2;
The result returns something like this:
+--------+------+
| rank | itemID |
+--------+------+
| 1 | 265 |
| 2 | 135 |
| 3 | 36 |
| 4 | 145 |
| 5 | 123 |
| 6 | 342 |
| 7 | 111 |
+--------+------+
My question is: How can I get the result in 1 simple SINGLE QUERY that returns items having lower rank than itemID of 145, i.e.:
+--------+------+
| rank | itemID |
+--------+------+
| 5 | 123 |
| 6 | 345 |
| 7 | 111 |
+--------+------+
Oracle sql query is also welcomed. Thanks.
An Oracle solution (not sure if it meets your criteria of "one simple single query"):
WITH t AS
(SELECT item_id, row_number() OVER (ORDER BY some_criteria DESC) rn
FROM orders)
SELECT t2.rn, t2.item_id
FROM t t1 JOIN t t2 ON (t2.rn > t1.rn)
WHERE t1.item_id = 145;
My assumption is no repeating values of item_id.
Attempting to put this in MySQL terms, perhaps something like this might work:
SELECT t2.rank, t2.itemID
FROM (SELECT #rn:=#rn+1 AS rank, itemID
FROM (SELECT itemID
FROM orders
ORDER BY somecriteria DESC), (SELECT #rn:=0)) t1 INNER JOIN
(SELECT #rn:=#rn+1 AS rank, itemID
FROM (SELECT itemID
FROM orders
ORDER BY somecriteria DESC), (SELECT #rn:=0)) t2 ON t2.rank > t1.rank
WHERE t1.itemID = 145;
Disclaimer: I don't work with MySQL much, and it's untested. The Oracle piece works.
SELECT #rn:=#rn+1 AS rank, itemID
FROM (
SELECT itemID
FROM orders
ORDER BY somecriteria DESC
) t1, (SELECT #rn:=0) t2
where rank >
(
select rank from
(
SELECT #rn:=#rn+1 AS rank, itemID
FROM
(
SELECT itemID
FROM orders
ORDER BY somecriteria DESC
) t1, (SELECT #rn:=0) t2
) x where itemID = 145
) y