Simplify query to make TIMESTAMP column - mysql

I have a table with columns of 'year', 'month', 'day', 'hour' and 'minute' separately.
All columns INTEGER except 'month' (VARCHAR) and I added a new column to put everything together, 'date_time' (TIMESTAMP).
+------+-------+------+------+--------+-----------+
| year | month | day | hour | minute | date_time |
+------+-------+------+------+--------+-----------+
| 1987 | 12 | 15 | 9 | 25 | <NULL> |
| 1997 | 10 | 10 | 10 | 40 | <NULL> |
| 1994 | 08 | 9 | 6 | 30 | <NULL> |
+------+-------+------+------+--------+-----------+
I made a query to fill the 'date_time' column like this:
UPDATE dates
SET date_time =
COALESCE(
STR_TO_DATE(
CASE WHEN
CONCAT(
year,
'-',
CASE WHEN LPAD(month, 2, 0) BETWEEN 1 AND 12 THEN LPAD(month, 2, 0) ELSE NULL END,
'-',
CASE WHEN LPAD(day, 2, 0) BETWEEN 1 AND 31 THEN LPAD(day, 2, 0) ELSE NULL END,
' ',
CASE WHEN LPAD(hour, 2, 0) BETWEEN 0 AND 23 THEN LPAD(hour, 2, 0) ELSE NULL END,
':',
CASE WHEN LPAD(minute, 2, 0) BETWEEN 0 AND 59 THEN LPAD(minute, 2, 0) ELSE NULL END,
':00'
)
LIKE '%NULL%' THEN NULL
ELSE
CONCAT(
year,
'-',
CASE WHEN LPAD(month, 2, 0) BETWEEN 1 AND 12 THEN LPAD(month, 2, 0) ELSE NULL END,
'-',
CASE WHEN LPAD(day, 2, 0) BETWEEN 1 AND 31 THEN LPAD(day, 2, 0) ELSE NULL END,
' ',
CASE WHEN LPAD(hour, 2, 0) BETWEEN 0 AND 23 THEN LPAD(hour, 2, 0) ELSE NULL END,
':',
CASE WHEN LPAD(minute, 2, 0) BETWEEN 0 AND 59 THEN LPAD(minute, 2, 0) ELSE NULL END,
':00'
)
END,
'%Y-%m-%d %H:%i:%s'
),
'0000-00-00 00:00:00'
);
It worked but...
My question is if there's a simpler way to write a query to do the same thing.

Putting a number or string in the "YYYYMMDD" format in the DATE() function results in a DATE.
And the MAKETIME() function accepts hours, minutes and seconds to return a TIME.
Then when you concat a DATE and a TIME with a space in between, the resulting string can be put in a DATETIME or TIMESTAMP.
UPDATE dates
SET date_time = CONCAT(DATE(year*10000 + month*100 + day),' ',MAKETIME(hour,minute,0));
A test on db<>fiddle here

You can use STR_TO_DATE to convert a string into a datetime. The second argument is a format specifier, that you can use to describe the format of your input data. There are format specifiers available for some of your use cases, such as :
%c : Month in numeric e.g., 1, 2, 3…12
%e : Day of the month without leading zeros e.g., 1,2,…31
%k : Hour in 24-hour format without leading zero e.g., 0,1,2…23
I cannot see a format specifier for "minutes without leading zeros" and "seconds without leading zeros", but it does not seem to cause issue for MySQL to process them (I tested on MySQL 8.0).
So I guess that you could do :
UPDATE dates
SET datetime = STR_TO_DATE(
CONCAT(year, '-', month, '-', day, ' ', hour, ':', minute, ':', second),
'%Y-%c-%e %k:%i:%s'
)

Related

date format in oracle/mysql

