Combining grouping set queries into one - sql

I have 5 different queries that work fine but basically do the same thing. The difference is that they group by different periods.
My question is can these 5 queries be combined into 1 query perhaps a procedure, Where I pass in a D (Day), W (Week) M (Month), Q (quarter) or Y (year).
Below are the queries and some test data. Thanks in advance to all who respond.
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY HH24:MI:SS.FF';
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
CREATE TABLE customers
(CUSTOMER_ID, FIRST_NAME, LAST_NAME) AS
SELECT 1, 'Faith', 'Mazzarone' FROM DUAL UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM DUAL UNION ALL
SELECT 3, 'Micheal', 'Palmice' FROM DUAL UNION ALL
SELECT 4, 'Jerry', 'Torchiano' FROM DUAL;
CREATE TABLE items
(PRODUCT_ID, PRODUCT_NAME, PRICE) AS
SELECT 100, 'Black Shoes', 79.99 FROM DUAL UNION ALL
SELECT 101, 'Brown Pants', 111.99 FROM DUAL UNION ALL
SELECT 102, 'White Shirt', 10.99 FROM DUAL;
CREATE TABLE purchases
(CUSTOMER_ID, PRODUCT_ID, QUANTITY, PURCHASE_DATE) AS
SELECT 1, 101, 3, TIMESTAMP'2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 1, 100, 1, TIMESTAMP '2022-10-12 19:04:18' FROM DUAL UNION ALL
SELECT 2, 101,1, TIMESTAMP '2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 2, 101, 3, TIMESTAMP '2022-10-17 19:34:58' FROM DUAL UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-06 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-26 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-21 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-27 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-22 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 15 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-11 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-17 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-12 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 5;
/* purchases per day for each customer */
SELECT TO_CHAR (p.purchase_date, 'YYYY-MM-DD') AS year_mon_day
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'YYYY-MM-DD'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'YYYY-MM-DD'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'YYYY-MM-DD'), p.customer_id;
/* purchases per week for each customer */
SELECT TO_CHAR (p.purchase_date, 'IYYY"W"IW') AS year_week
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'IYYY"W"IW'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'IYYY"W"IW'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'IYYY"W"IW'), p.customer_id;
/* purchases per month for each customer */
SELECT TO_CHAR (p.purchase_date, 'YYYY"M"MM') AS year_month
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'YYYY"M"MM'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'YYYY"M"MM'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'YYYY"M"MM'), p.customer_id;
/* purchases per quarter for each customer */
SELECT TO_CHAR (p.purchase_date, 'YYYY"Q"Q') AS year_quarter
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'YYYY"Q"Q'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'YYYY"Q"Q'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'YYYY"Q"Q'), p.customer_id;
/* purchases per year for each customer */
SELECT TO_CHAR (p.purchase_date, 'YYYY"Y"') AS year
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'YYYY"Y"'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'YYYY"Y"'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'YYYY"Y"'), p.customer_id;

If you want it as a procedure then:
CREATE PROCEDURE get_customer_data(
i_period IN VARCHAR2,
o_cursor OUT SYS_REFCURSOR
)
AS
v_format VARCHAR2(10);
BEGIN
v_format := CASE UPPER(i_period)
WHEN 'D' THEN 'YYYY-MM-DD'
WHEN 'W' THEN 'IYYY"W"IW'
WHEN 'M' THEN 'YYYY"M"MM'
WHEN 'Q' THEN 'YYYY"Q"Q'
WHEN 'Y' THEN 'YYYY"Y"'
ELSE 'YYYY-MM-DD'
END;
OPEN o_cursor FOR
SELECT TO_CHAR (p.purchase_date, v_format) AS period
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY
GROUPING SETS(
( TO_CHAR (p.purchase_date, v_format), p.customer_id, c.first_name, c.last_name )
, TO_CHAR (p.purchase_date, v_format)
, ()
)
ORDER BY TO_CHAR (p.purchase_date, v_format), p.customer_id;
END;
/
fiddle

