Count in sql birthday on a weekend - sql

I'm trying to write a program in pl/sql (oracle) that must calculate how many times someone 's birthday was on a weekend.
This is what i got, but im missing somthing like an extraction at everyloop (-1 year) from 2018 to 1990 for example.
Can someone help me out please?
SET SERVEROUTPUT ON;
DECLARE
v_counter number default 0;
v_real_birthdate date default to_date('01/01/1990', 'DD/MM/YYYY');
v_birthdate date default to_date('01/01/2018', 'DD/MM/YYYY');
BEGIN
WHILE v_counter < 28
LOOP
v_leeftijd := v_leeftijd +1;
dbms_output.put_line( ( TO_CHAR( v_birthdate, 'DAY' ) ) );
END LOOP;
END;

If we suppose that I was born on today's day 2010 (which would then be 2010-09-12 (yyyy-mm-dd)), the result would be as follows, step by step.
MY_BIRTHDAY represents what we agreed to be my birthday
YEARS uses hierarchical query and produces my birthdays from 2010 to current year (2018)
DAYS extracts day name from my birthday for every year, using English language
the final result filters out weekends (sat, sun)
If you're interested in finding out what every CTE returns, run it one by one and you'll see.
SQL> with
2 my_birthday as
3 (select date '&par_birthday' birthday from dual),
4 years as
5 (select to_date((extract(year from birthday) + level - 1) ||'-'||
6 case when to_char(birthday, 'mm-dd') = '02-29' then '02-28'
7 else to_char(birthday, 'mm-dd')
8 end,
9 'yyyy-mm-dd'
10 ) birthday_yr
11 from my_birthday
12 connect by level <= extract(year from sysdate) -
13 extract(year from birthday) + 1
14 ),
15 days as
16 (select birthday_yr,
17 to_char(birthday_yr, 'dy', 'nls_date_language=english') dy
18 from years
19 )
20 select birthday_yr, dy
21 from days
22 where dy in ('sat', 'sun');
Enter value for par_birthday: 2010-09-12
BIRTHDAY_Y DY
---------- ---
2010-09-12 sun
2015-09-12 sat
SQL> /
Enter value for par_birthday: 2012-02-29
BIRTHDAY_Y DY
---------- ---
2015-02-28 sat
2016-02-28 sun
SQL>

You can use the following query (assuming Feb 06, 1981 is the birthday):
WITH b AS (SELECT TO_DATE('02/06/1981', 'MM/DD/RRRR') birthday FROM dual)
SELECT SUM(CASE WHEN TO_CHAR(birthday + (INTERVAL '1' YEAR) * (LEVEL -1), 'FMD') IN ('1','7') THEN 1 END)
FROM b
CONNECT BY birthday + (INTERVAL '1' YEAR) * (LEVEL - 1) <= sysdate
Where TO_CHAR(...,'FMD') gives the day of the week from 1 = Sunday till 7 = Saturday

Related

Oracle - Get all days from month

How create select which return all days from month where month = PARAMETER_MONTH eg. 5 and year = extract(year from sysdate).
1
2
3
..
30
31
"Row generator" is the search keyword. For example:
SQL> with temp (col) as
2 (select to_date(&par_month, 'mm') from dual)
3 select to_number(to_char(col + level - 1, 'dd')) day
4 from temp
5 connect by level <= last_day(col) - col + 1
6 order by day;
Enter value for par_month: 5
DAY
----------
1
2
3
4
5
6
<snip>
29
30
31
31 rows selected.
SQL>
TO_DATE function will convert entered value into the 1st day of that month in current year, so you don't have to worry about "and year = extract(year from sysdate)" you mentioned in question.

Count days between two segments

