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