Exclude partial weeks from results BQ SQL - google-bigquery

I am trying to find an easy way to exclude partial weeks from the results.
What I have so far:
WITH a AS (SELECT
FORMAT_DATE("%G-%V", created_date) as report_week
, created_date
, FORMAT_DATE('%A', created_date) AS day
, emp_id
, ROUND(SAFE_DIVIDE(SUM(working_time),3600),2) as hours
FROM `table1` a
WHERE created_date >= current_date()-10
GROUP BY 1,2,3,4,5)
SELECT
report_week
, emp_id
, hours
FROM a
WHERE day LIKE '%Monday%'
GROUP BY 1,2,3
ORDER BY report_week ASC
Input:
report_week: conversion of employee's shift date into week
created_date: date of employee's shift
day: conversion of date of employee's shift into day of week (Monday, Tuesday..)
emp_id: the employee's ID
hours: Number of worked hours by the employee
if current_date is 19 April 2022 then current_date()-10 is 9 April 2022.
Output:
The desired output is to return the number of hours worked for each employee during the full week 11 - 17 April only (it would exclude 9th, 10th, 18th and 19th of April from the results).
To obtain this, I tried to filter by having only week starting on a Monday with WHERE day LIKE '%Monday%' but in the example, it would also return the number of hours worked for each employee on 18th and 19th (since the 18th is a Monday). And if I combine this clause with AND (for example WHERE day LIKE '%Monday%' AND day LIKE '%Sunday%', it does not work at all.
Additionally, I see here another potential problem. If a Monday is a day off (like during Easter), then no employees will have hours on that Monday and the rest of the week will then not be returned.
My question: Is there an easy way to get only full weeks (Monday-Sunday) regardless the date range chosen?
Thank you in advance.
Best,
Fabien

You need to use UNNEST and create an array with a range of dates. Also, you need to use DATE_TRUNC to get the week and LAST_DAY to get the last day of the week. You can get the weeks that belong to each day in a range of dates.
You can see this example.
with sample_data as (
SELECT date FROM UNNEST(generate_timestamp_array('2022-04-09 00:00:00', '2022-04-19 00:00:00', INTERVAL 1 DAY)) as date
),
counting as(
select
DATE_ADD(DATE (date), INTERVAL 1 DAY) date
, DATE_TRUNC(DATE(date), WEEK)+1 week_start
, DATE_TRUNC(DATE(date), WEEK) +7 week_end
from sample_data
)
select b.date from (
select week_start,count(*) as ndays
from counting
group by week_start
having ndays=7
) a
join counting b on a.week_start=b.week_start
where timestamp(b.date) between timestamp(b.week_start) and timestamp(b.week_end)
I used the same range of dates like your example.

Related

SQL: MTD Calculation on specific dates

I am trying to Calculate MTD sales on daily sales number but my month starts from 26th of previous month to 25th of next month.
data contains only 3 columns (date, vendor_id, total_sales).
Below Code is working fine for calculating months starting from the 1st. I tried to do this by using the below approaches but it does not works
date(date - interval '25 day') : Not working
Mapping table creation for each day, but will not work for 30/31 days month
Need suggestion on above.
SUM(sales) OVER (
PARTITION BY
vendor_id,
EXTRACT(YEAR FROM date),
EXTRACT(MONTH FROM date)
ORDER BY
date ROWS UNBOUNDED PRECEDING
) AS mtd_total_sales,
So, if the date is >= 26, then "it is the next month".
As a result, something like
CASE
WHEN EXTRACT(DAY FROM date) >= 26 THEN ADD_MONTHS(date, 1)
ELSE date
END
should suffice, since you extract the year and month anyway.

BigQuery SQL to change start date and end date into groups of months

I work with a hotel client where they have a BigQuery database which has hotel booking data. I've shared the relevant columns in the image below which list the names of each hotel, the arrival date of the guest, the departure date, and the revenue generated from the each booking:
My problem statement is that I have to showcase how many rooms have been booked, and how much revenue has been made for each hotel every month where my final grid would look similar to this:
The important points to remember are:
the depart_dt - arrival_dt are the number of nights that the guest is staying
the Rez_rate_total / (depart_dt - arrival_dt) is the revenue made per night
My problem here is trying to figure out how to change the start date and end date columns into groups of months. The challenge comes when a guest arrives in one month and leaves in the next month. For example, Row 5 in the original data has the guest coming in on 18th July and leaving on 1st Aug - so 13 days of his stay and 13 days of revenue has to be included in July and 1 day has to be included in August.
I haven't used SQL in a while so this is as far as I got:
WITH
temp_table AS (
SELECT
hotel_long_nm,
arrival_dt,
depart_dt,
DATE_DIFF(depart_dt, arrival_dt, day) AS room_nights,
rez_rate_total
FROM
`DATABASE.analytics.bookings` )
SELECT
*
FROM
temp_table
Any help would be greatly appreciated!
Consider the following approach:
with bookings as (
select hotel_long_nm, date(arrival_dt) as arrival_dt, date(depart_dt) as depart_dt, rez_rate_total from project.dataset.bookings
),
tmp as (
-- expose the dates in the reservation (excluding last day of reservation)
select *, generate_date_array(arrival_dt,date_sub(depart_dt, interval 1 day)) as stay_dates from bookings
),
calc as (
-- unnest and calculate the daily rate
select
hotel_long_nm,
stay_dt,
1 as stay_nights,
rez_rate_total/array_length(stay_dates) as rez_rate_daily
from tmp
left join unnest(stay_dates) as stay_dt
),
agg as (
-- aggregate to the year-month level
select
date_trunc(stay_dt, month) as year_month,
hotel_long_nm,
sum(stay_nights) as room_nights,
round(sum(rez_rate_daily),2) as rez_rate_total
from calc
group by 1,2
)
select * from agg
order by hotel_long_nm, year_month
You can consider this approach, following this logic.
Validate if both dates are in the same month
If are not in the same month, i get the final date of the month of
arrival date and subtract both dates
I get the first date of the month of the depart date and subtract
and subtract both dates
In this code you can see an example:
SELECT
/*arrival date*/
CURRENT_DATE() AS the_arival,
/*depart_dt*/
DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY) AS the_depart,
/*total of night between arrival date and depart date*/
DATE_DIFF(DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY) , CURRENT_DATE(), DAY) AS total_room_nights,
/* validate if the dates are in the same month or different month if equal 0 same month if >0 another month */
DATE_DIFF(DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY) , CURRENT_DATE(), MONTH) AS Same_Month,/*1 no and 0 yes/
/*in this case are in different month*/
/*I get the final date of the arrival month and subtract with the arrival date*/
DATE_DIFF(DATE_SUB(DATE_TRUNC(DATE_ADD(DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY), INTERVAL 1 MONTH), MONTH), INTERVAL 1 DAY),DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY), DAY) as total_room_nights_first_mont,
/*I get the initial date of the depart month and subtract with the depart date i add +1 because is the night between last day of the mont and first day of the next month*/
DATE_DIFF(DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY),DATE_TRUNC(DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY), MONTH), DAY)+1 as total_room_nights_second_month
You can see more information about the date function.Click Here.