I have two tables below. I want to count the number of days, Monday-Friday only between Hire_dt and end of calendar month the hire date falls under.
TableA
Hire_DT Id
09/26/2018 1
TableCalendar:
Date WorkDay(M-F) EOM WorkDay
09/26/2018 Wednesday 9/30/2018 1
09/27/2018 Thursday 09/30/2018 1
09/28/2018 Friday 09/30/2018 1
09/29/2018 Saturday 09/30/2018 0
09/30/2018 Sunday 09/30/2018 0
Expected Results
Hire_dt WorkDaysEndMonth WorkDaysEndMonth --counting hire_dt
09/26/2018 2 3
Here is one way to do the calculation - WITHOUT using a calendar table. The only input data is what comes from your first table (ID and HIRE_DATE), which I included in a WITH clause (not part of the query that answers your question!). Everything else is calculated. I show how to compute the number of days INCLUDING the hire date; if you don't need that, subtract 1 at the end.
TRUNC(<date>, 'iw') is the Monday of the week of <date>. The query computes how many days are in the EOM week, between Monday and EOM, but no more than 5 (in case EOM may be a Saturday or Sunday). It does a similar calculation for HIRE_DATE, but it counts the days from Monday to HIRE_DATE excluding HIRE_DATE. The last part is adding 5 days for each full week between the Monday of HIRE_DATE and the Monday of EOM.
with
sample_data(id, hire_date) as (
select 1, to_date('09/26/2018', 'mm/dd/yyyy') from dual union all
select 2, to_date('07/10/2018', 'mm/dd/yyyy') from dual
)
select id, to_char(hire_date, 'Dy mm/dd/yyyy') as hire_date,
to_char(eom, 'Dy mm/dd/yyyy') as eom,
least(5, eom - eom_mon + 1) - least(5, hire_date - hire_mon)
+ (eom_mon - hire_mon) * 5 / 7 as workdays
from (
select id, hire_date, last_day(hire_date) as eom,
trunc(hire_date, 'iw') as hire_mon,
trunc(last_day(hire_date), 'iw') as eom_mon
from sample_data
)
;
ID HIRE_DATE EOM WORKDAYS
---------- ----------------------- ----------------------- ----------
1 Wed 09/26/2018 Sun 09/30/2018 3
2 Tue 07/10/2018 Tue 07/31/2018 16
You may use the following routine ( where last_day function is a great contributor ):
SQL> alter session set NLS_TERRITORY="AMERICA";
SQL> create table TableA( ID int, Hire_DT date );
SQL> insert into TableA values(1,date'2018-09-26');
SQL> select sum(case when mod(to_char(myDate,'D'),7) <= 1 then 0 else 1 end )
as "WorkDaysEndMonth"
from
(
select Hire_DT + level - 1 myDate
from TableA
where ID = 1
connect by level <= last_day(Hire_DT) - Hire_DT + 1
);
WorkDaysEndMonth
----------------
3
P.S. integer value comes from to_char(<date>,'D') depends on the NLS_TERRITORY setting. Here I used AMERICA for which Saturday is the 7th and Sunday is the 1st day, while for UNITED KINGDOM(or my country TURKEY) setting those are 6th and 7th respectively.
Rextester Demo

Counting the number of days excluding sunday between two dates

I am trying to calculate number of days betwen two dates excluding sundays. This is my query,
SELECT F_PLANHM_END_DT
- F_PLANHM_ST_DT
- 2
* (TO_CHAR (F_PLANHM_END_DT, 'WW') - TO_CHAR (F_PLANHM_ST_DT, 'WW'))
FROM VW_S_CURV_PROC
WHERE HEAD_MARK = 'IGG-BLH-BM 221';
SELECT COUNT (*)
FROM (SELECT SYSDATE + l trans_date
FROM ( SELECT LEVEL - 1 l
FROM VW_S_CURV_PROC
CONNECT BY LEVEL <= ( (SYSDATE + 7) - SYSDATE)))
WHERE TO_CHAR (trans_date, 'dy') NOT IN ('sun');
I am retrieving date from a view called VW_S_CURV_PROC with start date : F_PLANHM_ST_DT and end date F_PLANHM_END_DT. Somehow i cant make this to work. Please help me...
You could use the ROW GENERATOR technique to first generate the dates for a given range, and then exclude the SUNDAYs.
For example, this query will give me the total count of days between 1st Jan 2014 and 31st Dec 2014, excluding the Sundays -
SQL> WITH DATA AS
2 (SELECT to_date('01/01/2014', 'DD/MM/YYYY') date1,
3 to_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual
5 )
6 SELECT SUM(holiday) holiday_count
7 FROM
8 (SELECT
9 CASE
10 WHEN TO_CHAR(date1+LEVEL-1, 'DY','NLS_DATE_LANGUAGE=AMERICAN') <> 'SUN'
11 THEN 1
12 ELSE 0
13 END holiday
14 FROM data
15 CONNECT BY LEVEL <= date2-date1+1
16 )
17 /
HOLIDAY_COUNT
-------------
313
SQL>