Help me to populate below columns as date without using to_date or to_char functions.
day
month
year
1
2
1995
2
3
1998
5
6
2020
output
date
01-02-1995
02-03-1998
05-06-2020
If Oracle, then concatenation of zero left-padded values might do the job (see line #7):
SQL> with test (day, month, year) as
2 (select 1, 2, 1995 from dual union all
3 select 2, 3, 1998 from dual
4 )
5 select day, month, year,
6 --
7 lpad(day, 2, '0') ||'-'|| lpad(month, 2, '0') || '-'|| year as result
8 from test;
DAY MONTH YEAR RESULT
---------- ---------- ---------- ----------------------------------------------
1 2 1995 01-02-1995
2 3 1998 02-03-1998
SQL>
If you want query without to_date and to_char. Let try below query:
SELECT
LPAD(EXTRACT(DAY FROM DATE (year || '-' || month || '-' || day)),2,'0')
|| '-'
|| LPAD(EXTRACT(MONTH FROM DATE (year || '-' || month || '-' || day)),2,'0')
|| '-'
|| EXTRACT(YEAR FROM DATE (year || '-' || month || '-' || day))
FROM YOUR_TABLE;
You should use TO_DATE as that is what it is designed for.
However, as an academic exercise, if you start with DATE '0001-01-01' and then use, for Oracle, ADD_MONTHS (or for MySQL, TIMESTAMPADD) for the years and months and addition for the days:
In Oracle:
SELECT t.*,
TO_DATE(year || '-' || month || '-' || day, 'YYYY-MM-DD') AS dt,
ADD_MONTHS(DATE '0001-01-01', 12 * (year - 1) + (month - 1)) + (day - 1) AS dt2
FROM table_name t
Which, for the sample data:
CREATE TABLE table_name(day, month, year) AS
SELECT 1, 2, 1995 FROM DUAL UNION ALL
SELECT 2, 3, 1998 FROM DUAL UNION ALL
SELECT 5, 6, 2020 FROM DUAL;
Outputs:
DAY
MONTH
YEAR
DT
DT2
1
2
1995
1995-02-01 00:00:00
1995-02-01 00:00:00
2
3
1998
1998-03-02 00:00:00
1998-03-02 00:00:00
5
6
2020
2020-06-05 00:00:00
2020-06-05 00:00:00
fiddle
Or MySQL:
SELECT t.*,
TIMESTAMPADD(MONTH, 12 * (year - 1) + (month - 1), DATE '0001-01-01')
+ (day - 1) AS dt
FROM table_name t
Which outputs:
day
month
year
dt
1
2
1995
19950201
2
3
1998
19980302
5
6
2020
20200605
fiddle

mysql LAST_DAY() only reads 1 subquery result, how to process all results? using joins?

I have an insurance policies table like this:
+-------------------------------------------------------------+
| id | cancellation_val | cancellation_interval | expire_date |
+-------------------------------------------------------------+
| 1 | 30 | day | 2019-06-09 |
| 2 | 2 | month | 2019-12-01 |
+-------------------------------------------------------------+
I need to get the ids of the policies that are going to expire based on cancellation, from today and within 4 months, calculating the last day of the month, like this pseudo-code:
'today' <= LAST_DAY( expire_date - cancellation_val/interval ) < 'today + 4 months'
Being not a pro I think I should use JOINs but I don't know how, after days of trying the only thing I achieved was this:
SELECT LAST_DAY(
DATE_FORMAT(
STR_TO_DATE(
(SELECT CASE cancellation_interval
WHEN "day" THEN date_sub(expire_date, INTERVAL cancellation_val DAY)
WHEN "month" THEN date_sub(data_scadenzaexpire_date, INTERVAL cancellation_val MONTH)
END
AS newDate
FROM insurance WHERE id=2
), '%Y-%m-%d'
), '%Y-%m-%d'
)
)
This is working but I don't need the "WHERE id=2" clause (because I need to process ALL rows of the table), and if I remove it I got error "subquery returns more than 1 row".
So how I can proceed? And using the result to stay between 'today' AND 'today + 4 months' ?
I think with some kind of JOIN I could do it in a easier way but I don't know how.
Thank you all
The problem is the structure of the query, not the LAST_DAY function.
We want to return the id values of rows that meet some condition. So the query would be of the form:
SELECT t.id
, ...
FROM insurance t
WHERE ...
HAVING ...
Introducing another SELECT keyword basically introduces a subquery. There are restrictions on subqueries... in the SELECT list, a subquery can return a single column and (at most) a single row.
So let's ditch that extra SELECT keyword.
We can derive the newdate as an expression of the SELECT list, and then we can reference that derived column in the HAVING clause. The spec said we wanted to return the id value, so we include that in the SELECT list. We don't have to return any other columns, but for testing/debugging, it can be useful to return the values that were used to derive the newdate column.
Something like this:
SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
, t.expire_date
, t.cancellation_interval
, t.cancellation_val
FROM insurance t
HAVING newdate >= DATE(NOW())
AND newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY newdate ASC
We don't have to include the newdate in the SELECT list; we could just replace occurrences of newdate in the HAVING clause with the expression.
We could also use an inline view to "hide" the derivation of the newdate column
SELECT v.id
, v.newdate
FROM ( SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
FROM insurance t
) v
WHERE v.newdate >= DATE(NOW())
AND v.newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY v.newdate ASC
check this query: remove the HAVING Line to see all rows
SELECT
IF(cancellation_interval = 'day',
i.expire_date - INTERVAL i.`cancellation_val` DAY,
i.expire_date - INTERVAL i.`cancellation_val` MONTH
) as cancellation_day,
i.*
FROM `insurance` i
HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
SAMPLES
MariaDB [test]> SELECT IF(cancellation_interval = 'day', i.expire_date - INTERVAL i.`cancellation_val` DAY, i.expire_date - INTERVAL i.`cancellation_val` MONTH ) as cancellation_day, i.* FROM `insurance` i HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
+------------------+----+------------------+-----------------------+-------------+
| cancellation_day | id | cancellation_val | cancellation_interval | expire_date |
+------------------+----+------------------+-----------------------+-------------+
| 2019-05-10 | 1 | 30 | day | 2019-06-09 |
+------------------+----+------------------+-----------------------+-------------+
1 row in set (0.001 sec)
When you use a SELECT query as an expression, it can only return one row.
If you want to process all the rows, you need to call LAST_DAY() inside the query, not on the result.
SELECT *
FROM insurance
WHERE CURDATE() <= LAST_DAY(
expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH))
AND LAST_DAY(expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH)) < CURDATE + INTERVAL 4 MONTH

