Change start day of the week impala - sql

I am trying to change the start day of the week, but not able to achieve
currently its Monday to sunday, it should be made sunday- saturday
I tried using this query on the below dataset invoice date and invoice_week
SELECT invoice_date,
weekofyear(invoice_date) as invoice_week,
datesub(invoice_date,1),
weekofyear(datesub(invoice_date,1)) as invoice_week1
from table

I think instead of subtract the date by 1, you should add the date by 1:
select
invoice_date,
weekofyear(invoice_date) as invoice_week,
adddate(invoice_date,1),
weekofyear(adddate(invoice_date,1)) as invoice_week1
from(
select cast('2018-01-07 16:00:00' as timestamp) invoice_date
)stg

Found a solution which works. Given a date, here's the Date for the start of the week and end of the week if you want a Sunday - Saturday week, not a Monday - Sunday week as is default.
SET var:date=2019-01-06;
select to_date(IF( dayofweek(cast('${var:date}' as timestamp)) = 1, cast('${var:date}' as timestamp), trunc(cast('${var:date}' as timestamp), 'd') - interval 1 day)) as startOfWeek, to_date(IF( dayofweek(cast('${var:date}' as timestamp)) = 1, (trunc(cast('${var:date}' as timestamp) + interval 1 day, 'd') + interval 5 day), (trunc(cast('${var:date}' as timestamp), 'd') + interval 5 day) )) as endOfWeek;
+-------------+------------+
| startofweek | endofweek |
+-------------+------------+
| 2019-01-06 | 2019-01-12 |
+-------------+------------+

Related

Selecting Dates from Monday to Saturday and Hours Between Morning 8:00:00 AM To Next Day Morning 7:00:00 AM

I am trying to work on a query where there is date selection in the where clause i.e. if sysdate is Monday I have to get the dates from Monday to Saturday and Hours Between Morning 08:00:00 AM to Next Day Morning 07:00:00 AM. I am hardcoding the dates and Hours in the where clause, When I run the query data does not show.
Query:
SELECT TO_CHAR(sysdate, 'HH24:MI:SS'), REPLACE(TO_CHAR(sysdate, 'DAY'), ' ')
FROM dual
WHERE TO_CHAR(sysdate, 'HH24:MI:SS') BETWEEN '08:01:00' AND '08:00:00'
AND TO_CHAR(sysdate, 'DAY') >= 'MONDAY'
AND TO_CHAR(sysdate, 'DAY') <= 'SATURDAY';
You need to filter the wider range first (from 8 AM at Monday till the end of a Saturday, if I understood correctly) and then exclude time from 7 AM till 8 AM.
In the below code iw format element stands for ISO week, that starts on the Monday.
with a as (
select
date '2022-04-17'
+ interval '01:30:00' hour to second
+ interval '2' hour * level
as dt
from dual
connect by level < 90
)
select
to_char(dt, 'yyyymmdd') as day_
, listagg(to_char(dt, 'hh24:mi'), ',')
within group (order by dt asc) as hours
from a
where 1 = 1
/*From Mon 08 AM*/
and dt > trunc(dt, 'iw') +
interval '8' hour
/*Till Sat end of the day*/
and dt < trunc(dt, 'iw') + 6
/*and except minutes between 7 and 8 AM*/
and not (
to_char(dt, 'hh24mi') < '0800'
and to_char(dt, 'hh24mi') > '0700'
)
group by to_char(dt, 'yyyymmdd')
DAY_ | HOURS
:------- | :----------------------------------------------------------------
20220418 | 09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220419 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220420 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220421 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220422 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220423 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
db<>fiddle here
(And what if sysdate isn't Monday?)
Therefore, could you explain a little bit better what is the input (dates? One date? SYSDATE?) and what is desired output (related to that input).
Basically, I don't understand what you want. Meanwhile, errors you made (if it'll help).
Format model is wrong; this is what you did:
SQL> select to_char(sysdate, 'DAY') day, length(to_char(sysdate, 'DAY')) len from dual;
DAY LEN
--------- ----------
FRIDAY 9
"FRIDAY" doesn't have 9 characters; it has 6 of them --> use the fm format modifier (it'll truncate trailing spaces):
SQL> select to_char(sysdate, 'fmDAY') day, length(to_char(sysdate, 'fmDAY')) len from dual;
DAY LEN
--------- ----------
FRIDAY 6
SQL>
Today (22.04.2022) is Friday. Your query searches for data whose day is between "MONDAY" and "SATURDAY". As you're comparing strings and alphabet goes as [A, B, ..., F, G, ..., M, N, ..., S, T], "F(riday)" is NEVER between M(onday) and S(aturday) so there's zero chance that it'll work.
As of hours: which time exactly is between 08:01 and 08:00? Time doesn't go backwards (unless you meant "08:01 today and 08:00 tomorrow").
if sysdate is Monday I have to get the dates from Monday to Saturday and Hours Between Morning 08:00:00 AM to Next Day Morning 07:00:00 AM.
You can find whether SYSDATE is Monday by comparing the day to the start of the ISO week (which will always be midnight on Monday):
SELECT *
FROM DUAL
WHERE SYSDATE - TRUNC(SYSDATE, 'IW') < 1
You can find out whether the hours are between 08:00 and 07:00 the next day by subtracting 8 hours and finding out whether the time is between 00:00 and 23:00:
SELECT *
FROM DUAL
WHERE (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR)) DAY TO SECOND
<= INTERVAL '23' HOUR;
You can combine the two to find out if the day is between Monday and Saturday and the time is between 08:00 and 07:00 on the next day (so for Saturday, it would include 7 hours of Sunday) using:
SELECT *
FROM DUAL
WHERE (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR), 'IW') < 6
AND (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR)) DAY TO SECOND
<= INTERVAL '23' HOUR;
Note: This does not use TO_CHAR so it is unaffected by any changes to the NLS_TERRITORY or NLS_DATE_LANGUAGE session parameters so it will always give the same answer (independent of the settings of the user who runs the query).
You can use such a combination
SELECT TO_CHAR(dt,'HH24:MI:SS','NLS_DATE_LANGUAGE=English') AS Hour,
TO_CHAR(dt,'Day','NLS_DATE_LANGUAGE=English') AS day
FROM t
WHERE TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') IN ('Tue','Wed','Thu','Fri')
AND TO_CHAR(dt, 'HH24:MI:SS') NOT BETWEEN '07:00:01' AND '08:00:00'
OR TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') = 'Mon'
AND TO_CHAR(dt, 'HH24:MI:SS')>= '07:00:00'
OR TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') = 'Sat'
AND TO_CHAR(dt, 'HH24:MI:SS')<= '08:00:00'
where needs to consider restricting the periods for the bound dates individually
Demo