Getting Month values for specific year

Fisc_prd stands for month
fisc_yr stands for year
I need the result in such a way that if current month is 01(that is Jan) then i need all the period for previous year i.e 1 to 12 and if the current month is not equal to 01 then i need all the period for current year less than current month(e.g if the current month is 3 then i need 1 to 2 as fisc_prd)
I can only get all the fisc_prd for previous year but cant get fisc_prd less than current month for current year. In Below query, CALENDAR table contains all the Month and Year values.By usig this query i can get all the fisc_prd for previous year when current month is 01.
SELECT FISC_PRD,FISC_YR FROM CALENDAR WHERE FISC_YR=(SELECT DECODE(TO_NUMBER(TO_CHAR(SYSDATE,'MM')),01,(TO_NUMBER(TO_CHAR(SYSDATE,'YYYY'))-1) ,TO_NUMBER(TO_CHAR(SYSDATE,'YYYY'))) FROM DUAL)
Please share your ideas
If you're basing this on the current date then you can use a connect-by hierarchical query to get the period and year:
select extract(month from add_months(sysdate, 1-level)) as fisc_prd,
extract(year from add_months(sysdate, 1-level)) as fisc_year
from dual
where level > 1
connect by level <= case when extract(month from sysdate) = 1 then 13
else extract(month from sysdate) end;
Today that gives:
FISC_PRD FISC_YEAR
---------- ----------
1 2015
To check how it behaves for other dates you can use a modified version that uses a specific date but the same logic; I'm using a bind variable here:
variable dt varchar2;
exec :dt := '2015-01-17';
select extract(month from add_months(to_date(:dt, 'YYYY-MM-DD'), 1-level)) as fisc_prd,
extract(year from add_months(to_date(:dt, 'YYYY-MM-DD'), 1-level)) as fisc_year
from dual
where level > 1
connect by level <= case
when extract(month from to_date(:dt, 'YYYY-MM-DD')) = 1 then 13
else extract(month from to_date(:dt, 'YYYY-MM-DD'))
end;
FISC_PRD FISC_YEAR
12 2014
11 2014
10 2014
9 2014
8 2014
7 2014
6 2014
5 2014
4 2014
3 2014
2 2014
1 2014
Changing the value of dt seems to give the result you described; for example with
exec :dt := '2015-03-17';
you get:
FISC_PRD FISC_YEAR
---------- ----------
2 2015
1 2015
But this is just to test the logic, you can just use sysdate rather than a bind variable.
The 1-level and using 13 if the current month is January are because you don't want the current month to be included in the result.
This ought to work:
with sample_data as (select to_number(to_char(add_months(sysdate, 6 - level), 'mm')) fisc_prd,
to_number(to_char(add_months(sysdate, 6 - level), 'yyyy')) fisc_yr
from dual
connect by level <= 24)
select fisc_prd,
fisc_yr
from sample_data
where case when to_char(sysdate, 'mm') = '01' then to_number(to_char(sysdate, 'yyyy')) -1
else to_number(to_char(sysdate, 'yyyy')) end = fisc_yr
and case when to_char(sysdate, 'mm') = '01' then 13
else to_number(to_char(sysdate, 'mm')) end > fisc_prd;
If your colum "FISC_YR" is datetime datatype then the below query will works for you
SELECT FISC_PRD,FISC_YR
FROM CALENDAR
WHERE
(month(getdate())!=1 AND month(FISC_YR) in(month(getdate())- 1,month(getdate())-2) AND year(getdate())=year(FISC_YR))
OR
(month(getdate())=1 AND year(getdate())-1=year(FISC_YR))

return zero when grouping by week after select -