Given a date, select all the week numbers that month contains

I have an ApEx application for tracking working hours.
I have a view that looks like this:
CREATE OR REPLACE VIEW HOURSDAYS
AS SELECT
(MAX("TO_X") - MIN("FROM_X"))*24 -
(max(case when PROJECT_ID = 999 then to_x else to_date('01012000','DDMMYYYY') end) -
max(case when PROJECT_ID = 999 then from_x else to_date('01012000','DDMMYYYY') end))*24 AS TIME_SPENT,
DAY,
PERSON_ID
FROM ATTENDANCE_HOURS
GROUP BY PERSON_ID, DAY
ORDER BY DAY DESC
I need the sum of hours per week. So I have this:
SELECT TO_CHAR(DAY,'IW'), MIN(DAY), MAX(DAY), SUM(TIME_SPENT)
FROM HOURSDAYS
WHERE PERSON_ID = (SELECT ID FROM ATTENDANCE_PEOPLE WHERE MAIL_SSO = V('APP_USER') AND ROLE = 0) AND
EXTRACT (YEAR FROM DAY) = EXTRACT (YEAR FROM TO_DATE(:P100_DATE_PICKER,'DD-MON-RR'))
GROUP BY TO_CHAR(DAY,'IW')
ORDER BY TO_CHAR(DAY,'IW') ASC
And now the fun begins: I have a page item P100_DATE_PICKER and I need to display only those weeks that have > 0 days that belong to the month to which belongs the day I've picked using the date picker.
For example, for 1.1.2015 I want only weeks 1, 2, 3, 4, 5 displayed. 24.3.2015: 9, 10, 11, 12, 13, 14.
If anyone is interested in why would I do that, it is for validation - number of work hours per week cannot exceed 20/32, depends on what type of contract do you have.
You can do like this:
where day >= trunc(trunc(TO_DATE(:P100_DATE_PICKER,'DD-MON-RR'), 'MM'),'IW')
and day < trunc(last_day(TO_DATE(:P100_DATE_PICKER,'DD-MON-RR')),'IW') + 7
The first line starts by trunc to MM, which gives you the first day of the month of your date. Then trunc again to IW gives you the date of the monday in the week that contains the first day of month.
The second line uses last_day to get the last day of the month of your date. Then trunc to IW gets the monday of that week, and adding 7 days get the monday after. Then by using < rather than <= you get the desired result.
Also I suggest you do not group and order by TO_CHAR(DAY,'IW') but instead use TRUNC(DAY,'IW') for your grouping and ordering. Otherwise you can get into problems around new year, because your code for example when showing the weeks for December 2014 would have to show 49 to 52 of year 2014 as well as week 1 of year 2015. If you use TO_CHAR that week 1 would be wrongly sorted before weeks 49 to 52.
Similarly do not use EXTRACT(YEAR FROM ...) or even TO_CHAR(...,'YYYY') when you are working with ISO weeks. Because the date '2014-12-31' belongs to ISO week 1 year 2015, there is a special date format string 'IYYY' which gives the correct year for the ISO week. Try it out and see the difference between 'YYYY' and 'IYYY'.
This will not answer all your questions. This is the quick example of week calculation from a given date according to the subject line of your question. Pls copy/paste to see results. Also, here's the link to check the week numbers:
http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php
SELECT given_date
, end_date
, TRUNC(calc_start_date, 'iw') wk_starts
, TRUNC(calc_start_date, 'iw') + 7 - 1/86400 wk_ends
, TO_CHAR(calc_start_date, 'iw') wk_number
, calc_start_date
FROM
(
SELECT trunc(sysdate, 'mm') given_date
, trunc(sysdate, 'mm')-7 + LEVEL*7 AS calc_start_date
, Last_Day(trunc(SYSDATE, 'mm')) end_date
FROM dual
CONNECT BY LEVEL <= ROUND((trunc(last_day(sysdate)) - trunc(sysdate, 'mm')+7)/7) -- number of weeks --
)
/
GIVEN DATE END DATE WK_STARTS WK_ENDS WK NUMBER
-------------------------------------------------------------------------
1/1/2015 1/31/2015 12/29/2014 1/4/2015 11:59:59 PM 01
1/1/2015 1/31/2015 1/5/2015 1/11/2015 11:59:59 PM 02
1/1/2015 1/31/2015 1/12/2015 1/18/2015 11:59:59 PM 03
1/1/2015 1/31/2015 1/19/2015 1/25/2015 11:59:59 PM 04
1/1/2015 1/31/2015 1/26/2015 2/1/2015 11:59:59 PM 05
The best way to achieve the last part is to have a calendar table that has the following columns
Date, Day eg Mon, Month, Quarter, Year, WeekNo
Then for a given date you can simply select all the weeks that belong to the month.
This allows you to define weekNo so that it fits in with company circumstances. Eg occasionally you may get a 53 week year, especially if they operate 4-4-5 accounting periods
with days as (
select
to_number(to_char(trunc(to_date('2015-03-24', 'yyyy-MM-dd'), 'month'), 'WW')) w1,
to_number(to_char(last_day(to_date('2015-03-24', 'yyyy-MM-dd')), 'WW')) w2
from dual)
select w1+level-1 week from days
connect by level <= w2-w1+1
Subquery days gives minimum and maximum week, query with connect by returns numbers between these values. Please replace example date with :P100_DATE_PICKER.

