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
Related
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.
I need Dynamic query in BigQuery to find Date of Day Saturday has occurred in First week of Year 2021.
Consider the Day on date of 2022-01-01 is Saturday.
Therefore, I want to extract the date of last Year first week on which the Saturday was occurred.
Try the following:
with sample_dates as (
select date
from unnest(generate_date_array('2022-01-01','2022-12-31')) as date
)
select
date
,(select last_year_date
from unnest(generate_date_array(date_sub(date, INTERVAL 1 YEAR), date-1)) as last_year_date
where extract(week from date) = extract(week from last_year_date)
and extract(dayofweek from date) = extract(dayofweek from last_year_date)
) as last_year_date
from sample_dates
;
Given all of 2022 dates, this provides the same day of week for the same week in the previous year.
For example 2022-01-01 results in 2022-01-02 which is the first Saturday of the first week of last year.
If you're only looking for a single date the main portion would be the generating the appropriate date range with the generate_date_array function and then filtering based on the same week number and dayofweek value.
Use below - should work for any year - just replace 2021 with whatever year you need
select date(2021, 1, 8 - extract(dayofweek from date(2021, 1, 1)))
I think this code can help you!
You can find the first Saturday with a weekly cycle
declare
i number := 0;
date1 date := date '2022-01-01';
d varchar2(20);
begin
while i < 7 loop
SELECT case
when TO_CHAR(date1 + i, 'fmDay') = 'Saturday' then
date1 + i
end "Day"
into d
FROM DUAL;
i := i + 1;
dbms_output.put_line(d);
end loop;
end;
Objective:
I'm trying to update all but the latest HR record within a given week.
I've written the solution below, but as I reviewed oracle date functionality more, I think it won't work..
'ww' would only let me partition on weeks - starting on the weekday JAN 1 occurs.
'iw' would be closer, but the weeks would be split at new year, and there would be a record remaining from the last week of DEC - if the new year starts mid week..
However, I'm just needing all but the latest record - within that week (sun - Sat) updated.
here's what I've attempted so far.. :
UPDATE hr_info.hr_hours
SET expire_date = SYSDATE, deleted = 'Y', update_date = SYSDATE
WHERE (ROWID, compnay_id) IN (SELECT ROWID, compnay_id
FROM (SELECT hrs.*,
ROW_NUMBER ()
OVER (
PARTITION BY emp_nbr,
TO_CHAR (
hrs_effective_date + 1,
'iw'),
TO_CHAR (
hrs_effective_date,
'yy')
ORDER BY
hrs_effective_date DESC)
rown
FROM hr_info.hr_hours hrs
WHERE compnay_id = 3
AND expire_date =
TO_DATE ('12/31/9999',
'mm/dd/yyyy'))
WHERE rown > 1;
COMMIT;
requested example data :
So, in this example, I'm afraid Dec 1 would count as its own week with 'iw'.
I'd just want to keep / not update the 1/4 record for employee 22 and the 1/5 record for employee 33, and the 1/2 record for employee 44.
And, this is just a small snapshot of the data. Records would hypothetically be created on every week - going back years for each employee.
I am thinking something like this:
UPDATE hr_info.hr_hours h
SET expire_date = SYSDATE,
deleted = 'Y',
update_date = SYSDATE
WHERE h.hrs_effective_date < (
SELECT MAX(h2.hrs_effective_date)
FROM hr_info.hr_hours h2
WHERE h2.company_id = h.company_id AND
TRUNC(hr2.hrs_effective_date, 'IW') = TRUNC(hr.hrs_effective_date, 'IW')
);
Week numbering and thus specific dates within them can be represented 'WW' or 'IW' specification but neither serve OP requirements. The 'IW' represents weeks according to the ISO 8601 date specification; 'WW' according to Gregorian week numbering system. (see https://en.wikipedia.org/wiki/ISO_week_date ) The trouble is neither identify weeks in the required Sun-Sat period. So a third option is needed and that is to calculate the necessary weekly periods. The trouble revolves around the 1st week of the any given year, but we easily find the ending data of the 1st period and from there the beginning date as follows: 1) Truncate the date to the year. 2) Backup 1 day. 3) Find the next Saturday. Result is last day of 1st period. 4) Backup 6 days to the first day of 1st period. That becomes:
select next_day(trunc(sysdate,'yyyy')-1,'sat') - 6;
5) We can now calculate any Sat-Sun dates ranges for as many periods as we want.
With that in hand just find the max date within each period and update other rows.
update hr_hours
set deleted='Y'
, update_date = sysdate
, expire_date = sysdate
where 1=1
and expire_date = date '9999-12-31'
and (employee_nbr,hrs_effective_date) not in
(with date_range as (select date '2019-01-04' sdt, date '2019-03-01' edt from dual) -- get full date range
, weeks as (select level-1 wk from dual connect by level <= (select trunc( (edt-sdt)/7)+1 from date_range)) -- calc number of weekly periods
, yr1st as (select next_day(trunc(sdt,'yyyy'), 'Sat') - 6 p1_start from date_range) -- calculate 1st preiod start date
, periods as (select p1_start+(7*wk) period_begin, p1_start+(7*wk)+6 period_end from yr1st, weeks) -- calculate each period start and end date
select employee_nbr, eff_date
from (
select h.employee_nbr,max(h.hrs_effective_date) eff_date,p.period_begin
from hr_hours h
, periods p
where 1 = 1
and h.hrs_effective_date between p.period_begin and p.period_end
group by h.employee_nbr,p.period_begin));
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.
I want to return results where if the date falls on 10 & 28 of each month, but if either is a weekend return the result for Friday (first working day before).
Eg. if the following lines to be returned are
10 Oct 2010 Sunday
28 Oct 2010 Thursday
In the table I have
LineId Date
1 08 Oct 2010
2 28 Oct 2010
so, because 10 October is a Sunday, therefore won't be in the table, it will return LineID 1 as the is first working day before.
Thank you.
DATEPART(WEEKDAY and DATEPART(DW are dependant on the DATEFIRST setting. To avoid incorrect results the ##DATEFIRST function can be used.
WITH T(D) AS
(
SELECT CAST('20111008' AS DATE) UNION ALL
SELECT CAST('20111009' AS DATE) UNION ALL
SELECT CAST('20111010' AS DATE) UNION ALL
SELECT CAST('20111011' AS DATE) UNION ALL
SELECT CAST('20111012' AS DATE) UNION ALL
SELECT CAST('20111013' AS DATE) UNION ALL
SELECT CAST('20111014' AS DATE)
)
SELECT CASE
WHEN ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 > 1 THEN D
ELSE DATEADD(DAY, -( 1 + ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 ), D)
END AS WeekDayOrPrecedingFriday
FROM T
Select Case
When DatePart(dw,SampleData.[Date]) = 1 Then DateAdd(d,-2,SampleData.[Date])
When DatePart(dw,SampleData.[Date]) = 7 Then DateAdd(d,-1,SampleData.[Date])
Else SampleData.[Date]
End
From (
Select Cast('2010-10-10' As datetime) As [Date]
Union All Select '2010-10-28' As [Date]
) As SampleData
You may find that it is easier to have a Calendar table with one row for all days you need where you indicate whether the given day is a "working" day. In this way, you can easily account for holidays and the actual day off for holidays (e.g. if July 4th, in the US, is on a Saturday, mark the preceding Friday as a day off.).
If you are really worried about dealing with DateFirst, just set it prior to running your query:
Set DateFirst 7;
The above is the US default setting which is that Sunday is the first day of the week.
I really like to use a calendar table for queries like these.
-- For convenience, I'll use a view. The view "weekdays" is a proper
-- subset of the table "calendar".
create view weekdays as
select * from calendar
where day_of_week in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri');
Having done that, the query is not only dead simple, it can easily be seen to be right.
select max(cal_date)
from weekdays
where cal_date <= '2010-10-10' -- Returns 2011-10-08
Doesn't account for holidays that might fall on the 10th or 28th, but that's easy enough to remedy.