How to get lowest value from table - mysql

Problem 1
How can I get lowest value from table (not null), for ID_CAR? For example, for ID_CAR 1 lowest value is 50, for ID_CAR 2 lowest value is 50 and for ID_CAR 3 lowest value is 300. I don't need duplicates, I need only one value for one car.
ID_CAR | col_1 | col_2 | col_3 | col_4 | col_5 | col_6
1 | null | 250 | 300 | null | 900 | null
2 | 100 | null | 300 | 600 | 200 | 100
1 | 300 | 100 | 800 | 100 | 50 | 900
3 | 300 | 4000 | null | null | null | null
2 | null | null | null | 50 | null | 100
4 | 400 | 900 | 500 | 700 | 800 | 500
Problem 2
In this example, values in col_* are days. I need to add days to col_date and get lowest. For example lowest date for ID_CAR 1 is 2018-01-03 (col_2) and for ID_CAR 2 is 2018-01-15 (col_4).
ID_CAR | col_1 | col_2 | col_3 | col_4 | col_5 | col_6 | col_date
1 | null | 2 | 3 | null | 5 | null | 2018-01-01
2 | 1 | null | 3 | 6 | 10 | 10 | 2018-01-13
1 | 3 | 20 | 80 | 10 | 50 | 90 | 2018-01-02
3 | 30 | 40 | null | null | null | null | 2018-01-03
2 | null | null | null | 5 | null | 10 | 2018-01-10
4 | 10 | 9 | 5 | 70 | 8 | 50 | 2018-01-07

Without union you can simply combine least and min function :
select
ID_CAR,min(least(col_1,col_2,col_3,col_4,col_5,col_6)) lowest_value
from
table
group by
ID_CAR
or if you have nullvalues you need ifnull or coalesce function
select
ID_CAR,
min(least(
ifnull(col_1,~0),
ifnull(col_2,~0),
ifnull(col_3,~0),
ifnull(col_4,~0),
ifnull(col_5,~0),
ifnull(col_6,~0)
)) as lowest_value
from
table
group by
ID_CAR
~0 is the max bigint in mysql
The opposite function of least is greatest
The opposite function of min is max ;-)
Works with Mysql, Oracle, Postgres, Hive ...
Problem 2, something like this :
select
ID_CAR,
min(least(
DATE_ADD(col_date, INTERVAL ifnull(col_1,0) DAY),
DATE_ADD(col_date, INTERVAL ifnull(col_2,0) DAY),
DATE_ADD(col_date, INTERVAL ifnull(col_3,0) DAY),
DATE_ADD(col_date, INTERVAL ifnull(col_4,0) DAY),
DATE_ADD(col_date, INTERVAL ifnull(col_5,0) DAY),
DATE_ADD(col_date, INTERVAL ifnull(col_6,0) DAY)
)) as lowest_date
from
table
group by
ID_CAR
or like this (except if all columns can be null):
select
ID_CAR,
DATE_ADD(col_date, INTERVAL min(least(
ifnull(col_1,~0),
ifnull(col_2,~0),
ifnull(col_3,~0),
ifnull(col_4,~0),
ifnull(col_5,~0),
ifnull(col_6,~0)
)) DAY) as lowest_date
from
table
group by
ID_CAR

You need UNION :
select id_car, min(val) as lowest_value
from (select id_car, col_1 as Val
from table union
select id_car, col_2
from table
. . .
) t
group by id_car;

The following query will give you the required result
select tab.ID_CAR, min(tab.val) as lowest_value from
(
(select ID_CAR,min(col_1) val
from table
group by ID_CAR)
union
(select ID_CAR,min(col_2) val
from table
group by ID_CAR)
union
(select ID_CAR,min(col_3) val
from table
group by ID_CAR)
union
(select ID_CAR,min(col_4) val
from table
group by ID_CAR)
union
(select ID_CAR,min(col_5) val
from table
group by ID_CAR)
union
(select ID_CAR,min(col_6) val
from table
group by ID_CAR)
) tab
group by tab.ID_CAR

Try this
If you are expecting values greater than 9999999999999999999, then use a higher values
select id_car,
min(least(coalesce(col_1,9999999999999999999),coalesce(col_2,9999999999999999999),coalesce(col_3,9999999999999999999),
coalesce(col_4,9999999999999999999),coalesce(col_5,9999999999999999999),coalesce(col_6,9999999999999999999)
)
) as min_val
from your_table
group by id_car