Show number of weeks in a given month

So, I've been stuck on this problem for last couple of days and I still couldn't come up with solution.
I want to group given month into weeks which is fairly easy but the (horrible)business requirement is to consider a single day also as a week if it
falls on any day between Monday to Sunday. The end day of the week is going to be Sunday.
For example I'll take month of August for demonstration. According to business requirement, this is how the data should be displayed for the given month
First week - August 1st to August 2nd, 2015
Second week - August 3nd to August 9th, 2015
Third week - August 10th to August 16th, 2015
Fourth week - August 17th to August 23rd, 2015
Fifth week - August 24th to August 30th, 2015
Sixth week - August 31st, 2015
I'm completely clueless on how to proceed with the problem due to the sixth week occurrence.
I came across this query on AskTom which display 5 weeks but resets back to 1 on the 31st of August. Moreover, the query doesn't look like an elegant solution.
select dt, to_char( dt+1, 'w' )
from ( select to_date('1-aug-2015')+rownum dt
from all_objects
where rownum < 31 );
Looking for suggestions/insights on the problem.
Thanks
WITH x (dt)
AS ( SELECT DATE '2015-08-01' + LEVEL - 1 dt
FROM DUAL
CONNECT BY DATE '2015-08-01' + LEVEL - 1 < DATE '2015-09-01')
SELECT dt,
SUM (
CASE
WHEN TO_CHAR (dt, 'd') = '2' --if the day is monday
OR TO_CHAR (dt, 'fmdd') = '1' --or if its the first day of the month, assign 1.
THEN
1
ELSE
0
END)
OVER (ORDER BY dt)
wk_nr
FROM x;
First generate all days for the given month.
Identify the beginning of each week and the start of the month by marking it as 1. Mark rest of the days as 0. Here to_char(dt,'d') gives 2 for monday. But may change based on NLS territory of the session.
Now that you have beginning of each week, use SUM to calculate the cumulative sum. This gives you the desired week number.
Sample fiddle.
UPDATE
Looks like 10g doesn't support column alias with the CTE name. Remove it and try.
WITH x
AS (SELECT ....
--TRY THIS
--I DID IT FOR SYSDATE.
SELECT DT,
CASE
WHEN TO_CHAR(DT+1, 'W')='1'
AND SUBSTR(DT,1,2)>'24' THEN '6'
ELSE TO_CHAR(DT+1, 'W')
END
FROM
(SELECT TO_DATE(SYSDATE)+ROWNUM DT FROM ALL_OBJECTS
);
--THE QUERY IN YOUR EXAMPLE.
SELECT DT,CASE WHEN TO_CHAR( DT+1, 'w' )='1' AND SUBSTR( DT,1,2)>'24' THEN '6' ELSE TO_CHAR( DT+1, 'w' ) END
from ( select to_date('1-aug-2015')+rownum dt
FROM ALL_OBJECTS
WHERE ROWNUM < 31
);
A table variable works really well:
declare #calendar table (WkDay date, DayOfWk int, YR INT, MO INT)
DECLARE #BEG_DT DATE
SET #BEG_DT='2016-12-01'
WHILE #BEG_DT <='2250-01-01'
BEGIN
INSERT INTO #calendar VALUES (#BEG_DT, DATEPART(WEEKDAY,#BEG_DT), DATEPART(YEAR,#BEG_DT), DATEPART(MONTH,#BEG_DT))
SET #BEG_DT=DATEADD(DAY,1,#BEG_DT)
END
SELECT *
FROM #calendar
then count the number of "1" dayofwk and you get the number of weeks in a given month

select records weekly from Oracle table

I need to select recods from oracle table for the current calendar week based on a date datatype field. Currently I am doing like this:
select * from my_table where enter_date > sysdate -7
If today is Thursday, this query will select records seven days from today which infiltrates to last week on Thursday. Is there a way to select records for the current calendar week dynamically?
If your week starts on a Monday then:
select ...
from ...
where dates >= trunc(sysdate,'IW')
For alternative definitions of the first day of the week, trunc(sysdate,'W') truncates to the day of the week with which the current month began, and trunc(sysdate,'WW') truncates to the day of the week with which the current year began.
See other available truncations here: http://docs.oracle.com/cd/E11882_01/server.112/e26088/functions255.htm#i1002084
to_char(sysdate, 'd') returns day of week in [1..7] range; so try using
select *
from my_table
where enter_date >= trunc(sysdate) - to_char(sysdate, 'd') + 1
Here is the SQLFiddel Demo
Below is the query which you can try
select Table1.*
from Table1
where dates > sysdate - TO_CHAR(SYSDATE,'D')