Passing Dynamic Date Part to Date_Add in BigQuery - sql

Is there a way to pass a dynamic string to the date part that comes after the integer in the date_add function?
I.e.
date_add('2023-01-01', interval 7 {dynamic_date_part})
where {dynamic_date_part} could be 'day', 'week', 'month' etc?
I tried this but it rejects anything that gets put there as just "unexpected"

You can use this query to add the dynamic date part:
select date_add(dte, interval n {dynamic_date_part}) as date
from (select current_date as dte, 1 as n union all
SELECT CURRENT_DATE, 2)
As 1 and 2 in
from (select current_date as dte, 1 as n union all
SELECT CURRENT_DATE, 2)
represent the day/week/month of interval.
Here is the query and output:

Related

SQL Query to group dates and includes different dates in the aggregation

I have a table with two columns, dates and number of searches in each date. What I want to do is group by the dates, and find the sum of number of searches for each date.
The trick is that for each group, I also want to include the number of searches for the date exactly the following week, and the number of searches for the date exactly the previous week.
So If I have
Date
Searches
2/3/2023
2
2/10/2023
4
2/17/2023
1
2/24/2023
5
I want the output for the 2/10/2023 and 2/17/2023 groups to be
Date
Sum
2/10/2023
7
2/17/2023
10
How can I write a query for this?
You can use a correlated query for this:
select date, (
select sum(searches)
from t as x
where x.date between t.date - interval '7 day' and t.date + interval '7 day'
) as sum_win
from t
Replace interval 'x day' with the appropriate date add function for your RDBMS.
If your RDBMS supports interval in window functions then a much better solution would be:
select date, sum(searches) over (
order by date
range between interval '7 day' preceding and interval '7 day' following
) as sum_win
from t
Assuming weekly rows
CREATE TABLE Table1
([Dates] date, [Searches] int)
;
INSERT INTO Table1
([Dates], [Searches])
VALUES
('2023-02-03 00:00:00', 2),
('2023-02-10 00:00:00', 4),
('2023-02-17 00:00:00', 1),
('2023-02-24 00:00:00', 5)
;
;with cte as (
select dates
, searches
+ lead(searches) over(order by dates)
+ lag(searches) over(order by dates) as sum_searches
from table1)
select * from cte
where sum_searches is not null;
dates
sum_searches
2023-02-10
7
2023-02-17
10
fiddle

Need to split datetime ranges by each day

