How do I sum records by week using pl/sql? - oracle

I have a query in Oracle for a report that looks like this:
SELECT TRUNC (created_dt) created_dt
, COUNT ( * ) AllClaims
, SUM(CASE WHEN filmth_cd in ('T', 'C') THEN 1 ELSE 0 END ) CT
, SUM(CASE WHEN filmth_cd = 'W' THEN 1 ELSE 0 END ) Web
, SUM(CASE WHEN filmth_cd = 'I' THEN 1 ELSE 0 END ) Icon
FROM claims c
WHERE c.clsts_cd NOT IN ('IN', 'WD')
AND TRUNC (created_dt) between
to_date('1/1/2006', 'dd/mm/yyyy') AND
to_date('1/1/2100', 'dd/mm/yyyy')
GROUP BY TRUNC (created_dt)
ORDER BY TRUNC (created_dt) DESC;
It returns data like this:
Create_Dt AllClaims CT Web Icon
1/26/2011 675 356 285 34
1/25/2011 740 322 379 39
...
What I need is a result set that sums all of the daily values into a weekly value. I am pretty new to PL/SQL and not sure where to begin.

Something like
SELECT TRUNC (created_dt, 'IW') created_dt
, COUNT ( * ) AllClaims
, SUM(CASE WHEN filmth_cd in ('T', 'C') THEN 1 ELSE 0 END ) CT
, SUM(CASE WHEN filmth_cd = 'W' THEN 1 ELSE 0 END ) Web
, SUM(CASE WHEN filmth_cd = 'I' THEN 1 ELSE 0 END ) Icon
FROM claims c
WHERE c.clsts_cd NOT IN ('IN', 'WD')
AND TRUNC (created_dt) between
to_date('1/1/2006', 'dd/mm/yyyy') AND
to_date('1/1/2100', 'dd/mm/yyyy')
GROUP BY TRUNC (created_dt, 'IW')
ORDER BY TRUNC (created_dt, 'IW') DESC;
will aggregate the data based on the first day of the ISO week.

Here is a simple example that I quite often use:
SELECT
TO_CHAR(created_dt,'WW'),
max(created_dt),
COUNT(*)
from MY_TABLE
group by
TO_CHAR(created_dt,'WW');
The to_char(created_dt,'WW') create the groups, and the max(created_dt) displays the last day of the week. Use min(created_dt) if you want to display the first day of the week.

Related

In SQL Developer, how can I simulate nested for loops to extract a large volume of counts?