I have some code below
select
'WEEK '|| row_number() over(order by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW')) WEEK,
count(*)
from opencall
where trunc(to_date(substr(LOGDATE,1,10), 'DD-MM-YY')) BETWEEN '01-JAN-14' AND '31-DEC-14'
group by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW')
order by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW');
Which is a great piece of code that another stackoverflow chap wrote for me for extracting calls from the database and counting / grouping them by week and it works perfectly if there are always calls in every week.
However I've tried reusing this code for other uses and if there is no result in a given week I get fewer groupings i.e. perhaps only 37 weeks returned instead of the current 50 weeks for instance.
As this is removing the weeks where there are no calls the week numbers dont equate to real week numbers in the year. So I don't actually know what weeks the calls are being returned from as the weeks remain sequential rather than having zeros in the weeks with no calls.
Is there any way to return a zero value if there are no results returned for a given week? I imagine that may make the code much more complex.
If I am using this code over a week
e.g:
WEEK 1 231
WEEK 2 657
WEEK 3 529
WEEK 4 606
WEEK 5 594
WEEK 6 517
WEEK 7 683
WEEK 8 664
WEEK 9 583
WEEK 10 740
If you want to know which week you are showing, then why the fuss with row_number()?
This gives you the year separated in weeks, starting with the first seven days make week 1 and so on:
select to_char(mydate, 'ww'), count(*)
from
(
select to_date(substr(logdate,1,10), 'dd-mm-yy') as mydate
from opencall
)
where extract(year from mydate) = 2014
group by to_char(mydate, 'ww')
order by to_char(mydate, 'ww');
(Why is LOGDATE a string? And why are its first 10 characters in the format 'DD-MM-YY' which is 8 characters long?)
select
'WEEK '|| row_number() over(order by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW')) WEEK,
row_number() over(order by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW')) mWeek,
count(*)
from (SELECT to_char(SYSDATE - (LEVEL * 7),'IW') TheDate
FROM dual
CONNECT BY LEVEL <= 52) WoY
LEFT JOIN opencall
on Woy.TheDate = OpenCall.mWeek
where trunc(to_date(substr(LOGDATE,1,10), 'DD-MM-YY')) BETWEEN '01-JAN-14' AND '31-DEC-14'
group by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW')
order by trunc(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'SATURDAY'), 'IW');
Something like this may work.
I tried something like this with test data and works. I hope this works for you too:
select
'WEEK '|| row_number() over(order by nvl(to_char(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY') ,'SATURDAY'), 'IW'),week)) WEEK
,count(wk)
from (select t.*,to_char(to_date(substr(LOGDATE,1,10), 'DD-MM-YY'),'IW') wk,w.week from
table1 t right join (SELECT lpad(level,2,0) week
FROM dual
CONNECT BY LEVEL <= 52 ) w on (to_char(to_date(substr(t.LOGDATE,1,10), 'DD-MM-YY'),'IW') = w.week)
order by w.week)
where trunc(to_date(substr(LOGDATE,1,10), 'DD-MM-YY')) BETWEEN '01-JAN-14' AND '31-DEC-14'
group by nvl(to_char(NEXT_DAY(to_date(substr(LOGDATE,1,10), 'DD-MM-YY') ,'SATURDAY'), 'IW'),week)
;
--Creating a test table under schema alber and inserting data on it for all days on weeks multiple of 4.
create table alber.week_table (mydate date, mydescription varchar2(100));
declare
cur_date date := to_date('01/01/'||to_char(sysdate,'yyyy'),'dd/mm/yyyy') ;
week number := 01;
begin
EXECUTE IMMEDIATE 'TRUNCATE TABLE alber.week_table';
while (cur_date < sysdate) loop
if mod(week,4) = 0 then
insert into alber.week_table values(cur_date, to_char(sysdate, 'day dd month yyyy hh24:mi:ss')) ;
end if;
cur_date := cur_date + 1;
week := to_number(to_char(cur_date,'IW'));
end loop;
end;
--Then one possible solution would be:
select theweek "Week", sum( decode( nvl(mydescription,0),'0',0,1) ) "Num of Records"
FROM
(
SELECT to_char(SYSDATE - (LEVEL * 7),'IW') theweek
FROM dual
CONNECT BY LEVEL <= 52
) w
LEFT JOIN week_table ON w.theweek = to_char(mydate,'IW')
group by theweek
order by 1;
Result
Week Num of Records
---- ----------------------
01 0
02 0
03 0
04 7
05 0
06 0
07 0
08 7
09 0
10 0
11 0
12 7
13 0
14 0
15 0
16 7
17 0
18 0
.
.
.