I have a table that needs to be split on the basis of datetime
Input Table
ID| Start | End
--------------------------------------------
A | 2019-03-04 23:18:04| 2019-03-04 23:21:25
--------------------------------------------
A | 2019-03-04 23:45:05| 2019-03-05 00:15:14
--------------------------------------------
Required Output
ID| Start | End
--------------------------------------------
A | 2019-03-04 23:18:04| 2019-03-04 23:21:25
--------------------------------------------
A | 2019-03-04 23:45:05| 2019-03-04 23:59:59
--------------------------------------------
A | 2019-03-05 00:00:00| 2019-03-05 00:15:14
--------------------------------------------
Thanks!!
Try this below code. This will only work if the start and end date fall in two consecutive day. Not if the start and end date difference is more than 1 day.
MSSQL:
SELECT ID,[Start],[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) = 0
UNION ALL
SELECT ID,[Start], CAST(CAST(CAST([Start] AS DATE) AS VARCHAR(MAX)) +' 23:59:59' AS DATETIME)
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0
UNION ALL
SELECT ID,CAST(CAST([End] AS DATE) AS DATETIME),[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0
ORDER BY 1,2,3
PostgreSQL:
SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(endDate, 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) = 0
UNION ALL
SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(CONCAT(CAST(CAST (startDate AS DATE) AS VARCHAR) ,
' 23:59:59') , 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) > 0
UNION ALL
SELECT ID,
TO_TIMESTAMP(CAST(CAST (endDate AS DATE) AS VARCHAR) ,
'YYYY-MM-DD HH24:MI:SS') ,
TO_TIMESTAMP(endDate,'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) -
DATE_PART('day',startDate::date) > 0;
PostgreSQL Demo Here
demo:db<>fiddle
This works even when range crosses more than one day
WITH cte AS (
SELECT
id,
start_time,
end_time,
gs,
lag(gs) over (PARTITION BY id ORDER BY gs) -- 2
FROM
a
LEFT JOIN LATERAL
generate_series(start_time::date + 1, end_time::date, interval '1 day') gs --1
ON TRUE
)
SELECT -- 3
id,
COALESCE(lag, start_time) AS start_time,
gs - interval '1 second'
FROM
cte
WHERE gs IS NOT NULL
UNION
SELECT DISTINCT ON (id) -- 4
id,
CASE WHEN start_time::date = end_time::date THEN start_time ELSE end_time::date END, -- 5
end_time
FROM
cte
CTE: the generate_series function generates one row per day new day. So, there is no value if there is no date change
CTE: the lag() window function allows to move the current date value into the next row (the current end is the next start)
With this data set you can calculate the new start and end values. If there is no gs value: There is no date change. This ignored at this point. For all cases with date changes: If there is no lag value, it is the beginning (so it cannot got a previous value). In this case, the normal start_time is taken, otherwise it is a new day which takes the date break time. The end_time is taken with the last second of the day (interval - '1 second')
The second part: Because of the date breaks there is always one additional record which need to be unioned. The last record is from the beginning of the end_time (so cast to date). The CASE clause combines this step with the case of no date change which has been ignored so far. So if start_time and end_time are at the same date, here the original start_time is taken.
Unfortunately, Redshift doesn't have a convenient way to generate a series of numbers. If you table is big enough, you can use it to generate numbers. "Big enough" means that the number of rows is greater than the longest span. Perhaps another table would work, if not this one.
Once you have that, you can use this logic:
with n as (
select row_number() over () - 1 as n
from t
)
select t.id,
greatest(t.s, date_trunc('day', t.s) + n.n * interval '1 day') as s,
least(t.e, date_trunc('day', t.s) + (n.n + 1) * interval '1 day' - interval '1 second') as e
from t join
n
on t.e >= date_trunc('day', t.s) + n.n * interval '1 day';
Here is a db<>fiddle. It uses an old version of Postgres, but not quite old enough for Redshift.
Simulate loop for interval generation using recursive CTE, i.e. take range from start to midnight in seed row, take another day in subsequent rows etc.
with recursive input as (
select 'A' as id, timestamp '2019-03-04 23:18:04' as s, timestamp '2019-03-04 23:21:25' as e union
select 'A' as id, timestamp '2019-03-04 23:45:05' as s, timestamp '2019-03-05 00:15:14' as e union
select 'B' as id, timestamp '2019-03-06 23:45:05' as s, timestamp '2019-03-08 00:15:14' as e union
select 'C' as id, timestamp '2019-03-10 23:45:05' as s, timestamp '2019-03-15 00:15:14' as e
), generate_id as (
select row_number() over () as unique_id, * from input
), rec (unique_id, id, s, e) as (
select unique_id, id, s, least(e, s::date::timestamp + interval '1 day')
from generate_id seed
union
select remaining.unique_id, remaining.id, previous.e, least(remaining.e, previous.e::date::timestamp + interval '1 day')
from rec as previous
join generate_id remaining on previous.unique_id = remaining.unique_id and previous.e < remaining.e
)
select id, s, e from rec
order by id,s,e
Note:
your id column appears not to be unique, so I added custom unique_id column. If id was unique, CTE generate_id was unnecessary. Uniqueness is unavoidable for recursive query to work.
close-open range is better for representation of such data, rather than close-close range. So end time in my query returns 00:00:00, not 23:59:59. If it's not suitable for you, modify query as an exercise.
UPDATE: query works on Postgres. OP originally tagged question postgres, then changed tag to redshift.

SELECT to_char(date_col, 'YYYY-MM') from table - doesn't work in subquery

I'm trying to build a query using Postgres 9.5.3
Function to_char in simple statement like this
SELECT to_char(date_created,'YYYY-MM') FROM some_table;
Returns results as follow:
+-----------
| to_char
+-----------
| 2017-06
| 2017-07
| 2017-10
Full statement I want to run
SELECT * FROM generate_series(
to_date('2016-01-01', 'YYYY-MM'),
to_date('2017-01-01', 'YYYY-MM'),
interval '1 month')
AS dates
WHERE dates NOT IN (
SELECT to_char(date_created,'YYYY-MM') FROM some_table
);
Result in the following error
Error in query: ERROR: operator does not exist: timestamp with time zone = text
LINE 2: WHERE dates NOT IN (SELECT to_char(date_create,'YYYY-MM')...
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
You are comparing apples to oranges (timestamps to text).
generate_series() with an interval as the third parameter actually returns a timestamp, not a date. Your sub-select returns the column date_created as a string (text) - and comparing a timestamp to a text doesn't work.
As you apparently only want to check for the same month, you need to convert the date returned from generate_series() to the same text value:
SELECT *
FROM generate_series(to_date('2016-01-01', 'YYYY-MM'),
to_date('2017-01-01', 'YYYY-MM'),
interval '1 month') as dates (d)
WHERE to_char(dates.d, 'yyyy-mm') NOT IN (SELECT to_char(date_created,'YYYY-MM')
FROM some_table);
Another option is to compare dates by "normalizing" the date_created to the start of the month:
SELECT *
FROM generate_series(to_date('2016-01-01', 'YYYY-MM'),
to_date('2017-01-01', 'YYYY-MM'),
interval '1 month') as dates (d)
WHERE dates.d NOT IN (SELECT date_trunc('month', date_created)
FROM some_table);
You have a data type mismatch. Use TO_DATE to convert right side (inner query) back to DATE
...
WHERE dates NOT IN (
SELECT to_date(to_char(date_created,'YYYY-MM'), 'YYYY-MM-DD') FROM some_table
);
or use TO_CHAR to convert left side to CHAR
...
WHERE to_char(dates, 'YYYY-MM')NOT IN (
SELECT to_char(date_created,'YYYY-MM') FROM some_table
);

BigQuery Where Date is Less Than or Equal to 3 Days Minus Current Date

I'm trying to create a query to only return data where date is minus 3 days from the current date. I've tried:
date <= DATE_ADD(CURRENT_DATE(), -3, 'DAY')
But this returns Error: Expected INTERVAL expression
See WHERE clause in below example
#standardSQL
WITH yourTable AS (
SELECT i, date
FROM UNNEST(GENERATE_DATE_ARRAY('2017-04-15', '2017-04-28')) AS date WITH OFFSET AS i
)
SELECT *
FROM yourTable
WHERE date <= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY)
-- ORDER BY date
Btw, in case if you are still with Legacy SQL - see below example
#legacySQL
SELECT *
FROM -- yourTable
(SELECT 1 AS id, DATE('2017-04-20') AS date),
(SELECT 2 AS id, DATE('2017-04-21') AS date),
(SELECT 3 AS id, DATE('2017-04-22') AS date),
(SELECT 4 AS id, DATE('2017-04-23') AS date),
(SELECT 5 AS id, DATE('2017-04-24') AS date),
(SELECT 6 AS id, DATE('2017-04-25') AS date)
WHERE TIMESTAMP(date) <= DATE_ADD(TIMESTAMP(CURRENT_DATE()), -3, 'DAY')
-- ORDER BY date
This works with a string formatted date.
DATE(TIMESTAMP(date)) <= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY)
Just tested this and seems to work.
I added this :
and DATE(TIMESTAMP(datevalue)) >= DATE_SUB(CURRENT_DATE(), INTERVAL 21 DAY)
and managed to get all records greater than last 21 days worth. Only thing I changed from #ericbrownaustin 's code was changed the 'date' in the first piece of code in the second set of parenthesis.

Add days Oracle SQL

SELECT ORDER_NUM, CUSTOMER_NUM, CUSTOMER_NAME, ADD_DAYS (ORDER_DATE, 20)
FROM CUSTOMER, ORDERS;
Oracle Express says ADD_DAYS invalid? Any ideas what Am I doing wrong?
If you want to add N days to your days. You can use the plus operator as follows -
SELECT ( SYSDATE + N ) FROM DUAL;
You can use the plus operator to add days to a date.
order_date + 20
In a more general way you can use "INTERVAL". Here some examples:
1) add a day
select sysdate + INTERVAL '1' DAY from dual;
2) add 20 days
select sysdate + INTERVAL '20' DAY from dual;
2) add some minutes
select sysdate + INTERVAL '15' MINUTE from dual;
Some disadvantage of "INTERVAL '1' DAY" is that bind variables cannot be used for the number of days added. Instead, numtodsinterval can be used, like in this small example:
select trunc(sysdate) + numtodsinterval(:x, 'day') tag
from dual
See also: NUMTODSINTERVAL in Oracle Database Online Documentation
It's Simple.You can use
select (sysdate+2) as new_date from dual;
This will add two days from current date.
One thing about
select (sysdate+3) from dual
is that the sysdate is interpreted as date.
But if you want to use a custom date, not local, you need to make sure it is interpreted as date, not string. Like so (adding 3 days):
select (to_date('01/01/2020')+3) from dual