how to concatenate Date & Year into one column sql

i have Date in one column & year in column and i need to concatenate and get the answer as Jan-2017 and that should be in actual Month oder
SELECT CONCAT(column1, ',', column2) AS Date,
FROM table;
This is simple. I assume your both column data types are DateTime or Date. Try the following:
SELECT CONCAT(SUBSTRING(DATENAME(month, YourMonthColumn), 0,4), '-', Year(YourYearColumn))
FROM YourTableName
Sql Server 2008 compatible: (no concat() and no format())
Using a convert() style of 106 (or 113), and just taking the mon yyyy part, and replacing the ' ' with '-',
and using dates truncated to the month, so we can group and order by it using dateadd(month, datediff(month, 0, [Date] ),0):
select MonthYear = replace(stuff(convert(varchar(12)
,dateadd(month, datediff(month, 0, [Date]), 0)
,106),1,3,''),' ','-')
, Days = count(*)
from dates
group by dateadd(month, datediff(month, 0, [Date]), 0)
order by dateadd(month, datediff(month, 0, [Date]), 0)
rextester demo: http://rextester.com/DFF74396
returns:
+-----------+------+
| MonthYear | Days |
+-----------+------+
| Dec-2016 | 31 |
| Jan-2017 | 31 |
| Feb-2017 | 28 |
| Mar-2017 | 10 |
+-----------+------+
Alternate using datename(month,date) for the month and concatenating the year:
select MonthYear = left(datename(month,dateadd(month, datediff(month, 0, [Date]), 0)),3)
+ '-'
+ convert(char(4),year(dateadd(month, datediff(month, 0, [Date]), 0)))
, Days = count(*)
from dates
group by dateadd(month, datediff(month, 0, [Date]), 0)
order by dateadd(month, datediff(month, 0, [Date]), 0)

Add and Sub time in MySQL