Convert date to ISO week date

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"

Date SQL to get 31st August

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

Human readable elapsed time between many days

I work with Oracle SQL and I have a table with 3 columns: Process, Start Date and End Date. I want to calculate the time duration for every process.
I used this query:
select
(enddate.date_value - startdate.date_value) as duration
from dual
and the result is in days.
For example: The start date is 30.3.2016 17:14:53 and the end date is 8.7.2016 14:51:21
When I use the query, the result is 99.90032407407407407407407407407407407407, but I want result like this:
3 months, 7 days, 21 hours, 36 minutes, 28 seconds.
How can I do that?
This complex query (specialy for DAY !!) :
To compute a correct Day I add Month and 12 * Year to the original date.
with dates as(
select
sysdate as d1,
sysdate-99.90032407407407407407407407407407407407-365 as d2
from
dual
),
dates_parts as (
SELECT
d1,
d2,
EXTRACT(YEAR FROM (d1 - d2) YEAR TO MONTH ) as Year,
EXTRACT(MONTH FROM (d1 - d2) YEAR TO MONTH ) as Month,
EXTRACT(DAY FROM (d1 - d2) DAY TO SECOND ) as Day,
EXTRACT(HOUR FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Hour,
EXTRACT(MINUTE FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Minute,
EXTRACT(SECOND FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Second
FROM dates
)
select
dates_parts.Year,
dates_parts.Month,
dates_parts.Day,
dates_parts.Hour,
dates_parts.Minute,
dates_parts.Second,
EXTRACT(DAY FROM (d1 - ADD_MONTHS(d2,Month+Year*12)) DAY TO SECOND ) as Day_Corrected
from
dates_parts
will produce the different date part :
| YEAR | MONTH | DAY | HOUR | MINUTE | SECOND | DAY_CORRECTED |
|------|-------|-----|------|--------|--------|---------------|
| 1 | 3 | 464 | 21 | 36 | 28 | 7 |
The difference between two DATE values is a number representing the number of days. You seem to want an interval, this can be done using TIMESTAMP values.
select cast(enddate as timestamp) - cast(startdate as timestamp)
from the_table
The result of subtracting a timestamp from a timestamp is an interval.
Formatting of an interval value is however quite tricky in Oracle. See e.g. format interval with to_char
Based on my previous answer you can create an Oracle function sinceHumanReadable:
exemple from https://momentjs.com/docs/#/plugins/preciserange/ produce the same result
moment("2014-01-01 12:00:00").preciseDiff("2015-03-04 16:05:06");
// 1 year 2 months 3 days 4 hours 5 minutes 6 seconds
http://sqlfiddle.com/#!4/d6783/1
create or replace FUNCTION sinceHumanReadable(start_date IN date,end_date IN date)
RETURN VARCHAR2
IS result VARCHAR2(255);
BEGIN
with
dates_parts as (
SELECT
EXTRACT(YEAR FROM (end_date - start_date) YEAR TO MONTH ) as Year,
EXTRACT(MONTH FROM (end_date - start_date) YEAR TO MONTH ) as Month,
EXTRACT(HOUR FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Hour,
EXTRACT(MINUTE FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Minute,
EXTRACT(SECOND FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Second
FROM dual
),
dates_parts_with_day as (
select
Year,Month,Hour,Minute,Second,
EXTRACT(DAY FROM (end_date - ADD_MONTHS(start_date,Month+Year*12)) DAY TO SECOND ) as Day
from dates_parts
)
select
decode(dates_parts_with_day.Year, 0,'', dates_parts_with_day.Year || ' years ' )||
decode(dates_parts_with_day.Month,0,'', dates_parts_with_day.Month || ' months ')||
decode(dates_parts_with_day.Day,0,'', dates_parts_with_day.Day || ' days ')||
decode(dates_parts_with_day.Hour,0,'', dates_parts_with_day.Hour || ' hours ')||
decode(dates_parts_with_day.Minute,0,'', dates_parts_with_day.Minute || ' minutes ')||
dates_parts_with_day.Second || ' seconds'
into result
from
dates_parts_with_day;
RETURN(result);
END sinceHumanReadable;
GO
The query
with dates as (
select sysdate-99.90032407407407407407407407407407407407 as d1,sysdate as d2 from dual
union all
select to_date('2016-03-30 17:14:53','yyyy-mm-dd hh24:mi:ss') as d1,to_date('2016-07-08 14:51:21','yyyy-mm-dd hh24:mi:ss') as d2 from dual
union all
select to_date('2014-01-01 12:00:00','yyyy-mm-dd hh24:mi:ss') as d1,to_date('2015-03-04 16:05:06','yyyy-mm-dd hh24:mi:ss') as d2 from dual
union all
select sysdate as d1,add_months(sysdate,35) as d2 from dual
union all
select sysdate as d1,sysdate as d2 from dual
)
select
d1,d2,
sinceHumanReadable(d1,d2) as since
from
dates;
will produce :
| D1 | D2 | SINCE |
|----------------------|----------------------|-----------------------------------------------------|
| 2017-07-19T17:50:00Z | 2017-10-27T15:26:28Z | 3 months 7 days 21 hours 36 minutes 28 seconds |
| 2016-03-30T17:14:53Z | 2016-07-08T14:51:21Z | 3 months 7 days 21 hours 36 minutes 28 seconds |
| 2014-01-01T12:00:00Z | 2015-03-04T16:05:06Z | 1 years 2 months 3 days 4 hours 5 minutes 6 seconds |
| 2017-10-27T15:26:28Z | 2020-09-27T15:26:28Z | 2 years 11 months 0 seconds |
| 2017-10-27T15:26:28Z | 2017-10-27T15:26:28Z | 0 seconds |

Create list with first and last day of month for given period

I have to generate a list with two columns of day intervals for every month in a specific period. First column must be the first day of month and the second column the last day of month.
Example:
Start date: 2014-01-01
End date: 2014-06-30
The result should be in two columns:
1. 2014-01-01 | 2014-01-31
2. 2014-02-01 | 2014-02-28
3. 2014-03-01 | 2014-03-31
4. 2014-04-01 | 2014-04-30
5. 2014-05-01 | 2014-05-31
6. 2014-06-01 | 2014-06-30
I have two functions that get the first and last day from a date.
I am trying to combine two series but with no luck.
select i::date
from generate_series(first_day('2014-01-01')
,first_day('2014-06-30'), '1 month'::interval) i
select i::date
from generate_series(last_day('2014-01-01')
,last_day('2014-06-30'), '1 month'::interval) i
The second function does not show the last day correctly when it is in the series.
Based on this answer:
select d::date as start_date,(d + '1 month'::interval - '1 day'::interval )::date end_date
from generate_series('2014-01-01'::date, '2014-06-30'::date, '1 month'::interval) d
Add a month and subtract a day - in a single interval expression:
SELECT d AS mon_first
, d + interval '1 month - 1 day' AS mon_last
FROM generate_series(timestamp '2014-01-01'
, timestamp '2014-06-30'
, interval '1 month') d;
The manual about interval input
Further reading ("end of day" is very similar to "last day of month"):
How to get the end of a day?
About generating a time series:
Generating time series between two dates in PostgreSQL