I am using Oracle SQL Developer and I need to pull counts from a very large database.
I need to count both the number of visits and the number of unique visitors at each of 5 sites (and in total) for each of 10 quarters (and in total) - resulting in 120 counts. Each row in this database represents one visit, and the visitors each have a unique visitor_ID.
Currently, I have one line for each count, but I need to make modifications and I don't want to do it in such an inefficient way this time.
select
sum(case when visit_date between '01-JAN-2019 00:00:00' and '31-MAR-2019 00:00:00' and site=site1 then 1 else 0 end) as 19Q1_visits_site1,
count(distinct case when visit_date between '01-JAN-2019 00:00:00' and '31-MAR-2019 00:00:00' and site=site1 then visitor_id) as 19Q1_unique_site1,
[...]
from visitdata
where [additional qualifiers];
If possible, I would like to create something along the lines of:
19Q1 = visit_date between '01-JAN-2019 00:00:00' and '31-MAR-2019 00:00:00'
19Q2 = visit_date between '01-APR-2019 00:00:00' and '30-JUN-2019 00:00:00'
[...]
allQ = visit_date between '01-JAN-2019 00:00:00' and '30-SEP-2021 00:00:00'
S1 = site in (site1)
[...]
allS = site in (site1, site2, site3, site4, site5)
sites = [S1, S2, S3, S4, S5, allS]
quarters = [19Q1, 19Q2, ..., allQ]
for s in sites:
for q in quarters:
select
sum(case when q and s then 1 else 0 end) as (str(q) + 'visits' + str(s)),
count(distinct case when q and s then visitor_id) as (str(q) + 'unique' + str(s))
from visitdata
where [additional qualifiers];
I know SQL doesn't do for loops. Any advice would be fantastic so I don't have to create another embarrassing script with almost 200 lines. Thanks!
Very briefly, use a built-in or user-defined function that takes a date and returns the quarter to which that date belongs. (SQL Server supports "quarter" as a datepart, but you can probably write this yourself if Oracle doesn't support it. You could also add visit_quarter to your table as a materialized computed column and even index it if you use this a lot.) Then you can write a single grouping query along the lines of
SELECT
site,
quarter(visit_date) as Q,
COUNT(visitor_id) as numvisits,
COUNT(DISTINCT visitor_id) AS numDistinctVisitors
FROM T
WHERE <additional conditions>
GROUP BY site, quarter(visit_date)
ORDER BY site, Q
For finding quarters you can use TO_CHAR() on dates, and for changing the start date and end date of a quarter, you can use ADD_MONTHS() eg
Table
create table randomdates( date_ )
as
select sysdate - dbms_random.value( 1, 600 )
from dual
connect by level <= 50 ;
Query
select date_
, to_char( date_, 'YYYY-Q' ) quarter
from randomdates
;
-- result
DATE_ QUARTER
09/23/2021 2021-3
09/24/2020 2020-3
03/23/2020 2020-1
03/29/2021 2021-1
11/29/2020 2020-4
03/05/2021 2021-1
04/08/2021 2021-2
...
GROUP BY should also be possible - as #Steve Kass suggested.
select to_char( date_, 'YYYY-Q' ), count(*)
from randomdates
group by to_char( date_, 'YYYY-Q' )
order by 1 desc
;
TO_CHAR(DATE_,'YYYY-Q') COUNT(*)
2021-4 6
2021-3 13
2021-2 22
2021-1 13
2020-4 20
2020-3 12
2020-2 10
2020-1 4
...
From your comment:
I'm using a modified fiscal year anyway. Do you know how I can define
quarters when the visit_date is stored as yyyymmdd?
If you need different start/end days for the, ADD_MONTHS() could help eg the modified_quarters in the following query start a month later than the "standard" quarters. Regarding the dates: in Oracle, they contain the century, the year within the century, the month, the day of the month, the hour, the minute and the second (7 bytes). You can just use TO_CHAR() and pick up whichever component (of the date) you need using the "Format Model" eg 'Q' in the example below.
-- Query executed in APEX.
-- Column date_ : no formatting (compare the output to the same query in the dbfiddle).
select date_
, to_char( date_, 'YYYY-Q' ) quarter
, to_char( date_, 'YY' ) || 'Q' || to_char( date_, 'Q' ) quarter_
, to_char( add_months( date_, 1 ), 'YYYY-Q' ) modified_quarter
from randomdates
;
DATE_ QUARTER QUARTER_ MODIFIED_QUARTER
09/23/2021 2021-3 21Q3 2021-4
09/24/2020 2020-3 20Q3 2020-4
03/23/2020 2020-1 20Q1 2020-2
03/29/2021 2021-1 21Q1 2021-2
11/29/2020 2020-4 20Q4 2020-4
03/05/2021 2021-1 21Q1 2021-2
For calculating subtotals and total (counts) per site, you could use GROUP BY ROLLUP() eg
Table & data
-- Caution: dates in this table are not the same as in the randomdates table.
create table sitesanddates( site_, date_ )
as
select
trunc( dbms_random.value( 1, 6 ) )
, sysdate - dbms_random.value( 1, 600 )
from dual
connect by level <= 50 ;
-- group by site and quarter
select site_, to_char( date_, 'YYYY-Q' ), count(*)
from sitesanddates
group by site_, to_char( date_, 'YYYY-Q' )
order by 1, 2
;
SITE_ TO_CHAR(DATE_,'YYYY-Q') COUNT(*)
1 2020-1 1
1 2020-4 3
1 2021-1 1
1 2021-2 1
1 2021-3 2
2 2020-1 1
2 2020-2 1
GROUP BY ROLLUP
select site_, to_char( date_, 'YYYY-Q' ) q_, count(*)
from sitesanddates
group by rollup( site_, to_char( date_, 'YYYY-Q' ) )
order by 1, 2
;
SITE_ Q_ COUNT(*)
1 2020-1 1
1 2020-4 3
1 2021-1 1
1 2021-2 1
1 2021-3 2
1 - 8 -- <- subtotal for site 1
2 2020-1 1
2 2020-2 1
...
5 2020-4 2
5 2021-1 2
5 2021-2 1
5 2021-4 1
5 - 10 -- <- subtotal for site 5
- - 50 -- <- grand total
link to dbfiddle

How to get last workday before holiday in Oracle [duplicate]

This question already has answers here:
How to get the previous working day from Oracle?
(4 answers)
Closed 1 year ago.
need help for some oracle stuff ..
I need to get Day-1 from sysdate, holiday and weekend will be excluded .
And for holiday, we need to get the range to get the last workday before holiday.
The start date and end date will coming from my holiday table.
ex :
Holiday Table
HolidayName
Start_date
End_Date
holiday1
5th Aug'21
6th Aug'21
condition :
this query run on 9th Aug 2021
expected result :
4th Aug'21
I've tried some query and function but I just can't get what I need.
Thanks a lot for help!
Here's one way to do it.
select max(d) as last_workday
from (select trunc(sysdate)-level as d from dual connect by level < 30) prior_month
where to_char(d, 'DY') not in ('SAT','SUN')
and not exists (select holidayname from holiday_table
where prior_month.d between start_date and end_date)
;
Without seeing your Holiday table, it's hard to say how many days back you would need to look to find the last workday. If you have a holiday that lasts for more than 30 days, you'll need to change the 30 to a larger number.
You can use a simple case expression to determine what day of the week the start of your holiday is, then subtract a number of days based on that.
WITH
holiday (holidayname, start_date, end_date)
AS
(SELECT 'holiday1', DATE '2021-8-5', DATE '2021-8-6' FROM DUAL
UNION ALL
SELECT 'Christmas', DATE '2021-12-25', DATE '2021-12-26' FROM DUAL
UNION ALL
SELECT 'July 4th', DATE '2021-7-4', DATE '2021-7-5' FROM DUAL)
SELECT holidayname,
start_date,
end_date,
start_date - CASE TO_CHAR (start_date, 'Dy') WHEN 'Mon' THEN 3 WHEN 'Sun' THEN 2 ELSE 1 END AS prior_business_day
FROM holiday;
HOLIDAYNAME START_DATE END_DATE PRIOR_BUSINESS_DAY
______________ _____________ ____________ _____________________
holiday1 05-AUG-21 06-AUG-21 04-AUG-21
Christmas 25-DEC-21 26-DEC-21 24-DEC-21
July 4th 04-JUL-21 05-JUL-21 02-JUL-21
You can use a recursive sub-query factoring clause from this answer:
WITH start_date (dt) AS (
SELECT DATE '2021-05-02' FROM DUAL
),
days ( dt, day, found ) AS (
SELECT dt,
TRUNC(dt) - TRUNC(dt, 'IW'),
0
FROM start_date
UNION ALL
SELECT dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END,
CASE WHEN day IN (0, 6, 5) THEN 4 ELSE day - 1 END,
CASE WHEN h.start_date IS NULL THEN 1 ELSE 0 END
FROM days d
LEFT OUTER JOIN holidays h
ON ( dt - CASE day WHEN 0 THEN 3 WHEN 6 THEN 2 ELSE 1 END
BETWEEN h.start_date AND h.end_date )
WHERE found = 0
)
SELECT dt
FROM days
WHERE found = 1;
Which, for the sample data:
CREATE TABLE holidays (HolidayName, Start_date, End_Date) AS
SELECT 'holiday1', DATE '2021-08-05', DATE '2021-08-06' FROM DUAL;
Outputs:
DT
2021-08-04 00:00:00
db<>fiddle here
Don't know if it's very efficient. Did it just for fun
create table holidays (
holiday_name varchar2(100) primary key,
start_date date not null,
end_date date not null
)
/
Table created
insert into holidays (holiday_name, start_date, end_date)
values ('holiday1', date '2021-08-05', date '2021-08-06');
1 row inserted
with days_before(day, wrk_day) as
(select trunc(sysdate - 1) d,
case
when h.holiday_name is not null then 0
when to_char(trunc(sysdate - 1), 'D') in ('6', '7') then 0
else 1
end work_day
from dual
left join holidays h
on trunc(sysdate - 1) between h.start_date and h.end_date
union all
select db.day - 1,
case
when h.holiday_name is not null then 0
when to_char(db.day - 1, 'D') in ('6', '7') then 0
else 1
end work_day
from days_before db
left join holidays h
on db.day - 1 between h.start_date and h.end_date
where db.wrk_day = 0) search depth first by day set order_no
select day from days_before where wrk_day = 1;
DAY
-----------
04.08.2021

Remove duplicate in CONNECT BY LEVEL query

I've tried find all days between 2 dates and I have a query here
with tmp as(
select 1 sno, to_date('20181227', 'YYYYMMDD') curr_date, to_date('20181231', 'YYYYMMDD') curr_date2 from dual
union all
select 2 sno, to_date('20181227', 'YYYYMMDD'), to_date('20181231', 'YYYYMMDD') from dual
)
SELECT sno, curr_date + level - 1 DAY, LEVEL
FROM tmp
CONNECT BY curr_date + level -1 <= curr_date2
But I received duplicate result and I found because of missing START WITH clause here but I don't know START WITH where!!
I still can't find any solution to remove duplicate at result.
Use the prior + sys_guid method.
...
CONNECT BY curr_date + level -1 <= curr_date2
and prior sno = sno
and prior sys_guid() is not null;
DEMO

Oracle: Days between two date and Exclude weekdays how to handle negative numbers

I have two date columns and trying to measure days between the two dates excluding weekends. I'm getting a negative number and need help solving.
Table
CalendarDate DayNumber FirstAssgn FirstCnt DayNumber2 Id BusinessDays
5/21/2017 Sunday 5/21/17 5/21/17 Sunday 1 -1
Query:
TRUNC(TO_DATE(A.FIRST_CONTACT_DT, 'DD/MM/YYYY')) - TRUNC(TO_DATE(A.FIRST_ASSGN_DT, 'DD/MM/YYYY'))
- ((((TRUNC(A.FIRST_CONTACT_DT,'D'))-(TRUNC(A.FIRST_ASSGN_DT,'D')))/7)*2)
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english') ='SUN' THEN 1 ELSE 0 END)
- (CASE WHEN TO_CHAR(A.FIRST_CONTACT_DT,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END)
- (SELECT COUNT(1) FROM HUM.CALENDAR CAL
WHERE 1=1
AND CAL.CALENDAR_DATE >= A.FIRST_ASSGN_DT
AND CAL.CALENDAR_DATE < A.FIRST_CONTACT_DT
--BETWEEN A.FIRST_ASSGN_DT AND A.FIRST_CONTACT_DT
AND CAL.GRH_HOLIDAY_IND = 'Y'
) AS Business_Days
Looks like below piece needs editing...
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END)
Adapted from my answer here:
Get the number of days between the Mondays of both weeks (using TRUNC( datevalue, 'IW' ) as an NLS_LANGUAGE independent method of finding the Monday of the week) then add the day of the week (Monday = 1, Tuesday = 2, etc., to a maximum of 5 to ignore weekends) for the end date and subtract the day of the week for the start date. Like this:
SELECT ( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( end_date - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( start_date - TRUNC( start_date, 'IW' ) + 1, 5 )
AS WeekDaysDifference
FROM your_table
With RANGE_TEMP as (
SELECT
STARTPERIOD start_date,
ENDPERIOD end_date
FROM
TABLE_DATA -- YOUR TABLE WITH ALL DATA DATE
), DATE_TEMP AS (
SELECT
(start_date + LEVEL) DATE_ALL
FROM
RANGE_TEMP
CONNECT BY LEVEL <= (end_date - start_date)
), WORK_TMP as (
SELECT
COUNT(DATE_ALL) WORK_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('1','7')
), BUSINESS_TMP as (
SELECT
COUNT(DATE_ALL) BUSINESS_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('1','7')
)
SELECT
L.WORK_DATE,
H.BUSINESS_DATE
FROM
BUSINESS_TMP H,
WORK_TMP L
;

Missing Right Parenthesis error while executing this query in oracle

I am executing this query in oracle and it is giving me an error of missing right parenthesis at line 33. Is there anyone who can help me resolve this issue. Thank you
Here is my query
WITH t AS (
SELECT RM_LIVE.EMPLOYEE.EMPNO,
RM_LIVE.EMPNAME.FIRSTNAME,
RM_LIVE.EMPNAME.LASTNAME,
RM_LIVE.CRWBASE.BASE,
RM_LIVE.CRWCAT.crwcat,
RM_LIVE.CRWSPECFUNC.IDCRWSPECFUNC
FROM RM_LIVE.EMPBASE,
RM_LIVE.EMPLOYEE,
RM_LIVE.CRWBASE,
RM_LIVE.EMPNAME,
RM_LIVE.CRWSPECFUNC,
RM_LIVE.EMPSPECFUNC,
RM_LIVE.EMPQUALCAT,
RM_LIVE.CRWCAT
where RM_LIVE.EMPBASE.IDEMPNO = RM_LIVE.EMPLOYEE.IDEMPNO
AND RM_LIVE.EMPBASE.IDCRWBASE = RM_LIVE.CRWBASE.IDCRWBASE
AND RM_LIVE.EMPLOYEE.IDEMPNO = RM_LIVE.EMPNAME.IDEMPNO
AND RM_LIVE.EMPSPECFUNC.IDCRWSPECFUNC =RM_LIVE.CRWSPECFUNC.IDCRWSPECFUNC
AND RM_LIVE.EMPSPECFUNC.IDEMPNO =RM_LIVE.EMPLOYEE.IDEMPNO
AND RM_LIVE.EMPQUALCAT.IDEMPNO=RM_LIVE.EMPLOYEE.IDEMPNO
AND RM_LIVE.CRWCAT.IDCRWCAT = RM_LIVE.EMPQUALCAT.IDCRWCAT
AND RM_LIVE.CRWCAT.crwcat IN ('CP','FO','CM','MC')
AND RM_LIVE.CRWBASE.BASE <> 'XYZ'
AND RM_LIVE.CRWSPECFUNC.IDCRWSPECFUNC IN
('921','2' ,'1','301','17','4','3','7','302' ,'861','31',
'723','30','722 ','29 ','721','16','601','581')
AND RM_LIVE.EMPBASE.STARTDATE <= SYSDATE
AND RM_LIVE.EMPBASE.ENDDATE >= SYSDATE
AND RM_LIVE.EMPSPECFUNC.STARTDATE <= SYSDATE
AND RM_LIVE.EMPSPECFUNC.ENDDATE >= SYSDATE
AND RM_LIVE.EMPNAME.FROMDATE <=SYSDATE
AND RM_LIVE.EMPQUALCAT.STARTDATE <= SYSDATE
AND RM_LIVE.EMPQUALCAT.ENDDATE >= SYSDATE AS ta (EMPNO,EMPFIRSTNAME,EMPLASTNAME, Base, CAT, code)
)
SELECT DISTINCT
t.EMPNO,
t.EMPFIRSTNAME,
t.EMPLASTNAME,
t.Base,
t.CAT,
(ABS(oa.val1) * NVL(NULLIF((ABS(oa.val2) * ABS(oa.val3)),0),1) * ABS(oa.val4) * ABS(oa.val5) * ABS(oa.val6) * ABS(oa.val7) * ABS(oa.val8) * ABS(oa.val9)) AS "FTE VALUE"
FROM t
OUTER APPLY (SELECT MAX(CASE WHEN t2.code IN (1,2,4) THEN 0.70 ELSE -1 END) AS val1,
MAX(CASE WHEN t2.code IN (1,2) THEN 0 ELSE -1 END) AS val2,
MAX(CASE WHEN t2.code IN (4) THEN 1.29 ELSE -1 END) AS val3,
MAX(CASE WHEN t2.code IN ( 861 ) THEN 0.80 ELSE -1 END) AS val4
MAX(CASE WHEN t2.code IN (921,301,30,722,601,581) THEN 0.50 ELSE -1 END) AS val5
MAX(CASE WHEN t2.code IN (17,302,16) THEN 0.85 ELSE -1 END) AS val6
MAX(CASE WHEN t2.code IN (29,721) THEN 0.25 ELSE -1 END) AS val7
MAX(CASE WHEN t2.code IN (31,723) THEN 0.75 ELSE -1 END) AS val8
MAX(CASE WHEN t2.code IN (3,7) THEN 0.90 ELSE -1 END) AS val9
FROM t AS t2 WHERE t2.EMPNO = t.EMPNO) oa
The last line of your sub-query factoring (WITH ... AS ( ... )) clause is:
AND RM_LIVE.EMPQUALCAT.ENDDATE >= SYSDATE AS ta (EMPNO,EMPFIRSTNAME,EMPLASTNAME, Base, CAT, code)
The AS ta (...) is invalid syntax.
If you want to name the columns then you need to delete that part and change the first line to:
WITH t (EMPNO,EMPFIRSTNAME,EMPLASTNAME, Base, CAT, code) AS (
However, that is syntax introduced in Oracle 11g and won't work in Oracle 10g - if you want to support that version (and it seems you do since you've tagged it) then just explicitly alias each column:
WITH t AS (
SELECT RM_LIVE.EMPLOYEE.EMPNO,
RM_LIVE.EMPNAME.FIRSTNAME AS EMPFIRSTNAME,
RM_LIVE.EMPNAME.LASTNAME AS EMPLASTNAME,
RM_LIVE.CRWBASE.BASE,
RM_LIVE.CRWCAT.crwcat AS CAT,
RM_LIVE.CRWSPECFUNC.IDCRWSPECFUNC AS CODE

Resources