The naive approach would be using least:
SELECT ID_CAR, least(t.col_1, t.col_2, t.col_3, t.col_4, t.col_5, t.col_6)
FROM
(SELECT ID_CAR, min(col_1) as col_1, min(col_2) as col_2, min(col_3) as col_3, min(col_4) as col_4, min(col_5) as col_5, min(col_6) as col_6
FROM YOUR_TABLE GROUP BY ID_CAR) t;
However: If ANY argument to LEAST is NULL, it'll return NULL. You'll either need to convert the NULLs to a high value (which is a hack but will work in practice, see other answers for this).
Which means doing something like this:
SELECT ID_CAR, LEAST(col_1, col_2, col_3,
col_4, col_5, col_6) as l
FROM
(SELECT ID_CAR,
IFNULL(min(col_1), 9999) as col_1,
IFNULL(min(col_2), 9999) as col_2,
IFNULL(min(col_3), 9999) as col_3,
IFNULL(min(col_4), 9999) as col_4,
IFNULL(min(col_5), 9999) as col_5,
IFNULL(min(col_6), 9999) as col_6
FROM YOUR_TABLE GROUP BY ID_CAR) t;
However, it might be good to use a trick to convert your table
into a three row table of the form:
car_id | attr | value
1 1 NULL ; or use strings such as "size"
1 2 250

Related

Set limit for IN condition element that evaluate true

table: t
+--------------+-----------+-----------+
| Id | price | Date |
+--------------+-----------+-----------+
| 1 | 30 | 2021-05-09|
| 1 | 24 | 2021-04-26|
| 1 | 33 | 2021-04-13|
| 2 | 36 | 2021-04-18|
| 3 | 15 | 2021-04-04|
| 3 | 33 | 2021-05-06|
| 4 | 46 | 2021-02-16|
+--------------+-----------+-----------+
I want to select rows where id is 1,2,4 and get maximum 2 row for each id by date descending order.
+--------------+-----------+-----------+
| Id | price | Date |
+--------------+-----------+-----------+
| 1 | 30 | 2021-05-09|
| 1 | 24 | 2021-04-26|
| 2 | 36 | 2021-04-18|
| 4 | 46 | 2021-02-16|
+--------------+-----------+-----------+
Something like:
Select * from t where Id IN ('1','2','4') limit 2 order by Date desc;
this will limit the overall result fetched.
Use row_number():
select id, price, date
from (select t.*,
row_number() over (partition by id order by date desc) as seqnum
from t
where id in (1, 2, 4)
) t
where seqnum <= 2;
Probably the most efficient method is a correlated subquery:
select t.*
from t
where t.id in (1, 2, 4) and
t.date >= coalesce( (select t2.date
from t t2
where t2.id = t.id
order by t2.date desc
limit 1,1
), t.date
);
For performance, you want an index on (id, date). Also, this can return duplicates if there are multiple rows for a given id on the same date.
Here is a db<>fiddle.

MySQL: How to select rows from one table between each interval taken from other table

There are tables:
1.current_table:
date value
02.10.2019 1
03.10.2019 2
04.10.2019 2
05.10.2019 -1
06.10.2019 1
07.10.2019 1
08.10.2019 2
09.10.2019 2
10.10.2019 -1
11.10.2019 2
12.10.2019 1
2.intervals
date_start date_end
02.10.2019 04.10.2019 3
06.10.2019 09.10.2019 4
11.10.2019 12.10.2019 2
"intervals" table contains maximum length of an uninterrupted sequence of positive values.
How to select rows from "current_table" between each interval taken from "intervals" table (there are many of such intervals)?
So result should be:
date value
02.10.2019 1
03.10.2019 2
04.10.2019 2
06.10.2019 1
07.10.2019 1
08.10.2019 2
09.10.2019 2
11.10.2019 2
12.10.2019 1
My first inclination is simply:
select t1.*
from table1 t1
where t1.value > 0;
Perhaps your intervals might overlap. Or you might want to filter only for intervals in the second table. If so, then exists is handy:
select t1.*
from table1 t1
where t1.value > 0 and
exists (select 1
from table2 t2
where t1.date between t2.date_start and t2.date_end
);
This is overkill for your sample data, though.
Join the tables.
Only the rows that belong to an interval in table intervals will be returned:
select t.*
from current_table t inner join intervals i
on t.date between i.date_start and i.date_end
See the demo.
Or with EXISTS:
select t.*
from current_table t
where exists (
select 1 from intervals i
where t.date between i.date_start and i.date_end
)
See the demo.
Results:
| date | value |
| ---------- | ----- |
| 2019-02-10 | 1 |
| 2019-03-10 | 2 |
| 2019-04-10 | 2 |
| 2019-06-10 | 1 |
| 2019-07-10 | 1 |
| 2019-08-10 | 2 |
| 2019-09-10 | 2 |
| 2019-11-10 | 2 |
| 2019-12-10 | 1 |

