How to add an integer to a date field in Snowflake? - sql

Have a WHERE clause in my SQL query in Snowflake where I would like to filter by adding an integer to a date field:
AND CAST(DATETIME AS DATE) >= '2022-10-26' + 30
SNK returns me this error: invalid type [TO_DATE((2022 - 10) - 26)] for parameter 'TO_DATE'
Anyone know how to overturn this error?

Use the DATEADD() function to add days:
CAST(DATETIME AS DATE) >= DATEADD(day, 30, '2022-10-26')

Using INTERVAL arithmetic:
CAST(DATETIME AS DATE) >= '2022-10-26'::DATE + INTERVAL '30 DAYS'

the base problem '2022-10-26' + 30 is that a string and number do not implicitly compare to a date
So there are a couple of ways to do the cast:
select
column1 as datetime
,to_date(datetime, 'yyyy-mm-dd') as d1
,try_to_date(datetime, 'yyyy-mm-dd') as d2
,datetime::date as d3
,cast(datetime as date) as d4
--,d1 + 30 as add1
-- ,dateadd(day, 30, '2022-10-26') as add2
--,d1 + interval '30 days'
from values
('2023-02-10'),
('2022-10-26');
DATETIME
D1
D2
D3
D4
2023-02-10
2023-02-10
2023-02-10
2023-02-10
2023-02-10
2022-10-26
2022-10-26
2022-10-26
2022-10-26
2022-10-26
and the second half is addition
select
column1 as datetime
,to_date(datetime, 'yyyy-mm-dd') as d1
,d1 + 30 as add1
,dateadd(day, 30, '2022-10-26') as add2
,d1 + interval '30 days' as add3
from values
('2023-02-10'),
('2022-10-26')
DATETIME
D1
ADD1
ADD2
ADD3
2023-02-10
2023-02-10
2023-03-12
2022-11-25 00:00:00.000
2023-03-12
2022-10-26
2022-10-26
2022-11-25
2022-11-25 00:00:00.000
2022-11-25
this shows that DATEADD can handle being given a string (constant of dynamic) and the compiler will correctly cast the string to DATE, but then the DATEADD givens you a TIMESTAMP back. But the two inline addition methods require the date side of the addition to already be a date, thus it needs casting in one fashion or another.

Related

Postgres Join tables using timestamp (part of it)

I have data with a frequency of one minute for 3 years and I would need to put it in one table to make it comparable.
Table1-2019
date_time
v_2020
01.01.2019 01:00:00
50
01.01.2019 01:01:00
49
01.01.2019 01:02:00
56
Table2-2020
date_time
v_2020
01.01.2020 01:00:00
60
01.01.2020 01:01:00
59
01.01.2020 01:02:00
56
Table3-2021
date_time
v_2020
01.01.2021 01:00:00
55
01.01.2021 01:01:00
54
01.01.2021 01:02:00
48
requested table
date_time
v_2019
v_2020
v_2021
01.01. 01:00:00
50
60
55
01.01. 01:01:00
49
59
54
01.01. 01:02:00
56
56
48
Visualisation of tables
I tried several codes, but they didn't work. With functions JOIN and LEFT, I have a problem with the format of date_time column (it is a timestamp without zone). With the SUBSTR I had also a problem with format of date_time.
Finally I tried code below, but it also doesn't work.
CREATE TABLE all AS
SELECT A.date_time, A.v_2019 FROM Table1 AS A
JOIN Table2
WHERE (select datepart(day, month, hour, minute) from A.date_time)=(select datepart(day, month, hour, minute) from Table2.date_time)
JOIN Table3
WHERE (select datepart(day, month, hour, minute) from A.date_time)=(select datepart(day, month, hour, minute) from Table3.date_time)
Once you create your tables run this query. I believe that it is straightforward:
select to_char(t1.date_time, 'mm-dd hh24:mi') date_time,
t1.v_2020 v_2020_2019,
t2.v_2020 v_2020_2020,
t3.v_2020 v_2020_2021
from table1 t1
join table2 t2 on t2.date_time = t1.date_time + interval '1 year'
join table3 t3 on t3.date_time = t1.date_time + interval '2 years';
See DB-fiddle
date_time
v_2020_2019
v_2020_2020
v_2020_2021
01-01 01:00
50
60
55
01-01 01:01
49
59
54
01-01 01:02
56
56
48
While you can do this with an INTERVAL I think you should consider a JOIN condition that uses date manipulating functions.
Keep in mind using something like WHERE DATE_TRUNC(...) or JOIN ... ON DATE_TRUNC(...) will NOT respect indexes on these fields. When passing the field value into a function you're essentially creating a black box that cannot take advantage of an index. You would need to create an index specifically on DATE_TRUNC('DAY', date_time) for example.
Here is another DBFiddle for you to consider
You can do this in a couple ways:
SELECT TO_CHAR(v19.date_time, 'MM-DD HH24:MI') datetime
, v19.v_2019
, v20.v_2020
, v21.v_2021
FROM t_2019 v19
FULL JOIN t_2020 v20
ON DATE_PART('MONTH', v19.date_time) = DATE_PART('MONTH', v20.date_time)
AND DATE_PART('DAY', v19.date_time) = DATE_PART('DAY', v20.date_time)
AND v19.date_time::TIME = v20.date_time::TIME
FULL JOIN t_2021 v21
ON DATE_PART('MONTH', v20.date_time) = DATE_PART('MONTH', v21.date_time)
AND DATE_PART('DAY', v20.date_time) = DATE_PART('DAY', v21.date_time)
AND v20.date_time::TIME = v21.date_time::TIME
;
SELECT TO_CHAR(v19.date_time, 'MM-DD HH24:MI') datetime
, v19.v_2019
, v20.v_2020
, v21.v_2021
FROM t_2019 v19
FULL JOIN t_2020 v20
ON TO_CHAR(v19.date_time, 'MM-DD HH24:MI') = TO_CHAR(v20.date_time, 'MM.DD HH24:MI')
FULL JOIN t_2021 v21
ON TO_CHAR(v20.date_time, 'MM-DD HH24:MI') = TO_CHAR(v21.date_time, 'MM.DD HH24:MI')
;
Both of these result in the following:
datetime
v_2019
v_2020
v_2021
01-01 01:00
50
60
55
01-01 01:01
49
59
54
01-01 01:02
56
56
48