You can certainly do this in a procedure or function, either REF CURSOR return like MT0's answer, (if you can handle how to interface with that) or returning a nested table object, etc. But involving functions does add more complication and may not be ideal for simple needs. It may be simpler just to write a view with a set of UNION ALLs and use a literal to select the one you want.
CREATE OR REPLACE myview AS
SELECT 'D' period,
[column_list]
FROM [table list with joins]
GROUP BY TO_CHAR(purchase_date,'YYYY-MM-DD'),customer_id,first_name,last_name
UNION ALL
SELECT 'M' period,
[column_list]
FROM [table list with joins]
GROUP BY TO_CHAR(purchase_date,'YYYY-MM'),customer_id,first_name,last_name
UNION ALL
SELECT 'Y' period,
[column_list]
FROM [table list with joins]
GROUP BY TO_CHAR(purchase_date,'YYYY'),customer_id,first_name,last_name
[etc...]
Then query it:
SELECT * FROM myview WHERE period = 'D'
Oracle should skip the work behind the other query blocks in the UNION ALL that don't match the literal period requested in your predicate, so there's no performance penalty.

Related

Using Pivot in Oracle SQL to dynamically show one or two columns in case multiple records are present

I am working in Oracle Fusion HCM and would like to create a query which pulls an employee's base data such as name, location, etc. We also want to include the managers.
Our manager structure is as such so that there's 1 line manager and 1 to n (realistically not more than 3) matrix managers, named 'REVIEWER'.
I have a working code that fetches the data, but it gives issues when there's not exactly 2 managers. When there's 1, it shows the same name twice and if there's 3, there is one that is not shown.
Can anyone help me out on how to fetch the correct manager names without using the MIN/MAX aggregrates? My query is already fetching the correct data, but my pivot clause is not working correctly.
Select DISTINCT *
from
(
SELECT DISTINCT
emplName.DISPLAY_NAME Worker_Name,
INITCAP(loc.LOCATION_NAME) Location_Name,
gra.NAME Grade_Name,
hou.NAME Department_Name,
ass.MANAGER_TYPE Manager_Type,
mgr.DISPLAY_NAME Manager_Name,
REPLACE(ctr.CONTRACT_END_DATE,'4712-12-31') Contract_End_Date,
aa.ASSIGNMENT_NUMBER
FROM
PER_ALL_ASSIGNMENTS_M aa,
PER_ASSIGNMENT_SUPERVISORS_F ass,
PER_PERSON_NAMES_F emplName,
PER_ALL_PEOPLE_F empl,
PER_PERSON_NAMES_F mgr,
HR_ORGANIZATION_UNITS hou,
HR_LOCATIONS_ALL_F_VL loc,
PER_GRADES_F_TL gra,
PER_CONTRACTS_F ctr
WHERE
aa.ASSIGNMENT_ID (+) = ass.ASSIGNMENT_ID
AND emplName.PERSON_ID = ass.PERSON_ID
AND ass.MANAGER_ID = mgr.PERSON_ID
AND empl.PERSON_ID = ass.PERSON_ID
AND hou.ORGANIZATION_ID = aa.ORGANIZATION_ID
AND loc.LOCATION_ID = aa.LOCATION_ID
AND gra.GRADE_ID = aa.GRADE_ID
AND ctr.CONTRACT_ID = aa.CONTRACT_ID
AND aa.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
AND to_char(ass.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
AND to_char(aa.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
AND to_char(ctr.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
AND gra.SOURCE_LANG = 'US'
AND gra.NAME in (:p_grade)
AND hou.NAME in (:p_department)
AND INITCAP(loc.LOCATION_NAME) in (:p_location)
AND (ctr.CONTRACT_END_DATE <= (:p_contractenddate)
OR (:p_contractenddate) is null)
) S
Pivot
(
MAX(Manager_Name) Manager1,
MIN(Manager_Name) Manager2
for manager_type in
('LINE_MANAGER' as Line_Manager,
'REVIEWER' as Reviewer
))
Piv
The data regarding managers is recorded in PER_ASSIGNMENT_SUPERVISORS_F ass as follows:
ASSIGNMENT_ID
MANAGER_TYPE
MANAGER_ID
0129312
LINE_MANAGER
2343943
0129312
REVIEWER
456756
0129312
REVIEWER
456334
0129312
REVIEWER
234324
1232232
LINE_MANAGER
232242
1232232
REVIEWER
122312
Edit: Table formatting was broken
Use:
Select *
from (
SELECT ass.assignment_id,
ass.person_id,
ass.MANAGER_TYPE Manager_Type,
mgr.DISPLAY_NAME Manager_Name,
ROW_NUMBER() OVER (
PARTITION BY ass.assignment_id, ass.person_id, ass.manager_type
ORDER BY mgr.display_name
) AS rn
FROM PER_ASSIGNMENT_SUPERVISORS_F ass
INNER JOIN PER_PERSON_NAMES_F mgr
ON (ass.MANAGER_ID = mgr.PERSON_ID)
WHERE ass.EFFECTIVE_END_DATE = DATE '4712-12-31'
)
PIVOT (
MAX(Manager_Name)
for (manager_type, rn) in (
('LINE_MANAGER', 1) as Line_Manager,
('REVIEWER', 1) as Reviewer1,
('REVIEWER', 2) as Reviewer2,
('REVIEWER', 3) as Reviewer3
)
)
Then join the rest of the tables to that pivoted query (rather than trying to join first and then pivot).
Which, for the (minimal) sample data:
CREATE TABLE PER_ASSIGNMENT_SUPERVISORS_F (assignment_id, person_id, manager_id, manager_type, effective_end_date) AS
SELECT 1, 1, 2, 'LINE_MANAGER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 1, 1, 3, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 1, 1, 4, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 1, 1, 5, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 2, 2, 3, 'LINE_MANAGER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 2, 2, 4, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 2, 2, 5, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 3, 3, 4, 'LINE_MANAGER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 3, 3, 5, 'REVIEWER', DATE '4712-12-31' FROM DUAL UNION ALL
SELECT 4, 4, 5, 'LINE_MANAGER', DATE '4712-12-31' FROM DUAL;
CREATE TABLE PER_PERSON_NAMES_F (person_id, display_name) AS
SELECT 1, 'Alice' FROM DUAL UNION ALL
SELECT 2, 'Beryl' FROM DUAL UNION ALL
SELECT 3, 'Carol' FROM DUAL UNION ALL
SELECT 4, 'Debra' FROM DUAL UNION ALL
SELECT 5, 'Emily' FROM DUAL;
Outputs:
ASSIGNMENT_ID
PERSON_ID
LINE_MANAGER
REVIEWER1
REVIEWER2
REVIEWER3
1
1
Beryl
Carol
Debra
Emily
2
2
Carol
Debra
Emily
null
3
3
Debra
Emily
null
null
4
4
Emily
null
null
null
fiddle
Rewrote the query based on MT0s answer. For anyone interested in the end-result:
Select *
from
(
SELECT
emplName.DISPLAY_NAME Worker_Name,
INITCAP(loc.LOCATION_NAME) Location_Name,
gra.NAME Grade_Name,
hou.NAME Department_Name,
ass.MANAGER_TYPE Manager_Type,
mgr.DISPLAY_NAME Manager_Name,
ROW_NUMBER() OVER (
PARTITION BY aa.ASSIGNMENT_NUMBER, ass.assignment_id, ass.person_id, gra.NAME, hou.NAME, ass.manager_type
ORDER BY mgr.display_name
) AS rn,
REPLACE(ctr.CONTRACT_END_DATE,'4712-12-31') Contract_End_Date,
aa.ASSIGNMENT_NUMBER
FROM
PER_ALL_ASSIGNMENTS_F aa
LEFT JOIN PER_ASSIGNMENT_SUPERVISORS_F ass
ON (aa.ASSIGNMENT_ID = ass.ASSIGNMENT_ID
AND to_char(ass.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712')
LEFT JOIN PER_PERSON_NAMES_F_V emplName
ON (ass.PERSON_ID = emplName.PERSON_ID
AND to_char(emplName.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
AND emplName.NAME_TYPE = 'GLOBAL')
LEFT JOIN PER_ALL_PEOPLE_F empl
ON (empl.PERSON_ID = ass.PERSON_ID
AND to_char(empl.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712')
LEFT JOIN PER_PERSON_NAMES_F mgr
ON (mgr.PERSON_ID = ass.MANAGER_ID
AND to_char(mgr.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
AND mgr.NAME_TYPE = 'GLOBAL')
LEFT JOIN HR_ORGANIZATION_UNITS hou
ON (hou.ORGANIZATION_ID = aa.ORGANIZATION_ID
AND to_char(hou.DATE_TO, 'DD/MM/YYYY') = '31/12/4712')
LEFT JOIN HR_LOCATIONS_ALL_F_VL loc
ON (loc.LOCATION_ID = aa.LOCATION_ID
AND to_char(loc.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712')
LEFT JOIN PER_GRADES_F_TL gra
ON (gra.GRADE_ID = aa.GRADE_ID
AND gra.LANGUAGE = 'US'
AND to_char(gra.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712')
LEFT JOIN PER_CONTRACTS_F ctr
ON (ctr.CONTRACT_ID = aa.CONTRACT_ID
AND to_char(ctr.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712')
WHERE 1=1
AND aa.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
AND to_char(aa.EFFECTIVE_END_DATE, 'DD/MM/YYYY') = '31/12/4712'
-- PARAMETERS
AND gra.NAME in (:p_grade)
AND hou.NAME in (:p_department)
AND INITCAP(loc.LOCATION_NAME) in (:p_location)
AND (ctr.CONTRACT_END_DATE <= (:p_contractenddate)
OR (:p_contractenddate) is null)
) S
Pivot
(
MAX(Manager_Name)
for (manager_type, rn) in (
('LINE_MANAGER', 1) as Line_Manager,
('REVIEWER', 1) as Reviewer1,
('REVIEWER', 2) as Reviewer2,
('REVIEWER', 3) as Reviewer3
))
Piv

SQL query to find ids in the same table but different timestamp events (cohorts)

I need to write a query that gives me the count with the following logic. The example below shows that ACCOUNT_ID 123 signup in 2020-02-21 so M0 is 1 and then the same ACCOUNT_ID had an event in the consecutive month so M1 is 1.
M0 is a the signup date
M1 is signup date + 1 month
M2 is signup date + 2 consecutive months
M3 is signup date + 3 consecutive months
WITH M_O AS (
SELECT
parsed_data."ACCOUNT_ID" AS "parsed_data.account_id",
MIN(TO_CHAR(TO_DATE(parsed_data."TIMESTAMP"::timestamp_ntz ), 'YYYY-MM-DD')) AS "SIGNUP",
COUNT(DISTINCT (parsed_data."ACCOUNT_ID") ) AS "COUNT_USERS_O"
FROM "PUBLIC"."PARSED_DATA"
AS parsed_data
WHERE (parsed_data."ACCOUNT_ID") IS NOT NULL
AND (((parsed_data."EVENT") = 'Started'))
AND (
((TO_CHAR(TO_DATE(parsed_data."TIMESTAMP"::timestamp_ntz ), 'YYYY-MM-DD')) >= '2020-02-21')
AND ((parsed_data."TIMESTAMP"::timestamp_ntz ) < CURRENT_DATE())
)
GROUP BY 1),
M_1 AS (
SELECT
parsed_data."ACCOUNT_ID" AS "parsed_data.account_id",
TO_CHAR(TO_DATE(parsed_data."TIMESTAMP"::timestamp_ntz ), 'YYYY-MM-DD') AS "parsed_data.timestamp_date",
COUNT(DISTINCT (parsed_data."ACCOUNT_ID") ) AS "COUNT_USERS_1"
FROM "PUBLIC"."PARSED_DATA"
AS parsed_data INNER JOIN M_O ON parsed_data.account_id = M_O."parsed_data.account_id"
WHERE
(parsed_data."ACCOUNT_ID") IS NOT NULL
AND (((parsed_data."EVENT") = 'Started'))
AND (
(TO_CHAR(TO_DATE(parsed_data."TIMESTAMP"::timestamp_ntz ), 'YYYY-MM-DD')) >= DATEADD('MONTH', 1, SIGNUP)
AND ((parsed_data."TIMESTAMP"::timestamp_ntz ) < CURRENT_DATE())
)
GROUP BY 1,2
)
It looks like you want to create cohorts? As in "establish the creation date for each id, and then look how they changed their behavior every month thereafter".
This code should work:
with events as (
select 1 id, '2020-01-01'::date e_date
union all select 1, '2020-02-03'
union all select 2, '2020-03-01'
union all select 2, '2020-05-08'
union all select 3, '2020-08-01'
union all select 3, '2020-09-02'
union all select 3, '2020-09-22'
union all select 3, '2020-09-30'
union all select 3, '2020-10-10'
),
first_per_id as (
select id, min(e_date) first_date
from events
group by id
)
select a.id
, count_if(e_date>=dateadd(month, 0, first_date) and e_date<dateadd(month, 1, first_date)) m0
, count_if(e_date>=dateadd(month, 1, first_date) and e_date<dateadd(month, 2, first_date)) m1
, count_if(e_date>=dateadd(month, 2, first_date) and e_date<dateadd(month, 3, first_date)) m2
from events a
join first_per_id b
where a.id=b.id
group by 1

How to select next value

The task is to see the date of payment of the loan. If it falls on a date where there is no such number, it does not show data but should show the first date from the next month
Sql work good if i enter date 15.01.2019
But if i can enter date 31.01.2019 i have problem .
I can not see correct result sql request.
With days as (
Select rownum As Day from All_Objects where Rownum<=31
),
a as (Select 'WHWWHHWWWWWHHWWWWWHHWWWWWHHWWWW' as hl ,1 as Mnth,2019 as Yr from Dual
Union All
Select 'WHHWWWWWHHWWWWWHHWWWWWHHWWWW' as hl ,2 as Mnth,2019 as Yr from Dual
Union All
Select 'WHHWWWWHHHWWWWWHHWWHHHHHHHWWWHH' as hl ,3 as Mnth,2019 as Yr from Dual
Union All
Select 'WWWWWHHWWWWWHHWWWWWHHWWWWWHHWW' as hl ,4 as Mnth,2019 as Yr from Dual
Union All
Select 'WWWHHWWWHWHHWWWWWHHWWWWWHHWHWWW' as hl ,5 as Mnth,2019 as Yr from Dual
Union All
Select 'HHWWHHWHHWWWWWHHHWWWWHHWWHWWHH' as hl ,6 as Mnth,2019 as Yr from Dual
Union All
Select 'WWWWWHHWWWWWHHWWWWWHHWWWWWHHWWW' as hl ,7 as Mnth,2019 as Yr from Dual
)
,
Alll as
(Select TO_Date(Yr|| substr('0'||Mnth,-2,2)||substr('0'||Day,-2,2),'YYYYMMDD') as Dt,a.Yr,a.Mnth,Days.Day,substr(a.Hl,Days.Day,1) as Daytype from Days,a Where Days.Day<=Length(a.Hl)
),
Taksit as
(
Select To_Date('31.01.2019') as TDate, 1000 as Amount ,3 as Tcount from Dual
),
PD as (
Select
A.Dt,A.DayType , Case when A.DayType='H' then Min(W.Dt) else A.Dt end As PayableDate
From Alll A inner Join Alll W on W.DT>=A.DT and W.DayType='W'
Group by A.Dt, A.Daytype
Order by 1
),
PreResult as
(
Select PD.PayableDate,Amount,TCount,Max(PD.PayableDate) over (Partition by 'Contract') as MPD
From PD inner join Taksit T on PD.DT between add_months(T.TDate,1) and Add_Months(T.TDate,TCount)
and TO_Char(PD.DT,'DD')=TO_Char(T.TDate,'DD')
)
Select
PayableDate, Case when PayableDate=MPD then Amount-(Round(Amount/TCount,2)*(TCount-1)) else Round(Amount/TCount,2) end PayAmount
from PreResult
You have used TO_CHAR(PD.DT, 'DD') = TO_CHAR(T.TDATE, 'DD') but I don't think that Feb month has any date which will match with it.
Ideally, you should use add_month function as following in PRERESULT (I believe you need only 3 months data)
PRERESULT AS (
SELECT
PD.PAYABLEDATE,
AMOUNT,
TCOUNT,
MAX(PD.PAYABLEDATE) OVER(
PARTITION BY 'Contract'
) AS MPD
FROM
PD
INNER JOIN TAKSIT T ON PD.DT BETWEEN ADD_MONTHS(T.TDATE, 1) AND ADD_MONTHS(T.TDATE, TCOUNT)
AND PD.DT IN (ADD_MONTHS(T.TDATE, 1), ADD_MONTHS(T.TDATE, 2), ADD_MONTHS(T.TDATE, 3))
-- AND TO_CHAR(PD.DT, 'DD') = TO_CHAR(T.TDATE, 'DD')
)
It is giving 3 dates with 31.01.2019 and also it is working as expected in the case of 15.01.2019 also.
I think you should check if it is giving an expected result with 31.01.2019 as you have not mentioned the expected result. see this db<>fiddle demo
Cheers!!

Nesting a WITH statement into an existing select and passing dates into it

I have found an excellent solution on here that will allow me to compare number working days between 2 dates excluding holidays. However what I cannot figure out is how to pass t1.ATTRIBUTE_DATE1 into the start_date and t1.CHECK_DATE into the end_date.
I have tried and put the With into the select it says too many arguments.
SELECT DISTINCT t1.invoice_date
, t1.creation_date
, t1.INVOICE_RECEIVED_DATE
, (t1.check_Date - t1.INVOICE_RECEIVED_DATE)
, ((t1.check_Date - t2.REPORT_SUBMIT_DATE)+3)
, ((t1.check_Date - t1.invoice_date)+3)
, t1.ATTRIBUTE_DATE1
, t1.invoice_num
, t1.payment_number
, t1.check_date
, t1.vendor_type_lookup_code
, t1.source
, t1.PAY_GROUP_LOOKUP_CODE
, t1.Batch_Name
, t1.Description
, t1.Vendor_Name
, t1.Amount_Paid
, t1.Invoice_ID
, t2.REPORT_SUBMIT_DATE
, t2.FINAL_APPROVAL_DATE
FROM ( SELECT DISTINCT APA.INVOICE_ID
, APA.INVOICE_DATE
, APA.CREATION_DATE
, APA.ATTRIBUTE_DATE1
, APA.INVOICE_NUM
, ACA.CHECK_NUMBER as PAYMENT_NUMBER
, ACA.CHECK_DATE
, APA.INVOICE_RECEIVED_DATE
, APA.CREATION_DATE
, SUP.VENDOR_TYPE_LOOKUP_CODE
, APA.SOURCE
, APA.PAY_GROUP_LOOKUP_CODE
, BAT.BATCH_NAME
, APA.DESCRIPTION
, APA.AMOUNT_PAID
, ACA.VENDOR_NAME
FROM AP_INVOICES_ALL APA
LEFT JOIN AP_INVOICE_LINES_ALL AIL
ON APA.INVOICE_ID= AIL.INVOICE_ID
LEFT JOIN AP_INVOICE_DISTRIBUTIONS_ALL AID
ON APA.INVOICE_ID = AID.INVOICE_ID AND AIL.LINE_NUMBER =
AID.INVOICE_LINE_NUMBER
JOIN AP_INVOICE_PAYMENTS_ALL AIP
ON APA.INVOICE_ID = AIP.INVOICE_ID
JOIN AP_CHECKS_ALL ACA
ON AIP.CHECK_ID = ACA.CHECK_ID
LEFT JOIN AP_BATCHES_ALL BAT
ON APA.BATCH_ID = BAT.BATCH_ID
LEFT JOIN POZ_SUPPLIERS_V SUP
ON APA.PARTY_ID = SUP.PARTY_ID
WHERE AID.LINE_TYPE_LOOKUP_CODE = 'ITEM'
AND APA.SOURCE NOT IN ('INVOICE GATEWAY' , 'B2B XML INVOICE')
AND ACA.STATUS_LOOKUP_CODE<> 'VOIDED'
AND APA.INVOICE_TYPE_LOOKUP_CODE NOT IN ('CREDIT' , 'PREPAYMENT')
AND ACA.CHECK_DATE BETWEEN :Start_Date AND :End_Date
AND BAT.BATCH_NAME IS NOT NULL) t1
LEFT JOIN (Select EXPENSE_REPORT_NUM
,REPORT_SUBMIT_DATE
,FINAL_APPROVAL_DATE
,EXPENSE_REPORT_TOTAL
FROM EXM_EXPENSE_REPORTS)t2
ON t1.INVOICE_NUM =t2.EXPENSE_REPORT_NUM
ORDER BY t1.CHECK_DATE ASC
With statment that I would like to use to give me days between t1.ATTRIBUTE_DATE1(can be blank) and t1.check_date
(WITH test_data AS
(
SELECT TO_DATE('01/01/2019', 'DD/MM/YYYY') AS start_date,-----t1.ATTRIBUTE_DATE1
TO_DATE('27/08/2019', 'DD/MM/YYYY') AS end_date------t1.check_date
FROM dual
),
all_dates AS
(
SELECT td.start_date, td.end_date, td.start_date + LEVEL-1 as week_day
FROM test_data td
CONNECT BY td.start_date + LEVEL-1 <= td.end_date)
SELECT TO_CHAR(week_day, 'MON'), COUNT(*)
FROM all_dates
WHERE to_char(week_day, 'FMDAY', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN
('SATURDAY','SUNDAY')
AND to_char(week_day, 'DD/MM/YYYY') NOT IN ( '01/01/2019', '25/12/2019',
'26/12/2019', '26/08/2019', '19/04/2019', '22/04/2019', '06/05/2019',
'27/05/2019')
GROUP BY TO_CHAR(week_day, 'MON')
)
Want to replace the the above with statement with this
(WITH test_data AS
(
SELECT TO_DATE(t1.ATTRIBUTE_DATE1, 'DD/MM/YYYY') AS start_date,
TO_DATE(t1.check_date, 'DD/MM/YYYY') AS end_date
FROM dual
),
all_dates AS
(
SELECT td.start_date, td.end_date, td.start_date + LEVEL-1 as week_day
FROM test_data td
CONNECT BY td.start_date + LEVEL-1 <= td.end_date)
SELECT TO_CHAR(week_day, 'MON'), COUNT(*)
FROM all_dates
WHERE to_char(week_day, 'FMDAY', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('SATURDAY','SUNDAY')
AND to_char(week_day, 'DD/MM/YYYY') NOT IN ( '01/01/2019', '25/12/2019', '26/12/2019', '26/08/2019', '19/04/2019', '22/04/2019', '06/05/2019', '27/05/2019')
GROUP BY TO_CHAR(week_day, 'MON')
)
I don't have a clue what your real issue is, but if you want to count working days between 2 dates you can use something like:
with test_data AS
(SELECT TO_DATE('01/01/2019', 'DD/MM/YYYY') AS start_date, -- t1.ATTRIBUTE_DATE1
TO_DATE('27/08/2019', 'DD/MM/YYYY') AS end_date -- t1.check_date
FROM dual
union all
SELECT TO_DATE('01/04/2019', 'DD/MM/YYYY') AS start_date, -- t1.ATTRIBUTE_DATE1
TO_DATE('10/08/2019', 'DD/MM/YYYY') AS end_date -- t1.check_date
FROM dual)
, dates as (
select trunc(sysdate,'YYYY') + level -1 bd from dual connect by level <= 250)
, evaluate_free as (
select bd
, case when to_char(bd, 'FMDAY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SATURDAY','SUNDAY')
or to_char(bd, 'DD/MM/YYYY') IN ( '01/01/2019', '25/12/2019', '26/12/2019', '26/08/2019', '19/04/2019', '22/04/2019', '06/05/2019', '27/05/2019')
then 0 else 1 end free_work
from dates)
select start_date,end_date, sum(free_work) working_days
from evaluate_free, test_data
where bd between start_date and end_date
group by start_date,end_date

To list 12 months in a query Oracle SQL

I need your help, please.
I have a query to list a PDAs numbers and I need to sort this in twelve months. My query:
SELECT TO_CHAR(primeirodia_mes,'mm/yyyy') competencia,
'Suporte' tipo,
(SELECT COUNT(tipo)
FROM v_pdas_suporte_se
WHERE TO_CHAR(primeirodia_mes,'mm/yyyy') = TO_CHAR(v_pdas_suporte_se.dt,'mm/yyyy')
AND tipo IN ( 'S','E')
) quantidade
FROM
(SELECT To_Date( '01/'
||LPad(ID,2,0)
||'/'
||TO_CHAR(SYSDATE,'yyyy') ,'dd/mm/yyyy') primeirodia_mes
FROM
(SELECT LEVEL AS ID FROM DUAL CONNECT BY LEVEL <= 12 )
) contador
I need to list , for example, Oct/2014 to Oct/2015.
I assume you are looking for this:
WITH t AS
(SELECT ADD_MONTHS(TRUNC(SYSDATE, 'MM'), - LEVEL+1) AS primeirodia_mes
FROM dual CONNECT BY LEVEL <= 13)
SELECT
primeirodia_mes AS competencia,
'Suporte' tipo,
COUNT(tipo)
FROM v_pdas_suporte_se
RIGHT OUTER JOIN t ON primeirodia_mes = TRUNC(dt, 'MM')
GROUP BY primeirodia_mes;