MySQL split multiple value into multiple rows

I need one help on splitting multiple values from multiple columns into another column. Below is an example
CREATE TABLE split
(
`Col_1` VARCHAR(120),
`Col_2` VARCHAR(50),
`Col_3` VARCHAR(20),
`Col_4` VARCHAR(50)
);
Insert into split (Col_1,Col_2,Col_3,Col_4) values ('ABC','1','10',null);
Insert into split (Col_1,Col_2,Col_3,Col_4) values ('DEF','2,3','30,40',null);
Insert into split (Col_1,Col_2,Col_3,Col_4) values ('GHI','4,5','50','500,600,700');
select * from split;
+-------+-------+-------+-------------+
| Col_1 | Col_2 | Col_3 | Col_4 |
+-------+-------+-------+-------------+
| ABC | 1 | 10 | NULL |
| DEF | 2,3 | 30,40 | NULL |
| GHI | 4,5 | 50 | 500,600,700 |
+-------+-------+-------+-------------+
I am no expert in this, but have been playing around and have managed to split only col_2 into multiple rows as below:
SELECT
Col_1,Col_2,Col_3,Col_4,
SUBSTRING_INDEX(SUBSTRING_INDEX(split.Col_2, ',', numbers.n), ',', -1) Col_2_NEW,
SUBSTRING_INDEX(SUBSTRING_INDEX(split.Col_3, ',', numbers.n), ',', -1) Col_3_NEW,
SUBSTRING_INDEX(SUBSTRING_INDEX(split.Col_4, ',', numbers.n), ',', -1) Col_4_NEW
FROM
(SELECT 1 n UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4) numbers INNER JOIN split
ON CHAR_LENGTH(split.Col_2) - CHAR_LENGTH(REPLACE(split.Col_2, ',', ''))>=numbers.n-1
ORDER BY Col_2, n;
+-------+-------+-------+-------------+-----------+-----------+-----------+
| Col_1 | Col_2 | Col_3 | Col_4 | Col_2_NEW | Col_3_NEW | Col_4_NEW |
+-------+-------+-------+-------------+-----------+-----------+-----------+
| ABC | 1 | 10 | NULL | 1 | 10 | NULL |
| DEF | 2,3 | 30,40 | NULL | 2 | 30 | NULL |
| DEF | 2,3 | 30,40 | NULL | 3 | 40 | NULL |
| GHI | 4,5 | 50 | 500,600,700 | 4 | 50 | 500 |
| GHI | 4,5 | 50 | 500,600,700 | 5 | 50 | 600 |
+-------+-------+-------+-------------+-----------+-----------+-----------+
However, I would like to split, col_3 and col_4 into new as well, so it gives me below output.
+-------+-------+-------+-------------+-----------+-----------+-----------+
| Col_1 | Col_2 | Col_3 | Col_4 | Col_2_NEW | Col_3_NEW | Col_4_NEW |
+-------+-------+-------+-------------+-----------+-----------+-----------+
| ABC | 1 | 10 | NULL | 1 | 10 | NULL |
| DEF | 2,3 | 30,40 | NULL | 2 | 30 | NULL |
| DEF | 2,3 | 30,40 | NULL | 2 | 40 | NULL |
| DEF | 2,3 | 30,40 | NULL | 3 | 30 | NULL |
| DEF | 2,3 | 30,40 | NULL | 3 | 40 | NULL |
| GHI | 4,5 | 50 | 500,600,700 | 4 | 50 | 500 |
| GHI | 4,5 | 50 | 500,600,700 | 4 | 50 | 600 |
| GHI | 4,5 | 50 | 500,600,700 | 4 | 50 | 700 |
| GHI | 4,5 | 50 | 500,600,700 | 5 | 50 | 500 |
| GHI | 4,5 | 50 | 500,600,700 | 5 | 50 | 600 |
| GHI | 4,5 | 50 | 500,600,700 | 5 | 50 | 700 |
+-------+-------+-------+-------------+-----------+-----------+-----------+
I have searched all over and so far, they are splitting the row into one column only and have not been able to find problem something similar to mine.
Maybe some join is missing or some union, I don't know as I am not good at queries.
Can anyone help me here? without asking me to read guide or handbooks :-)
Thanks in advance
You can try one of the the recommendation in this thread here.
Something along the lines of
SELECT s.[Col_1], Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT [Col_1],
CAST ('<M>' + REPLACE([Col_2], ',', '</M><M>') + '</M>' AS XML) AS String
FROM split) AS s
CROSS APPLY String.nodes ('/M') AS Split(a);
iterated over your columns should work fine.
Edit: Didn't see that this is MySQL, sorry. See below for a working solution.
The following code should work for the first two columns.
1.) Create table:
CREATE TABLE split(
`Col_1` VARCHAR(120),
`Col_2` VARCHAR(50),
`Col_3` VARCHAR(20),
`Col_4` VARCHAR(50)
);
INSERT INTO split (Col_1,Col_2,Col_3,Col_4) values ('ABC','1','10',null);
INSERT INTO split (Col_1,Col_2,Col_3,Col_4) values ('DEF','2,3','30,40',null);
INSERT INTO split (Col_1,Col_2,Col_3,Col_4) values ('GHI','4,5','50','500,600,700');
which leads to
SELECT * FROM split;
Col_1 Col_2 Col_3 Col_4
ABC 1 10 (null)
DEF 2,3 30,40 (null)
GHI 4,5 50 500,600,700
2.) Split the strings in Col_2:
SELECT
split.Col_1,
SUBSTRING_INDEX(SUBSTRING_INDEX(split.Col_2, ',', numbers.n), ',', -1) Col_2,
Col_3,
Col_4
FROM
(select 1 n UNION ALL
select 2 UNION ALL select 3 UNION ALL
select 4 UNION ALL select 5) numbers INNER JOIN split
ON CHAR_LENGTH(split.Col_2)
-CHAR_LENGTH(REPLACE(split.Col_2, ',', ''))>=numbers.n-1
ORDER BY Col_1, Col_2;
3.) Result:
Col_1 Col_2 Col_3 Col_4
ABC 1 10 (null)
DEF 2 30,40 (null)
DEF 3 30,40 (null)
GHI 4 50 500,600,700
GHI 5 50 500,600,700
Here is a SQL fiddle with the above code: http://sqlfiddle.com/#!9/948fcb/4.
You should be able to iterate from there. Just comment this post if you need more guidance.
Important caveat: This works with up to 5 comma-separated strings in one column.
Solution is inspired by fthiella's answer to SQL split values to multiple rows.
Just wanted to answer with final query, which is giving me desired output as required.
Posting it here, as maybe it will help others.
SELECT distinct Col_1,Col_2,Col_3,Col_4,
SUBSTRING_INDEX(SUBSTRING_INDEX(t.Col_2, ',', n.n), ',', -1) Col_2_New,
SUBSTRING_INDEX(SUBSTRING_INDEX(t.Col_3, ',', n1.n), ',', -1) Col_3_New,
SUBSTRING_INDEX(SUBSTRING_INDEX(t.Col_4, ',', n2.n), ',', -1) Col_4_New
FROM split t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) c
ORDER BY n
) n,
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) c
ORDER BY n
) n1,
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 ) c
ORDER BY n
) n2
WHERE coalesce(n.n,'0') <= 1 + (LENGTH(coalesce(t.Col_2,'0')) - LENGTH(REPLACE(coalesce(t.Col_2,'0'), ',', '')))
AND coalesce(n1.n,'0') <= 1 + (LENGTH(coalesce(t.Col_3,'0')) - LENGTH(REPLACE(coalesce(t.Col_3,'0'), ',', '')))
AND coalesce(n2.n,'0') <= 1 + (LENGTH(coalesce(t.Col_4,'0')) - LENGTH(REPLACE(coalesce(t.Col_4,'0'), ',', '')))
ORDER BY 1,2,3,4,5,6,7 ;

