SQL - Dividing by Sum by Group - sql

I am just learning SQL and have run into a problem creating a custom report. I am working with school attendance data. I want to create a report that gives membership days and number of days for each absence type.
I have successfully created a report for these separately.
Membership Days (calculated by counting days school was in session between the student's entry date and the current date. Membership days does not exist as a field on its own)
SELECT sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp THEN cd.INSESSION ELSE 0 END), s.LASTFIRST
FROM CALENDAR_DAY cd,STUDENTS s
WHERE cd.SCHOOLID = 405
GROUP BY s.LASTFIRST
Count per absence type
SELECT s.STUDENT_NUMBER, s.LASTFIRST,SUM(CASE WHEN a.ATTENDANCE_CODEID = 2 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 4 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 3 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 51 THEN 1 ELSE 0 END)
FROM ATTENDANCE a
INNER join STUDENTS s
ON a.STUDENTID = s.ID
WHERE a.att_date between '%param1%' and '%param2%'
GROUP BY s.STUDENT_NUMBER, s.LASTFIRST
The problem is that if I try to put these in the same report, the membership days are multiplied by the number of times the student appears in the attendance table due to joining student and attendance. My thought on a solution was to then divide this line
sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp THEN cd.INSESSION ELSE 0 END)
by the number of times the student showed up in the attendance table to counteract the student information existing on every line. I can't figure out how to do that. I don't know much about these types of problems, so hopefully I've just gone off on the wrong track and there is an easy solution. Thanks.

Your problem is a common problem -- trying to summarize along two dimensions at the same time without using a subquery. You want to do this query with two aggregation subqueries. Something like this:
SELECT *
FROM (SELECT sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp
THEN cd.INSESSION
ELSE 0
END), s. STUDENT_NUMBER
FROM CALENDAR_DAY cd CROSS JOIN
STUDENTS s
WHERE cd.SCHOOLID = 405
GROUP BY s.STUDENT_NUMBER
) sc JOIN
(SELECT s.STUDENT_NUMBER, s.LASTFIRST,
SUM(CASE WHEN a.ATTENDANCE_CODEID = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 4 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 51 THEN 1 ELSE 0 END)
FROM ATTENDANCE a INNER join
STUDENTS s
ON a.STUDENTID = s.ID
WHERE a.att_date between '%param1%' and '%param2%'
GROUP BY s.STUDENT_NUMBER, s.LASTFIRST
) sa
on sc.STUDENT_NUMBER = sa.STUDENT_NUMBER;

Related

Oracle stored procedure with function ORA-08103

