I am trying to get the last 31st August every year dynamically.
E.g if current date is today I would like to get 31st August 2019
next year, and I want this to be dynamic and get 31st August 2020?
I have tried Date_Sub and Date_Trunc and they are not working. Any ideas would be really helpful?
SELECT DATE_SUB(current_date(), INTERVAL 5 DAY) as five_days_ago
Below will always return last /latest August 31st
#standardSQL
SELECT IF(CURRENT_DATE() < last_august_31, DATE_SUB(last_august_31, INTERVAL 1 YEAR), last_august_31) AS last_august_31
FROM UNNEST([DATE(EXTRACT(YEAR FROM CURRENT_DATE()), 8, 31)]) last_august_31
In case if you need to use this within the query with date field - consider below example
#standardSQL
WITH `project.dataset.table` AS (
SELECT DATE '2019-01-01'dt UNION ALL
SELECT '2019-12-31' UNION ALL
SELECT CURRENT_DATE()
)
SELECT dt, IF(dt < last_august_31, DATE_SUB(last_august_31, INTERVAL 1 YEAR), last_august_31) AS last_august_31
FROM `project.dataset.table`,
UNNEST([DATE(EXTRACT(YEAR FROM dt), 8, 31)]) last_august_31
-- ORDER BY dt
with result
Row dt last_august_31
1 2019-01-01 2018-08-31
2 2019-12-31 2019-08-31
3 2020-02-25 2019-08-31
with dates as (
select cast('2019-01-01' as date) as my_date union all select '2019-12-31' union all select current_date()
)
select
my_date,
date(extract(year from my_date) - case when extract(month from my_date) < 9 then 1 else 0 end, 8, 31) as prev_aug_31,
date(extract(year from my_date) + case when extract(month from my_date) >= 9 then 1 else 0 end, 8, 31) as next_aug_31
from dates
Related
How can you make a date range in a big query? A date range starts from 29th of the month and ends with 28th of the next month. It should be like this
Date | Starting Date | Ending Date
03-13-2020 | 02-29-2020 | 03-28-2021
06-30-2020 | 06-29-2020 | 07-28-2021
01-01-2021 | 12-29-2020 | 01-28-2021
11-11-2021 | 10-28-2021 | 11-29-2021
Actually, i make an article on it.
Check this out:
https://www.theaccountingtactics.com/2021/12/BigQueryBQ-DateProblems-DateSituations-that-are-Hard-to-Analyze-and-Takes-Time-ToCrack%20.html?m=1
Consider below approach
create temp function set_day(date date, day int64) as (
ifnull(
safe.date(extract(year from date), extract(month from date), day),
last_day(date)
)
);
select Date,
set_day(Starting_Date, 29) as Starting_Date,
set_day(Ending_Date, 28) as Ending_Date
from (
select *, if(extract(day from Date) < 29,
struct(date_sub(Date, interval 1 month) as Starting_Date, Date as Ending_Date),
struct(Date as Starting_Date, date_add(Date, interval 1 month) as Ending_Date)
).*
from your_table
)
if applied to sample data as in your question
with your_table as (
select date '2020-03-13' Date union all
select '2021-03-13' union all
select '2020-06-30' union all
select '2021-01-01' union all
select '2021-11-11'
)
output is
You can test whole stuff using below
create temp function set_day(date date, day int64) as (
ifnull(
safe.date(extract(year from date), extract(month from date), day),
last_day(date)
)
);
with your_table as (
select date '2020-03-13' Date union all
select '2021-03-13' union all
select '2020-06-30' union all
select '2021-01-01' union all
select '2021-11-11'
)
select Date,
set_day(Starting_Date, 29) as Starting_Date,
set_day(Ending_Date, 28) as Ending_Date
from (
select *, if(extract(day from Date) < 29,
struct(date_sub(Date, interval 1 month) as Starting_Date, Date as Ending_Date),
struct(Date as Starting_Date, date_add(Date, interval 1 month) as Ending_Date)
).*
from your_table
)
How can I count the days of each week for a specific month that the user will give to my SQL query? For example, if the user gives April 2021, the result will be:
If the user gives May 2021 the result will be:
Here is a direct computation of the same. The input is given as a string, such as May 2021; you can use a bind variable in its place. Just keep in mind the possibility that the user may be in a non-English-speaking locale; as long as they use their local language in passing the month to the query, everything should work fine.
with
inputs (mth) as (select 'May 2021' from dual)
, first_day (dt) as (select to_date(mth, 'fmMonth yyyy') from inputs)
, mondays (dt, ord, lst) as (
select trunc(dt, 'iw') + 7 * (level - 1), level, max(level) over ()
from first_day
connect by level <= 1 + (trunc(add_months(dt, 1), 'iw') - trunc(dt, 'iw')) / 7
)
select to_number(to_char(dt, 'iw')) as week_number,
case ord when 1 then dt + 7 - trunc(dt + 7, 'mm')
when lst then last_day(dt) + 1 - dt
else 7 end as week_days
from mondays
order by week_number
;
WEEK_NUMBER WEEK_DAYS
----------- ----------
17 2
18 7
19 7
20 7
21 7
22 1
I think this is the query you are looking for:
SELECT WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, LAST_DAY(supplied_date) - SUPPLIED_DATE+1 AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY WEEK
ORDER BY WEEK;
Just replace the inner TO_DATE with your date.
Edit 1: Additional Column (See Comments)
SELECT MONTH_NAME, WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY, 'MONTH') AS MONTH_NAME, TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, TO_CHAR(LAST_DAY(supplied_date), 'DD') AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY MONTH_NAME, WEEK
ORDER BY WEEK;
Note: I also changed the way I calculate the number of days in the month.
You can generate the weeks (without needing to use any aggregation) using:
WITH input ( month ) AS (
SELECT DATE '2021-04-01' FROM DUAL
),
weeks_of_month ( month_start, week_start, end_day ) AS (
SELECT TRUNC( month, 'MM' ),
TRUNC( month, 'IW' ),
LAST_DAY( TRUNC( month ) )
FROM input
UNION ALL
SELECT month_start,
week_start + INTERVAL '7' DAY,
end_day
FROM weeks_of_month
WHERE week_start + INTERVAL '7' DAY <= end_day
)
SELECT TO_CHAR( week_start, 'IW' ) AS iso_week,
GREATEST( month_start, week_start ) AS first_day_of_week,
LEAST( end_day, week_start + INTERVAL '6' DAY ) AS last_day_of_week,
LEAST( end_day + 1, week_start + INTERVAL '7' DAY )
- GREATEST( month_start, week_start ) AS days
FROM weeks_of_month;
Which outputs (with the NLS_DATE_FORMAT set to YYYY-MM-DD (DY)):
ISO_WEEK
FIRST_DAY_OF_WEEK
LAST_DAY_OF_WEEK
DAYS
13
2021-04-01 (THU)
2021-04-04 (SUN)
4
14
2021-04-05 (MON)
2021-04-11 (SUN)
7
15
2021-04-12 (MON)
2021-04-18 (SUN)
7
16
2021-04-19 (MON)
2021-04-25 (SUN)
7
17
2021-04-26 (MON)
2021-04-30 (FRI)
5
db<>fiddle here
How to convert dates to ISO week date in Impala SQL?
For example 2019-12-30 in the ISO week date calendar would be written as 2020-W01-1 or 2020W011
ANSWER:
Marked Gordon Linoff answer as correct, as it solves the essential part of the question, the deducing of the year part of ISO week date.
For the week part of the ISO week date there is a ready function, and the day part of the ISO week date can be easily converted from Sunday starting week to Monday starting week.
The query below contains all week dates from Monday to Sunday:
select datecol,
concat(cast(iso_year as string),'-W',lpad(cast(iso_week as string),2,'0'),'-',cast(iso_day as string)) as iso_Year_week_date_long,
concat(cast(iso_year as string),'W',lpad(cast(iso_week as string),2,'0'),cast(iso_day as string)) as iso_Year_week_date_short
from (
SELECT datecol,
(case when weekofyear(datecol) = 1 and
date_part('year',datecol) <> date_part('year',adddate(datecol,+7))
then date_part('year',datecol) + 1
when weekofyear(datecol) in (52, 53) and
date_part('year',datecol) <> date_part('year',adddate(datecol,-7))
then date_part('year',datecol) - 1
else date_part('year',datecol)
end) as iso_year,
weekofyear(datecol) as iso_week,
1+mod(dayofweek(datecol)+5,7) as iso_day
from (
select '2021-12-31' as datecol union
select '2020-12-31' as datecol union
select '2019-12-31' as datecol union
select '2018-12-31' as datecol union
select '2017-12-31' as datecol union
select '2016-12-31' as datecol union
select '2015-12-31' as datecol union
select '2014-12-31' as datecol union
select '2013-12-31' as datecol union
select '2012-12-31' as datecol union
select '2022-01-01' as datecol union
select '2021-01-01' as datecol union
select '2020-01-01' as datecol union
select '2019-01-01' as datecol union
select '2018-01-01' as datecol union
select '2017-01-01' as datecol union
select '2016-01-01' as datecol union
select '2015-01-01' as datecol union
select '2014-01-01' as datecol union
select '2013-01-01' as datecol
) as t1
) as t2
order by datecol;
and shows how January 1st belongs to
the new year, if January 1st is 1st, 2nd, 3rd or 4th day of the week, i.e., if there are at least 4 new year days in the week containing January 1st
the old year, if January 1st is 5th, 6th or 7th day of the week, i.e., if there are 3 or less new year days in the week containing January 1st
datecol |iso_year_week_date_long|iso_year_week_date_short|
----------|-----------------------|------------------------|
2014-12-31|2015-W01-3 |2015W013 |
2015-01-01|2015-W01-4 |2015W014 |
2015-12-31|2015-W53-4 |2015W534 |
2016-01-01|2015-W53-5 |2015W535 |
2016-12-31|2016-W52-6 |2016W526 |
2017-01-01|2016-W52-7 |2016W527 |
2017-12-31|2017-W52-7 |2017W527 |
2018-01-01|2018-W01-1 |2018W011 |
2018-12-31|2019-W01-1 |2019W011 |
2019-01-01|2019-W01-2 |2019W012 |
2019-12-31|2020-W01-2 |2020W012 |
2020-01-01|2020-W01-3 |2020W013 |
2020-12-31|2020-W53-4 |2020W534 |
2021-01-01|2020-W53-5 |2020W535 |
I think Impala returns the iso week for date_part() and extract() -- based on your previous question. There is no documentation to this effect.
If so, you can use conditional logic:
select (case when date_part(week, datecol) = 1 and
date_part(year, datecol) <> date_part(year, datecol + interval 1 week)
then date_part(year, datecol) + 1
when date_part(week, datecol) in (52, 53) and
date_part(year, datecol) <> date_part(year, datecol - interval 1 week)
then date_part(year, datecol) - 1
else date_part(year, datecol)
end) as iso_year,
date_part(week, datecol) as iso_week
Otherwise, you can get the first day of the iso year using:
select (case when to_char('DD', date_trunc(year, datecol), 'DD') in ('THU', 'FRI', 'SAT', 'SUN')
then next_day(date_trunc(year, date_trunc(year, datecol)), 'Monday')
else next_day(date_trunc(year, date_trunc(year, datecol)), 'Monday') - interval 7 day
end) as iso_year_start
You can then calculate the iso week from the start of the iso year using arithmetic.
For example 2019-12-30 in the ISO week date calendar would be written as 2020-W01-1 or 2020W011.
We could make use of string format:
select cast(cast('2019-12-30' as date format 'YYYY-MM-DD') as string format 'iyyy-iw-id')
Returns:
"2020-01-01"
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.
I would like to get a table of months between two dates with a fraction of each month that the two dates cover.
For example with a start date of 15/01/2017 and end date of 01/03/2017 it would output:
01/2017 : 0.5483..
02/2017 : 1
03/2017: 0.0322..
where for January and March the calculations are 17/31 and 1/31 respectively. I currently have the query:
WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE(:givenEndDate,'dd/mm/yyyy'), 'MON')
)
select * from dates_between
This outputs each month between two dates and formats it to the start of the month. I just need another column to give me the fraction the start and end dates cover. I'm not sure of a way to do this without it getting messy.
The months_between() function "calculates the fractional portion of the result based on a 31-day month". That means that if your range starts or ends in a month that doesn't have 31 days, the fraction you get might not be quite what you expect:
select months_between(date '2017-04-02', date '2017-04-01') as calc from dual
CALC
----------
.0322580645
... which is 1/31, not 1/30. To get 0.0333... instead you'd need to calculate the number of days in each month, at least for the first and last month. This uses a recursive CTE (11gR2+) to get the months, using a couple of date ranges provided by another CTE as a demo to show the difference (you can use a hierarchical query too of course):
with ranges (id, start_date, end_date) as (
select 1, date '2017-01-15', date '2017-03-01' from dual
union all select 2, date '2017-01-31', date '2017-03-01' from dual
union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
select id,
trunc(start_date, 'MM'),
extract(day from last_day(start_date)),
start_date,
end_date
from ranges
union all
select id,
month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select id,
to_char(month_start, 'YYYY-MM-DD') as month_start,
month_days,
case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end as range_days,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by id, month_start;
which gets:
ID MONTH_STAR MONTH_DAYS RANGE_DAYS FRACTION
------ ---------- ---------- ---------- --------
1 2017-01-01 31 17 0.5483
1 2017-02-01 28 28 1
1 2017-03-01 31 1 0.0322
2 2017-01-01 31 1 0.0322
2 2017-02-01 28 28 1
2 2017-03-01 31 1 0.0322
3 2017-02-01 28 1 0.0357
3 2017-03-01 31 31 1
3 2017-04-01 30 1 0.0333
The first CTE ranges is just the demo data. The second, recursive, CTE months generates the start and number of days in each month, while keeping track of the original range dates too. The final query just calculates the fractions based on the number of days in the month in the range against the number of days in that month overall.
The month_days and range_days are only shown in the output so you can see what the calculation is based on, you can obviously omit those from your actual result, and format the month start date however you want.
With your original single pair of bind variables the equivalent would be:
with months (month_start, month_days, range_start, range_end) as (
select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
to_date(:givenstartdate, 'DD/MM/YYYY'),
to_date(:givenenddate, 'DD/MM/YYYY')
from dual
union all
select month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by month_start;
MONTH FRACTION
------- --------
01/2017 0.5483
02/2017 1
03/2017 0.0322
Here's how I would do it (n.b. I have expanded your dates_between to work against multiple rows, purely for demonstration purposes. If you're only working with a single set of parameters, you wouldn't need to do that):
WITH params AS (SELECT 1 ID, '15/01/2017' givenstartdate, '01/03/2017' givenenddate FROM dual UNION ALL
SELECT 2 ID, '15/01/2017' givenstartdate, '23/01/2017' givenenddate FROM dual UNION ALL
SELECT 3 ID, '01/01/2017' givenstartdate, '07/04/2017' givenenddate FROM dual),
dates_between AS (SELECT ID,
to_date(givenstartdate, 'dd/mm/yyyy') givenstartdate,
to_date(givenenddate, 'dd/mm/yyyy') givenenddate,
add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) start_of_month,
last_day(add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1)) end_of_month
FROM params
CONNECT BY add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) <=
trunc(to_date(givenenddate, 'dd/mm/yyyy'), 'MON')
AND PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL)
SELECT ID,
givenstartdate,
givenenddate,
start_of_month date_out,
end_of_month,
months_between(LEAST(givenenddate, end_of_month) + 1, GREATEST(start_of_month, givenstartdate))
FROM dates_between;
ID GIVENSTARTDATE GIVENENDDATE DATE_OUT END_OF_MONTH DIFF
1 15/01/2017 01/03/2017 01/01/2017 31/01/2017 0.54838709
1 15/01/2017 01/03/2017 01/02/2017 28/02/2017 1
1 15/01/2017 01/03/2017 01/03/2017 31/03/2017 0.03225806
2 15/01/2017 23/01/2017 01/01/2017 31/01/2017 0.29032258
3 01/01/2017 07/04/2017 01/01/2017 31/01/2017 1
3 01/01/2017 07/04/2017 01/02/2017 28/02/2017 1
3 01/01/2017 07/04/2017 01/03/2017 31/03/2017 1
3 01/01/2017 07/04/2017 01/04/2017 30/04/2017 0.22580645
N.B. You may need to add a case statement to decide whether you want to add 1 or not to the diff calculation, based on your requirements.
Try this
For first month, I have calculated remaining days / total days and for last month, I subtracted it by 1 to get days passed / total days.
DBFiddle Demo
WITH tbl AS
(SELECT date '2017-01-15' AS givenStartDate
,date '2017-03-01' AS givenEndDate
FROM dual
)
SELECT ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) AS date_out ,
CASE
WHEN
rownum - 1 = 0
THEN months_between(last_day(givenStartDate), givenStartDate)
WHEN ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) = TRUNC(givenEndDate, 'MON')
THEN 1 - (months_between(last_day(givenEndDate), givenEndDate))
ELSE 1
END AS perc
FROM tbl
CONNECT BY ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1)
<= TRUNC(givenEndDate, 'MON');
Output
+-----------+-------------------------------------------+
| DATE_OUT | PERC |
+-----------+-------------------------------------------+
| 01-JAN-17 | .5161290322580645161290322580645161290323 |
| 01-FEB-17 | 1 |
| 01-MAR-17 | .0322580645161290322580645161290322580645 |
+-----------+-------------------------------------------+