Is there a way in SQL to select all days available in a table with a 30 days difference jump?

I need to select all dates, from current date, with a 30 days jump between each other in the past.
In example, today is October 18th, so I would need to get September 18th , August 19th, July 20th.
I cannot simply write the function current_date - 30, current_date - 60, current_date - 90 because the beginning of the data is far.
If you are using Oracle you can use below code -
WITH DATES(CUR_DATE, MON) AS (SELECT CURRENT_DATE CUR_DATE, 1 MON FROM DUAL
UNION ALL
SELECT D.CUR_DATE - 30, D.MON + 1
FROM DATES D
WHERE TO_CHAR(D.CUR_DATE, 'MM') > 1
AND TO_CHAR(D.CUR_DATE, 'YY') = TO_CHAR(SYSDATE, 'YY'))
SELECT * FROM DATES
Here is the fiddle. For other DBs please post your DBMS product.
If you are using Postgres this is quite easy using generate_series()
select d::date as "date"
from generate_series(current_date,
current_date - interval '1 year',
interval '-1 month') as t(d)
That returns the "last 12 months" starting from today.
If today is 2019-10-18, this returns:
date
----------
2019-10-18
2019-09-18
2019-08-18
2019-07-18
2019-06-18
2019-05-18
2019-04-18
2019-03-18
2019-02-18
2019-01-18
2018-12-18
2018-11-18
2018-10-18
If you want 30 days intervals (rather than 1 month), use:
select d::date as date
from generate_series(current_date,
current_date - interval '1 year',
interval '-30 day') as t(d)
For SQL Server, you can use the code below:
;WITH cte_Date30Days(n, d, dte)
AS (
SELECT 0, 0, current_timestamp as dte
UNION ALL
SELECT n+1, d - 30, dateadd(day, -30, dte)
FROM
cte_Date30Days
WHERE n < 7-1
)
SELECT dte FROM cte_Date30Days;
Only 7 rows are returned but change that value to suit your needs.

how to convert decimal time to datetime so I can run dateadd calculation on it