To find the last value in the dataset of 15 minutes interval

ID Timestamp Value
1 11:59.54 10
1 12.04.00 20
1 12.12.00 31
1 12.16.00 10
1 12.48.00 05
I want the result set as
ID Timestamp Value
1 11:59.54 10
1 12:00:00 10
1 12.04.00 20
1 12.12.00 31
1 12:15:00 31
1 12:16.00 10
1 12:30:00 10
1 12:45:00 10
1 12.48.00 05
More coffee will probably lead to a simpler solution, but consider the the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,timestamp TIMESTAMP
,value INT NOT NULL
);
INSERT INTO my_table VALUES
(1 ,'11:59:54',10),
(2 ,'12:04:00',20),
(3 ,'12:12:00',31),
(4 ,'12:16:00',10),
(5 ,'12:48:00',05);
... in addition, I have a table of integers, that looks like this:
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
So...
SELECT a.timestamp
, b.value
FROM
( SELECT x.*
, MIN(y.timestamp) min_timestamp
FROM
( SELECT timestamp
FROM my_table
UNION
SELECT SEC_TO_TIME((i2.i*10+i1.i)*900)
FROM ints i1
, ints i2
WHERE SEC_TO_TIME((i2.i*10+i1.i)*900)
BETWEEN (SELECT MIN(timestamp) FROM my_table)
AND (SELECT MAX(timestamp) FROM my_table)
ORDER
BY timestamp
) x
LEFT
JOIN my_table y
ON y.timestamp >= x.timestamp
GROUP
BY x.timestamp
) a
JOIN my_table b
ON b.timestamp = min_timestamp;
+-----------+-------+
| timestamp | value |
+-----------+-------+
| 11:59:54 | 10 |
| 12:00:00 | 20 |
| 12:04:00 | 20 |
| 12:12:00 | 31 |
| 12:15:00 | 10 |
| 12:16:00 | 10 |
| 12:30:00 | 5 |
| 12:45:00 | 5 |
| 12:48:00 | 5 |
+-----------+-------+
The idea is as follows. Use SERIES_GENERATE() to generate the missing time stamps with the 15 minute intervals and and union it with the existing data your table T. Now you would want to use LAST_VALUE with IGNORE NULLS. IGNORE NULLS is not implemented in HANA, therefore you have to do a bit of a workaround. I use COUNT() as a window function to count the non null values. I do the same on the original data and then join both on the count. This way I repeat the last non-null value.
select X.ID, X.TIME, Y.VALUE from (
select ID, TIME, value,
count(VALUE) over (order by TIME rows between unbounded preceding and current row) as CNT
from (
--add the missing 15 minute interval timestamps
select 1 as ID, GENERATED_PERIOD_START as TIME, NULL as VALUE
from SERIES_GENERATE_TIME('INTERVAL 15 MINUTE', '12:00:00', '13:00:00')
union all
select ID, TIME, VALUE from T
)
) as X join (
select ID, TIME, value,
count(value) over (order by TIME rows between unbounded preceding and current row) as CNT
from T
) as Y on X.CNT = Y.CNT