In MySQL table I have these value 00:00:00 and 06:00:00.
I need sub three hours on 00:00:00, return 21:00:00 and add three hours on 06:00:00, return 09:00:00.
I have tried this sql query, without success.
Please help me, thank you in advance.
mysql> SELECT
SUBTIME('00:00:00', 3),
ADDTIME('06:00:00', 3);
+------------------------+------------------------+
| SUBTIME('00:00:00', 3) | ADDTIME('06:00:00', 3) |
+------------------------+------------------------+
| -00:00:03 | 06:00:03 |
+------------------------+------------------------+
1 row in set
mysql>
#Edit 1
mysql> SELECT
'00:00:00' - INTERVAL 3 HOUR,
'06:00:00' + INTERVAL 3 HOUR;
+------------------------------+------------------------------+
| '00:00:00' - INTERVAL 3 HOUR | '06:00:00' + INTERVAL 3 HOUR |
+------------------------------+------------------------------+
| NULL | NULL |
+------------------------------+------------------------------+
1 row in set
mysql>
#Edit 2
mysql> SELECT
DATE_ADD('00:00:00', INTERVAL 3 HOUR),
DATE_ADD('06:00:00', INTERVAL 3 HOUR);
SELECT
SUBTIME('00:00:00', '03:00:00'),
ADDTIME('06:00:00', '03:00:00');
+---------------------------------------+---------------------------------------+
| DATE_ADD('00:00:00', INTERVAL 3 HOUR) | DATE_ADD('06:00:00', INTERVAL 3 HOUR) |
+---------------------------------------+---------------------------------------+
| NULL | NULL |
+---------------------------------------+---------------------------------------+
1 row in set
+---------------------------------+---------------------------------+
| SUBTIME('00:00:00', '03:00:00') | ADDTIME('06:00:00', '03:00:00') |
+---------------------------------+---------------------------------+
| -03:00:00 | 09:00:00 |
+---------------------------------+---------------------------------+
1 row in set
mysql>
Since there is actually no time like '-00:00:03', you cannot apply time operation to do such thing.
If you really need to to this, I recommend you to
Change the column to INT type, and do the operation by yourself.
Change the column to DATETIME type, and try to deal with the cross-day problem by yourself.
Or apply the following...
SELECT CONCAT(
TIMESTAMPDIFF(
HOUR,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 24,
":",
TIMESTAMPDIFF(
MINUTE,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 60,
":",
TIMESTAMPDIFF(
SECOND,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 60
);
Just replace '00:00:00' and 3 hour to anything you want.
It just try to concat you time to a datetime string, and transform it into datetime then apply the interval operation. After that it apply the timediff to compute elapsed time by given unit(do not forget to % to get the correct domain). And finally concatenation the three differences.
This answer is kinda ugly, hope it is helpful to you.
It Should be
SELECT
TIME(SUBTIME(CONCAT(curdate(),' ','00:00:00'), 03:00:00)),
TIME(ADDTIME(CONCAT(curdate(),' ','00:00:00'), 03:00:00));

Compute based on the value of time in mysql

I'm trying to compute a column based on the value of time, i'm not exactly sure what's better to use if I should use an If condition or Case?
Here are my columns
| Time_In | Time_Out | Val1 | Val2 |
| 2014-07-19 04:00:04 | 2014-07-19 08:00:00 | 5 | 15 |
What i'm trying to do is if the time is if the time is between '08:00:00' to '17:00:00' (business hours) it will automatically multiply Val1 and Val2
If the time is '18:00:00' to '23:00:00' (after business hours)
The only step I'm able to get right now is the getting the time in the timestamp cause that's what i'll be comparing
`SELECT date_format(STR_TO_DATE(Time_In, '%Y-%m-%d %H:%i:%s'), '%H:%i:%s')
Output: 04:00:04
`SELECT date_format(STR_TO_DATE(Time_Out, '%Y-%m-%d %H:%i:%s'), '%H:%i:%s')
Output: 08:00:00
How should I construct this?
Thank You for the help
I tried it using the if condition first and this is working :)
SELECT
IF(`Time_In` >= CAST('13:00:01' AS time) AND `Time_Out` <= CAST('15:00:00' AS time),(Val11 * Val2),'false')
FROM table WHERE ID = '5'
And got the output 75 :D
As you assumed you can do that with a CASE construct.
convert your string to a DATETIME value with STR_TO_DATE
extract the TIME part with the TIME function
So you could do it, here with to example values of
'2014-07-19 12:00:00'
'2014-07-19 18:00:00'
with this statement (you would need only one CASE construct of course):
SELECT
CASE
WHEN Time(STR_TO_DATE('2014-07-19 12:00:00', '%Y-%m-%d %H:%i:%s')) BETWEEN '04:00:00' AND '17:00:00'
THEN Val1 * Val2
ELSE NULL
END result,
CASE
WHEN Time(STR_TO_DATE('2014-07-19 18:00:00', '%Y-%m-%d %H:%i:%s')) BETWEEN '04:00:00' AND '17:00:00'
THEN Val1 * Val2
ELSE NULL
END result2
FROM
example;
DEMO