I have a stored procedure that has a function call, and I get the below errors after about 10 mins of it running:
[Error] Execution (1: 1):
ORA-08103: object no longer exists
ORA-06512: at "CRYSTAL_REPORTS.FT_PAYCOM_ASOF", line 141
ORA-06512: at "CRYSTAL_REPORTS.PROC_DASHQS_PRODUCTION", line 26
ORA-06512: at line 2
However the function does exist and works as expected. Stripping down the query returns results, so I am concerned that complexity may be the cause. I appreciate any help, below is the procedure:
select
to_char(dt.dt,'YYYYMM') yearmonth,
ud.user_id,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'DEPT') department,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'PDEPT') par_department,
sum(case when clm.event_desc = 'NEW-OPEN' then 1 else 0 end) new_claim,
sum(case when clm.event_desc in ('INITIAL-CLOSE', 'RECLOSE', 'VOID') then 1 else 0 end) close_claim,
sum(case when clm.event_desc ='REOPEN' then 1 else 0 end) reopen_claim,
sum(case when clm.event_desc ='TRANSFER-IN' then 1 else 0 end) trans_in_claim,
sum(case when clm.event_desc ='TRANSFER-OUT' then 1 else 0 end) trans_out_claim,
sum(case when res.event_desc ='NEW-OPEN' then 1 else 0 end) new_res,
sum(case when res.event_desc in ('INITIAL-CLOSE','RECLOSE','VOID') then 1 else 0 end) close_res,
sum(case when res.event_desc ='REOPEN' then 1 else 0 end) reopen_res,
sum(case when res.event_desc ='TRANSFER-IN' then 1 else 0 end) trans_in_res,
sum(case when res.event_desc ='TRANSFER-OUT' then 1 else 0 end) trans_out_res,
sum(clm_wh.pending) pending_claims,
sum(res_wh.pending) pending_reserves
from
(select "DATE" dt from CRYSTAL_REPORTS.MV_CALENDAR_MONTHDATE) dt
cross join
crystal_reports.user_director ud
left join
CRYSTAL_REPORTS.MV_PROD_CLM_EVENT clm on clm.USER_ID = ud.USER_ID and to_char(clm.event_date,'YYYYMM') = to_char(dt.dt,'YYYYMM')
left join
CRYSTAL_REPORTS.MV_PROD_RES_EVENT res on res.USER_ID = ud.USER_ID and to_char(res.event_date,'YYYYMM')=to_char(dt.dt,'YYYYMM')
left join
crystal_reports.TBL_CLAIM_PROD_WH clm_wh on clm_wh.ADJUSTER=ud.user_id and clm_wh.type='MONTH' and to_char(dt.dt,'YYYYMM')= clm_wh.datadate
left join
crystal_reports.TBL_FEAT_PROD_WH res_wh on res_wh.ADJUSTER=ud.user_id and res_wh.type='MONTH' and to_char(dt.dt,'YYYYMM')= res_wh.datadate
where
to_char(dt.dt,'YYYYMMDD') = 20210901
and ud.user_id not like '%TEST%'
group by
to_char(dt.dt,'YYYYMM'), ud.user_id,
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'DEPT'),
CRYSTAL_REPORTS.FT_PAYCOM_ASOF(ud.user_eecode, dt.dt, 'PDEPT')
The function goes through several IF statements, and ends up using:
SELECT upper(case when uc_dept.detaildesc is null and orig_dept.detaildesc is null then upper(pext_dept.detaildesc) else upper(nvl(uc_dept.detaildesc,orig_dept.detaildesc)) end)
INTO xout_val
FROM crystal_reports.API_PAYCOM_USER_EXTENDED pext
left join crystal_reports.API_PAYCOM_USER_CHANGES uc on pext.EECODE = uc.EECODE and changedesc='PAF: Department Change' and (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) <= asof
left join crystal_reports.api_paycom_category pext_dept on pext_dept.detailcode=pext.DEPARTMENT_CODE
left join crystal_reports.api_paycom_category uc_dept on uc_dept.DETAILCODE=uc.new_value
left join (select eecode, orig_value,rn
from
(
select eecode,old_value orig_value, row_number() over (partition by eecode order by (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) asc) rn
from
crystal_reports.API_PAYCOM_USER_CHANGES orig_val
where changedesc='PAF: Department Change'
)
) orig_val on pext.eecode=orig_val.eecode and orig_val.rn=1
left join crystal_reports.api_paycom_category orig_dept on orig_dept.detailcode=orig_val.orig_value
where
acct=pext.eecode
order by (to_date(substr(changetime, 1,10), 'yyyy-mm-dd')) desc
FETCH NEXT 1 ROWS ONLY
It is very hard to remote diagnose such an error but in my experience the most likely cause of this error is another process/user that has removed the object since the operation has begun.
Alternatively there are also some Oracle bugs related to this error and you might want to check your alert log and eventually ask Oracle for help using MOS.

SQL Year over Year Performance with Criteria