Default values when 'grouping by' when there's no data produced by the query

Suppose I want to select the largest value for the timestamp < XYZ for a list of grps. E.g. given the following table table:
+---------------------+-------+-----+
| timestamp | value | grp |
+---------------------+-------+-----+
| 2012-02-01 00:00:00 | 1 | 3 |
+---------------------+-------+-----+
| 2012-02-02 00:00:00 | 2 | 3 |
+---------------------+-------+-----+
| 2012-01-01 00:00:00 | 3 | 4 |
+---------------------+-------+-----+
The query SELECT grp, max(value) FROM table WHERE timestamp <= '2012-02-01 00:00:00' AND grp IN (3, 4) GROUP by grp; results in
+-------+------------+
| grp | max(value) |
+-------+------------+
| 3 | 1 |
+-------+------------+
| 4 | 3 |
+-------+------------+
All good. By I want the query to return 0 for value if there's no data for the given timestamp range for a particular grp.
SELECT grp, max(value) FROM table WHERE timestamp <= '2012-01-01 00:00:00' AND grp IN (3, 4) GROUP by grp; results in only one row, containing no data for grp = 3. How do I write a query which would have zero for it instead, producing the following result?
+-------+------------+
| grp | max(value) |
+-------+------------+
| 3 | 0 |
+-------+------------+
| 4 | 3 |
+-------+------------+
One way is to join your groups to your maxes using an inline queries like so and use COALESCE
SELECT grps.grp,
COALESCE(values.val, 0) val
FROM (SELECT grp
FROM table
GROUP BY grp) grps
LEFT JOIN (SELECT grp,
Max(value) val
FROM table
WHERE timestamp <= '2012-02-01 00:00:00'
GROUP BY grp) values
ON grps.grp = values.grp
DEMO
If your groups are in a table you don't need inline queries at all just a good left join
SELECT
grps.grp,
COALESCE(max(value),0)
FROM
groups grps
left join `table` t
on grps.grp = t.grp
and timestamp <= '2012-02-01 00:00:00'
GROUP BY
grps.grp
DEMO