Get DateTime corresponding to the lowest Time - mysql

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

Related

Show TOP 4 rows into 4 columns in SQL SELECT Query

I need to show values from one column in a SQL tabe into 4 columns according its top 4 values.
Example current table:
----------------------------------
ID | Name | Amount
1 | Test | 80
1 | Test2| 70
1 | Test3| 40
1 | Any | 25
1 | Any1 | 15
1 | Any2 | 12
1 | Any3 | 5
2 | TS1 | 70
2 | TS2 | 55
2 | TS3 | 30
2 | TS4 | 19
2 | Any | 11
--------------------------
Example expected SELECT Query result:
----------------------------------
ID | Col1 | Col2 | Col3 | Col4
1 | 80 | 70 | 40 | 25
2 | 70 | 55 | 30 | 19
----------------------------------
The issue here is to group the top 4 amount in 4 columns not considering names, just the numbers.
Is there some way to reach this result in table like that?
Please try this pseudocode. Where data retrieves as per given ordering in sample input or storing position in database.
SELECT t.id
, MAX(CASE WHEN row_num = 1 THEN t.amount END) col1
, MAX(CASE WHEN row_num = 2 THEN t.amount END) col2
, MAX(CASE WHEN row_num = 3 THEN t.amount END) col3
, MAX(CASE WHEN row_num = 4 THEN t.amount END) col4
FROM (SELECT id
, amount
, ROW_NUMBER() OVER (PARTITION BY id) row_num
FROM test) t
WHERE t.row_num <= 4
GROUP BY t.id;
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=8d3b9a3da33f9177f2bb6957ef08e21b
Also you can use amount in ORDER BY clause in descending order as per expected result if needed. Try this pseudocode then.
SELECT t.id
, MAX(CASE WHEN row_num = 1 THEN t.amount END) col1
, MAX(CASE WHEN row_num = 2 THEN t.amount END) col2
, MAX(CASE WHEN row_num = 3 THEN t.amount END) col3
, MAX(CASE WHEN row_num = 4 THEN t.amount END) col4
FROM (SELECT id
, amount
, ROW_NUMBER() OVER (PARTITION BY id ORDER BY amount DESC) row_num
FROM test) t
WHERE t.row_num <= 4
GROUP BY t.id;
FIRST STEP: Create Ranking (1 to 4) with order by DESC amount as TOP 4 will be highest to lowest
Second Step: USE MAX() on ranking to get all the rankings in one row rather than diagonally
WITH data as (
SELECT
ID,
AMOUNT,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC) RANKING
FROM [TABLE NAME]
)
SELECT
ID,
MAX(CASE WHEN RANKING = 1 THEN AMOUNT ELSE NULL END) COL_1,
MAX(CASE WHEN RANKING = 2 THEN AMOUNT ELSE NULL END) COL_2,
MAX(CASE WHEN RANKING = 3 THEN AMOUNT ELSE NULL END) COL_3,
MAX(CASE WHEN RANKING = 4 THEN AMOUNT ELSE NULL END) COL_4
FROM data
GROUP BY 1
Alternatively you can use window functionality to achieve this result.
Below is the query written in postgresql
select
id,col1,col2,col3,col4
from
(
SELECT
tbl.id
,tbl.amount AS col1 -- 1st
,LEAD(tbl.amount, 1) OVER (ORDER BY id ASC) as col2 --2nd
,LEAD(tbl.amount, 2) OVER (ORDER BY id ASC) as col3 --3rd
,LEAD(tbl.amount, 3) OVER (ORDER BY id ASC) as col4 --4th
,ROW_NUMBER() OVER (PARTITION BY id order by amount desc) rn
FROM
(
SELECT
id
, amount
, ROW_NUMBER() OVER (PARTITION BY id order by amount desc) rn
FROM <TABLE_NAME>
) tbl
WHERE 1=1 and tbl.rn <= 4 -- 4: variable
)tbl2
where 1=1 and tbl2.rn =1 -- 1: fixed
;
LEAD(column, n) returns column's value at the row n rows aer the current row

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;

MYSQL get First (MIN) and Last (MAX) order records grouped by user