I am trying to return year over year results based on date criteria. There is additional information I would like to include in the query i.e. first date of activity and first date of activity with spot name like '%6%'. The current query I have is multiplying the correct amounts by 6 and I can't figure out how to solve. When I remove the first "where" clause I get the correct amounts. Any help would be appreciated.
Select
V.IGB_License,
DBA,
V.Sci_Games_Name,
convert(date,v2.Activity_date) as First6thMachineDate,
convert(date,V3.Activity_date) as GoLiveDate,
sum(case when (v.Activity_date between '1/23/2019' and DATEADD(YEAR,-2,getdate()-1)) then v.Funds_in else 0 end) as FundsIn2019,
sum(case when (v.Activity_date between '1/23/2020' and DATEADD(YEAR,-1,getdate()-1)) then v.Funds_in else 0 end) as FundsIn2020,
sum(case when (v.Activity_date between '1/23/2021' and getdate()) then v.Funds_in else 0 end) as FundsIn2021,
sum(case when (v.Activity_date between '1/23/2019' and DATEADD(YEAR,-2,getdate()-1)) then v.Net_funds else 0 end) as NetFunds2019,
sum(case when (v.Activity_date between '1/23/2020' and DATEADD(YEAR,-1,getdate()-1)) then v.Net_funds else 0 end) as NetFunds2020,
sum(case when (v.Activity_date between '1/23/2021' and getdate()) then v.Net_funds else 0 end) as NetFunds2021
From VGT_activity V
Left Join Locations on v.IGB_License = Locations.IGB_License
left join VGT_activity V2 on v.IGB_License = v2.IGB_License
Left join VGT_activity V3 on v.IGB_License = v3.IGB_License
Where v2.Activity_date = (
Select Min(V1.Activity_date)
From VGT_activity V1
Where v1.IGB_License = V2.IGB_License
and Spot_name like '%6%'
)
and v3.Activity_date = (
Select Min(V1.Activity_date)
From VGT_activity V1
Where v1.IGB_License = V3.IGB_License
)
group by V.IGB_License, dba, V.Sci_Games_Name, v2.Activity_date, v3.Activity_date
order by 4

Historical sum for current group

I have a SQL database with people, from which I want to see how much experience each person has in a specific department.
In the current query I have the following code:
SELECT [PERSON_ID]
,sum(case when [DEPARTMENT] = 'Marketing' then 1 else 0 end) as Exp_Marketing
,sum(case when [FUNCTION_DESC] = 'Finance' then 1 else 0 end) as Exp_Finance
FROM [xxxx].[xxxx].[xxxx]
GROUP BY [PERSON_ID]
Each person has one row for the months of service, so a person with 12 months of experience in Finance has a value of 12 in the Exp_Finance column.
The issue however is that the result now shows the outcome for all people. Also the one who already left the organization. How can I make sure the result only shows the historical information for the people currently part in the organization. In other words, the ones actually having a row with "2018M06" as value for the Date column.
You can use a having clause:
SELECT [PERSON_ID],
sum(case when [DEPARTMENT] = 'Marketing' then 1 else 0 end) as Exp_Marketing,
sum(case when [FUNCTION_DESC] = 'Finance' then 1 else 0 end) as Exp_Finance
FROM [xxxx].[xxxx].[xxxx]
GROUP BY [PERSON_ID]
HAVING MAX([DATE]) = '2018M06';
Your month format seems amenable to using MAX().
You should add an EXISTS within a WHERE clause so you only include people meeting your criteria.
SELECT [PERSON_ID]
,sum(case when [DEPARTMENT] = 'Marketing' then 1 else 0 end) as Exp_Marketing
,sum(case when [FUNCTION_DESC] = 'Finance' then 1 else 0 end) as Exp_Finance
FROM [xxxx].[xxxx].[xxxx] A
WHERE EXISTS (SELECT * FROM [xxxx].[xxxx].[xxxx] B
WHERE A.PERSON_ID = B.PERSON_ID AND B.[DATE] = '2018M06')
GROUP BY [PERSON_ID]

SQL Query: Cannot perform aggregate functions on sub queries