I have a MSSQL table where the raw data is formatted as this:
date1 time1
2008-01-20 00:00:00 654
2008-01-20 00:00:00 659
2008-01-20 00:00:00 1759
and I need to join both of them together so I can query for example all date_time that happened in the last 15 hours. what I did was
in the select statement:
combined = CONVERT(VARCHAR(10), Date1, 103) +' ' + (left((replace((CONVERT(dec(7, 2), time1) / 100 ),'.',':')),4) + ':00') ,
This helped me with getting results for
date1 time1 combined1
2008-01-20 00:00:00 654 20/01/2008 6:54:00
2008-01-20 00:00:00 659 20/01/2008 6:59:00
2008-01-20 00:00:00 1759 20/01/2008 17:5:00
I cant change the table data & I cant get the right syntax to convert it fully (including taking in consideration the 24h hour format - 1759 for example)
And in the end I need to be able to do a where statement on the combined1 column to see only the rows that happened in the last 15 hours
DATEADD(hour, - 15, GETDATE())
Thanks in advance
Try This
select date1,time1, DATEADD(MINUTE, time1%100, DATEADD(HOUR, time1/100, convert(varchar(10),date1,101))) as Combined
from Table
Where DATEADD(MINUTE, time1%100, DATEADD(HOUR, time1/100, convert(varchar(10),date1,101)))>(DATEADD(hour,-15,GETDATE()))
Try like this:
DECLARE #date DATETIME = '2008-01-20 00:00:00'
, #Time INT = 654
SELECT DATEADD(MINUTE, #Time%100, DATEADD(HOUR, #Time/100, #date))
;WITH cte
AS (SELECT CAST('2008-01-20 00:00:00' AS DATETIME) AS date1, 654 AS Time1
UNION ALL
SELECT CAST('2008-01-20 00:00:00' AS DATETIME) AS date1, 659 AS Time1
UNION ALL
SELECT CAST('2008-01-20 00:00:00' AS DATETIME) AS date1, 1759 AS Time1
)
SELECT
DATEADD(ms, DATEDIFF(ms, '00:00:00', CAST(FORMAT(Time1, '##:##') AS TIME)), date1) AS [CombinedDateTime]
FROM cte;
--Results to:
CombinedDateTime
2008-01-20 06:54:00.000
2008-01-20 06:59:00.000
2008-01-20 17:59:00.000

Limit rows of each `intime` field to 30

I'm using following select statement to generate random data for one year
SELECT a intime
,a + format('%s', b || ' minutes')::interval otime
,b duration
FROM generate_series('2016-01-01 07:00:00'::timestamp, '2016-12-31 16:00:00', '05 minutes') s(a)
,generate_series(5, 20, 5) t(b)
where a::time between '07:00:00'
and '16:00:00';
This will produce 436 rows for each day in a month but how can I limit it to 30 rows in each day from 2016-01-01 to 2016-12-31
If you want 30 equal spaced times during a day:
SELECT s.a
FROM generate_series('2016-01-01'::timestamp, '2016-12-31',
'48 minutes') s(a)

PostgreSQL time range duration over time series with default end if null

I need to be able to calculate the duration (in seconds) between two time stamps as an aggregate over a time series using a default end_datetime if it is null.
Imagine you have something like a punch card when you puch in and out:
username, start_datetime, end_datetime
What I want is a generated time series of the last N minutes with the duration for all users that overlap within that time frame. So it would be the SUM(end_datetime - start_datetime) where you would COALESCE a default end_datetime if it is null.
So the basic pieces I think I need are:
Generate the time interval:
select TIMESTAMP '2013-01-01 12:01:00' - (interval '1' minute * generate_series(0,5)) as timestamps;
COALESCE a default end_datetime
COALESCE(end_datetime, NOW())
Figure out the seconds difference between the start and end dates
So if one user logged in at 11:56:50 and it is now 12:01:40 we should get a table like:
timestamps duration
-------------------------------------
2013-01-01 12:01:00 40
2013-01-01 12:00:00 60
2013-01-01 11:59:00 60
2013-01-01 11:58:00 60
2013-01-01 11:57:00 60
2013-01-01 11:56:00 10
with t as (select '2013-01-01 11:56:50'::timestamp startt, '2013-01-01 12:01:40'::timestamp endt)
select
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(select generate_series(date_trunc('minute',startt),date_trunc('minute',endt),'1 minute') timestamps, * from t) a
order by
timestamps desc;
2013-01-01 12:01:00;40
2013-01-01 12:00:00;60
2013-01-01 11:59:00;60
2013-01-01 11:58:00;60
2013-01-01 11:57:00;60
2013-01-01 11:56:00;10
If you have multiple rows with start and end timestamp than the following will work:
select
id,
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(
select
id,
generate_series(date_trunc('minute',startt) ,
coalesce(date_trunc('minute',endt),date_trunc('minute',Now())),'1 minute') as timestamps,
startt, endt
from test
) a
order by
id, timestamps desc
SQLFiddle