I have a mysql table that stores all customer orders as follows (simplified for question):
+----------+---------------+------+
| Field | Type | Null |
+----------+---------------+------+
| id (pk) | int(10) | NO |
| cust_id | int(10) | NO |
| total | decimal(10,2) | NO |
| created | datetime) | NO |
+----------+---------------+------+
In one query, I wish to get each user's first ever order and the order total and their most recent order and that order total
So that I should have results like:
+----------+------------------+---------------+------------------+---------------+
| cust_id | first_ord_total | first_ord_date| last_ord_total | last_ord_date |
+----------+------------------+---------------+------------------+---------------+
| 123 | 150.48 | 2018-03-01 | 742.25 | 2020-05-19 |
| 456 | 20.99 | 2019-08-01 | 67.22 | 2020-09-17 |
| 789 | 259.99 | 2019-01-01 | 147.15 | 2020-08-31 |
+----------+------------------+---------------+------------------+---------------+
I seem to be able to get the first and last order dates using MIN and MAX but I can't link it back to also give the order total from that same order/record
I know this is possible but I'm struggling to get it right
If your version of MySql supports window functions, with MIN(), MAX() and FIRST_VALUE():
select distinct cust_id,
first_value(total) over (partition by cust_id order by created) first_order_total,
min(created) over (partition by cust_id) first_order_date,
first_value(total) over (partition by cust_id order by created desc) last_order_total,
max(created) over (partition by cust_id) last_order_date
from customers
Without window functions, use a query that retutns the first and the last order dates of each customer and join it to the table where you use conditional aggregation:
select c.cust_id,
max(case when c.created = t.min_created then c.total end) first_order_total,
max(case when c.created = t.min_created then c.created end) first_order_date,
max(case when c.created = t.max_created then c.total end) last_order_total,
max(case when c.created = t.max_created then c.created end) last_order_date
from customers c
inner join (
select cust_id, min(created) min_created, max(created) max_created
from customers
group by cust_id
) t on t.cust_id = c.cust_id and c.created in (t.min_created, t.max_created)
group by c.cust_id
On MySQL 8+, ROW_NUMBER comes in handy here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY cust_id ORDER BY created) rn_first,
ROW_NUMBER() OVER (PARTITION BY cust_id ORDER BY created DESC) rn_last
FROM orders
)
SELECT
cust_id,
MAX(CASE WHEN rn_first = 1 THEN total END) AS first_ord_total,
MAX(CASE WHEN rn_first = 1 THEN created END) AS first_ord_date,
MAX(CASE WHEN rn_last = 1 THEN total END) AS last_ord_total,
MAX(CASE WHEN rn_last = 1 THEN created END) AS last_ord_date
FROM cte
GROUP BY
cust_id;
The stategy here is to use ROW_NUMBER, per each customer, to identify the first and last transaction records, in a CTE. Then, we aggregate by customer to find the first and last total amounts and dates.

MySQL - Count Rows between two values in a Column repeatedly

I have a table like so
id | status | data | date
----|---------|--------|-------
1 | START | a4c | Jan 1
2 | WORKING | 2w3 | Dec 29
3 | WORKING | 2d3 | Dec 29
4 | WORKING | 3ew | Dec 26
5 | WORKING | 5r5 | Dec 23
6 | START | 2q3 | Dec 22
7 | WORKING | 32w | Dec 20
8 | WORKING | 9k5 | Dec 10
and so on...
What I am trying to do, is to get the number of 'WORKING' rows between two 'START' i.e.
id | status | count | date
----|---------|--------|-------
1 | START | 4 | Jan 1
6 | START | 2 | Dec 22
and so on ...
I am using MySql 5.7.28.
Highly appreciate any help/suggestion!
date is unusable in the example, try using id as an ordering column instead
select id, status,
(select count(*)
from mytable t2
where t2.id > t.id and t2.status='WORKING'
and not exists (select 1
from mytable t3
where t3.id > t.id and t3.id < t2.id and status='START')
) count,
date
from mytable t
where status='START';
Fiddle
Assuming id is safe then you can do this by finding the next id for each block (and assigning some dummy values) then grouping by next id
drop table if exists t;
create table t
(id int,status varchar(20), data varchar(3),date varchar(10));
insert into t values
( 1 , 'START' , 'a4c' , 'Jan 1'),
( 2 , 'WORKING' , '2w3' , 'Dec 29'),
( 3 , 'WORKING' , '2d3' , 'Dec 29'),
( 4 , 'WORKING' , '3ew' , 'Dec 26'),
( 5 , 'WORKING' , '5r5' , 'Dec 23'),
( 6 , 'START' , '2q3' , 'Dec 22'),
( 7 , 'WORKING' , '32w' , 'Dec 20'),
( 8 , 'WORKING' , '9k5' , 'Dec 10');
SELECT MIN(ID) ID,
'START' STATUS,
SUM(CASE WHEN STATUS <> 'START' THEN 1 ELSE 0 END) AS OBS,
Max(DATE) DATE
FROM
(
select t.*,
CASE WHEN STATUS = 'START' THEN DATE ELSE '' END AS DT,
COALESCE(
(select t1.id from t t1 where t1.STATUS = 'START' and t1.id > t.id ORDER BY T1.ID limit 1)
,99999) NEXTID
from t
) S
GROUP BY NEXTID;
+------+--------+------+--------+
| ID | STATUS | OBS | DATE |
+------+--------+------+--------+
| 1 | START | 4 | Jan 1 |
| 6 | START | 2 | Dec 22 |
+------+--------+------+--------+
2 rows in set (0.00 sec)
This is a form of gaps-and-islands problem -- which is simpler in MySQL 8+ using window functions.
In older versions, probably the most efficient method is to accumulate a count of starts to define groupings for the rows. You can do this using variables and then aggregate:
select min(id) as id, 'START' as status, sum(status = 'WORKING') as num_working, max(date) as date
from (select t.*, (#s := #s + (t.status = 'START')) as grp
from (select t.* from t order by id asc) t cross join
(select #s := 0) params
) t
group by grp
order by min(id);
Here is a db<>fiddle.
SELECT id, status, `count`, `date`
FROM ( SELECT #count `count`,
id,
status,
`date`,
#count:=(#status=status)*#count+1,
#status:=status
FROM test,
( SELECT #count:=0, #status:='' ) init_vars
ORDER BY id DESC
) calculations
WHERE status='START'
ORDER BY id
> Since I am still in design/development I can move to MySQL 8 if that makes it easier for this logic? Any idea how this could be done with Windows functions? – N0000B
WITH cte AS ( SELECT id,
status,
`date`,
SUM(status='WORKING') OVER (ORDER BY id DESC) workings
FROM test
ORDER BY id )
SELECT id,
status,
workings - COALESCE(LEAD(workings) OVER (ORDER BY id), 0) `count`,
`date`
FROM cte
WHERE status='START'
ORDER BY id
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