I have the following SQL query
SELECT
[Date],
DATENAME(dw,[Date]) AS Day,
SUM(CASE WHEN ChargeCode IN (SELECT ChargeCode FROM tblChargeCodes WHERE Chargeable = 1) THEN Units ELSE 0 END) ChargeableTotal,
SUM(CASE WHEN ChargeCode IN (SELECT ChargeCode FROM tblChargeCodes WHERE Chargeable = 0) THEN Units ELSE 0 END) NotChargeableTotal,
SUM(Units) AS TotalUnits
FROM
tblTimesheetEntries
WHERE
UserID = 'PJW'
AND Date >= '2013-01-01'
GROUP BY
[Date]
ORDER BY
[Date] DESC;
But I get the error message:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Because I am using sub queries in the Case Else Summation.
How can I revise my query to get 2 x Sums of [Units] one for Chargeable = true, and one for Chargeable = false, even though the Chargeable field is in a different table to all the other information. The two tables are linked by ChargeCode which appears in both tblTimesheetEntries and tblChargeCodes.
Have you tried joining the tables on the chargeCode:
SELECT e.[Date],
DATENAME(dw,e.[Date]) AS Day,
SUM(CASE WHEN c.Chargeable = 1 THEN e.Units ELSE 0 END) ChargeableTotal,
SUM(CASE WHEN c.Chargeable = 0 THEN e.Units ELSE 0 END) NotChargeableTotal,
SUM(e.Units) AS TotalUnits
FROM tblTimesheetEntries e
LEFT JOIN tblChargeCodes c
on e.ChargeCode = c.ChargeCode
WHERE e.UserID = 'PJW'
AND e.Date >= '2013-01-01'
GROUP BY e.[Date]
ORDER BY e.[Date] DESC;

SQL Query to compare 2 weeks

I've got to design a query in visual studio where I have 2 data sets.
basically it goes like this.
I want to compare this weeks call total to last week per country calling.
the only thing is last weeks calls may have come from 20 diff countries while this weeks might only have come from 15.
How can I make the query such that the 20 countries will show up for both while having "0" value in for countries that do not appear this week.
below is my query:
Select country,
Sum(Case When actstatus in (5,105) Then 1 Else 0 End) As TotalCalls,
Sum(Case When actstatus = 105 Then 1 Else 0 End) As FailedCalls
From termactivity(nolock)
INNER JOIN termconfig(NOLOCK) ON cfgterminalID = actterminalID
INNER JOIN Country (nolock) on country = cycode
Where actstatus in (5,105)
and (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
Group By country
order By country asc
When Act status = 105 it means the call was not completed and when it = 5 it means the call was successful. I am doing this to get a successful call % rate per week.
Thanks in Advance!
Apply the same logic as you did to total calls and failed calls as you did to the this week and last week.
SELECT country,
COUNT(CASE WHEN actTerminalDateTime < #StartDate THEN 1 END) [LastWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime < #StartDate THEN 1 END) [LastWeekFailedCalls],
COUNT(CASE WHEN actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekFailedCalls]
FROM termactivity (NOLOCK)
INNER JOIN termconfig (NOLOCK)
ON cfgterminalID = actterminalID
INNER JOIN Country (NOLOCK)
ON country = cycode
WHERE actstatus in (5,105)
AND actTerminalDateTime BETWEEN DATEADD(DAY, -7, #StartDate) AND #EndDate
GROUP BY country
ORDER BY country ASC
I've also tidied up your query slightly, for example there is no point in specifying
WHEN ActStatus IN (5, 105) ...
When your WHERE clause already limits all results to 5, 105, therefore this is a redundant predicate in your case expression
From what I understand, you want to perform separate queries for two weeks, and you want both queries to produce rows for all countries, regardless of whether all countries had any calls. To achieve this, you need to use LEFT OUTER JOINS. The below code should guarantee that every country found in the Country table has a row, even if both sums are 0.
SELECT country,
SUM(CASE WHEN actstatus IN (5,105) THEN 1 ELSE 0 END) AS TotalCalls,
SUM(CASE WHEN actstatus = 105 THEN 1 ELSE 0 END) AS FailedCalls
FROM Country (NOLOCK)
LEFT OUTER JOIN termconfig (NOLOCK) ON country = cycode
LEFT OUTER JOIN termactivity (NOLOCK) ON cfgterminalID = actterminalID
WHERE (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
GROUP BY country
ORDER BY country ASC
If this was not what you wanted, perhaps you need to clarify your question. Many others have assumed that you want to combine the results into a single query.