COUNT and GROUP functions used in outer query and subquery - sql

I have written a query which involves creating sum figures and percentages of values (dumped to a single row) held on a single table. Each of the subqueries varies slightly with regard to the WHERE parameters. For example:
select distinct
count(KS2_PupilMatchingRefAnonymous) AS 'Total Pupils',
(select count(KS2_PupilMatchingRefAnonymous)
from KS2_CandInd_2011
where KS2_MMSCH=1
and KS2_ELIGENGTA=1) AS 'All Eligible Pupils',
(select count(*)
from KS2_CandInd_2011
where KS2_MMSCH=1
and KS2_ELIGENGTA=1
and KS2_ELIGENGTA=1
and FSMeligible=1) AS 'Eligible FSM only',
(select round(
(cast((
select count(*)
from KS2_CandInd_2011
where KS2_MMSCH=1
and KS2_ELIGENGTA=1
and KS2_ELIGENGTA=1
and FSMeligible=1) AS float) /
cast((
select count(KS2_PupilMatchingRefAnonymous)
from KS2_CandInd_2011
where KS2_MMSCH=1
and KS2_ELIGENGTA=1) AS float))*100,2
)) AS '% Eligible FSM'
from KS2_CandInd_2011
where KS2_MMSCH=1
I now want breakdown each subquery with a COUNT (and GROUP BY, of course) according to gender. If the COUNT/GROUP BY for gender is placed in the outer query, I only get a breakdown/split for the 'Total Pupils' value.
If I try to enter the same clauses in the subqueries I am told
Msg 116, Level 16, State 1, Line 12
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
The inner queries aren't really referencing the outer query, as per a correlated subquery, so im not really sure how to overcome the error despite my best attempts. Does anyone have any suggestions?

We can re-write your query in much simpler way as shown below.(assuming you have a column called gender in your table: KS2_CandInd_2011
select
a.gender
,count(*) AS 'Total Pupils',
,sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 then 1
else 0
end
) as 'All Eligible Pupils'
,sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 and KS2_ELIGENGTA=1 and FSMeligible=1 then 1
else 0
end) as 'Eligible FSM only'
,round(cast( sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 then 1
else 0
end
) as float)/
cast(sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 and KS2_ELIGENGTA=1 and FSMeligible=1 then 1
else 0
end) as float)
*100,2) AS '% Eligible FSM'
from KS2_CandInd_2011 as a
where a.KS2_MMSCH=1
group by a.gender
order by a.gender;
The following is I believe will give you the exact output of your original query:
select
,count(*) AS 'Total Pupils',
,sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 then 1
else 0
end
) as 'All Eligible Pupils'
,sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 and KS2_ELIGENGTA=1 and FSMeligible=1 then 1
else 0
end) as 'Eligible FSM only'
,round(cast( sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 then 1
else 0
end
) as float)/
cast(sum(case
when KS2_MMSCH=1 and KS2_ELIGENGTA=1 and KS2_ELIGENGTA=1 and FSMeligible=1 then 1
else 0
end) as float)
*100,2) AS '% Eligible FSM'
from KS2_CandInd_2011 as a
where a.KS2_MMSCH=1
;

Related

How to correct error when aggregating from subquery

I have a query that looks like this:
SELECT store_id,
(CASE WHEN txns_A>0 AND txns_B=0 THEN 'A Only' WHEN txns_A=0 AND txns_B>0 THEN 'B Only' END) A_B_indicator,
sum(1) cnt_customers,
sum(spend_A+spend_B)/sum(txns_A+txns_B) avg_receipt
FROM(
SELECT store_id, cust_id
SUM(CASE WHEN A_B_indicator='A' THEN spend else 0 end) spend_A,
SUM(CASE WHEN A_B_indicator='B' THEN spend else 0 end) spend_B,
SUM(CASE WHEN A_B_indicator='A' THEN spend else 0 end) txns_A,
SUM(CASE WHEN A_B_indicator='B' THEN spend else 0 end) txns_B
FROM table1
GROUP BY store_id, cust_id
) table2;
However, this generates an error because store_id is not in a GROUP BY clause. When I rewrite the query to include a GROUP BY store_id clause, it complains that the aggregate columns are not in the Group By. However, if I add them by rewriting the Group By to be Group BY 1,2,3,4, this also generates an error (Not yet supported place for UDAF Sum).
How can I rewrite this query to be error-free?
You can write this as:
SELECT store_id,
(CASE WHEN SUM(txns_A) > 0 AND SUM(txns_B) = 0 THEN 'A Only'
WHEN SUM(txns_A) = 0 AND SUM(txns_B) > 0 THEN 'B Only'
END) as A_B_indicator,
COUNT(*) as cnt_customers,
SUM(spend_A+spend_B)/sum(txns_A+txns_B) as avg_receipt
FROM (SELECT store_id, cust_id
SUM(CASE WHEN A_B_indicator='A' THEN spend else 0 end) as spend_A,
SUM(CASE WHEN A_B_indicator='B' THEN spend else 0 end) as spend_B,
SUM(CASE WHEN A_B_indicator='A' THEN spend else 0 end) as txns_A,
SUM(CASE WHEN A_B_indicator='B' THEN spend else 0 end) as txns_B
FROM table1
GROUP BY store_id, cust_id
) table2
GROUP BY store_id;

SUM value when another column value is DISTINCT

I was wondering how I can SUM the values of a column based on another column's values being distinct like below. I tried the following two ways, each giving errors due to the aggregate function. I am trying to get NonDistinctTotals with the queries below.
SELECT SUM(InvoiceSaleAmt) AS NonDistinctTotals, SUM(case when count(*) over (partition by InvoiceNo) = 1 then InvoiceSaleAmt else 0 END) as DistinctTotals, SUM(CASE WHEN PaymentType= 'CASH' THEN CashTotal ELSE 0 END) AS CashTotal
FROM #InvoiceTable a
group by LocationId, InvoiceNo
Error: Windowed functions cannot be used in the context of another windowed function or aggregate.
SELECT SUM(InvoiceSaleAmt) AS NonDistinctTotals, SUM(CASE WHEN InvoiceNoin (SELECT DISTINCT InvoiceNofrom #InvoiceTable) THEN InvoiceSaleAmt else 0 END) as DistinctTotals, SUM(CASE WHEN PaymentType= 'CASH' THEN CashTotal ELSE 0 END) AS CashTotal
FROM #InvoiceTable a
group by LocationId, InvoiceNo
Error: Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Use a subquery:
SELECT SUM(InvoiceSaleAmt) AS NonDistinctTotals,
SUM(case when cnt = 1 then InvoiceSaleAmt else 0 END) as DistinctTotals,
SUM(CASE WHEN PaymentType = 'CASH' THEN CashTotal ELSE 0 END) AS CashTotal
FROM (SELECT it.*, COUNT(*) over (partition by InvoiceNo) as cnt
FROM #InvoiceTable it
) it
GROUP BY LocationId, InvoiceNo

Group by in sql for two conditions

I have this table
SELECT
[WeldStatus]
,PackageId
FROM [SPMS2].[dbo].[JointHistory]
How can I group by this query to calculate total testpackage and total accept and total remain?
For example for packageid=6030 I need this:
packageid total accept remain
6030 4 3 1
Use CASE and Group By
SELECT
PackageId,
count(*) as total,
sum(case [WeldStatus] when 'Accept' then 1 end) as accept,
sum(case [WeldStatus] when 'accept' then 0 else 1 end) as remain
FROM [SPMS2].[dbo].[JointHistory]
group by PackageId;
SELECT
packageid
,count(*) as 'total'
,sum(case when weldstatus = 'Accept' then 1 else 0 end) as 'accept'
,sum(case when weldstatus is null then 1 else 0 end) as 'remain'
FROM [SPMS2].[dbo].[JointHistory]
group by packageid
"sum(case when..." has saved me so many times. NULLs can be a pain but the case when saves you.
You can use CASE expression with in your aggregation to achieve your desired output.
SELECT
PackageId,
SUM (CASE WHEN WeldStatus = 'Accept' THEN 1 ELSE 0 END) AS AcceptCount,
SUM (CASE WHEN WeldStatus IS NULL THEN 1 ELSE 0 END) AS RemainCount,
COUNT (*) AS Total
FROM [SPMS2].[dbo].[JointHistory]
GROUP BY PackageId
select packageid, SUM(TOTAL), SUM(ACCEPT), SUM(REMAIN) from (
select packageid, 1 as TOTAL,
case
when WeldStatus='Accept' then 1 else 0
END as ACCEPT,
case
when WeldStatus IS NULL then 1 else 0
END as REMAIN
from JointHistory
) group by packageid
If WeldStatus only takes on the values of 'Accept' or NULL, then a simple method is:
SELECT PackageId, count(*) as total,
count(WeldStatus) as accept,
count(*) - count(WeldStatus) as remain
FROM [SPMS2].[dbo].[JointHistory] jh
GROUP BY PackageId;
I'm the first to admit that this relies on the assumption of having a single value or NULL.
SELECT PackageId,
COUNT(*) AS total,
SUM(CASE WHEN [WeldStatus] = 'Accept' THEN 1 END) AS accept,
SUM(CASE WHEN ISNULL([WeldStatus],'') = '' THEN 1 ELSE 0 END) AS remain
FROM [SPMS2].[dbo].[JointHistory]
group by PackageId;

Get the Highest Value in different Select SUM

I want to get the highest value in my query
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'
The Output would be TotalMonday ='1' and TotalTuesday ='2'
I need to get the highest value from the outputs which in this case is TotalTuesday=2
select max(daycnt) from
(Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS daycnt
from tbl_sched WHERE teacherID='2014279384'
union all
Select SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS daycnt
from tbl_sched WHERE teacherID='2014279384')
If you need the max between many columns:
Something interesting in SQLServer 2008 and above
SELECT (SELECT Max(v)
FROM (VALUES ([Total Monday]), ([Total Tuesday]), ...) AS value(v)) as [MaxDate]
From
(
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
..........
FROM tbl_sched
WHERE teacherID='2014279384'
)a
Another option:
SELECT Case When [Total Monday] > [Total Tuesday] then [Total Monday] else [Total Tuesday] End as maxvalue
FROM
(
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'
)a
I'd say the query below is better in terms of performance and highlights the intention better, because basically we are just GROUPing by days and COUNTing the groups, we don't need CASE's or SUM's (in which case SQL Server will have to go over all the records of the selected teacher).
SELECT TOP 1 Day, COUNT(*) AS Total
FROM tbl_sched
WHERE teacherID='2014279384'
AND Day IN ('Monday','Tuesday')
GROUP BY Day
ORDER BY Total DESC
You can just group by Day, sort by COUNT(*) DESC and get the top count:
SELECT TOP (1)
TotalCount = COUNT(*)
FROM
dbo.tbl_sched
WHERE
teacherID = '2014279384'
GROUP BY
Day
ORDER BY
TotalCount DESC
;
You can also include Day into the output to return the day that had the topmost result.
You can achieve this by using Max Function
Select MAX(SUM(CASE WHEN Day='Monday' THEN 1 END)) AS 'Total Monday',
MAX(SUM(CASE WHEN Day='Tuesday' THEN 1 END)) AS 'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'

SQL Query using calculation in SELECT not working

the purpose of this query is to take the percentage of late orders to total number of orders. So the calculation part of this query is only pulling back zeros. Everything else works. I commented the part below that is pulling back zeros. What am I doing wrong?
SELECT
case LM.DState
WHEN 'OH' THEN 'OH Order'
WHEN 'NC' THEN 'NC Order'
WHEN 'TX' THEN 'TX Order'
WHEN 'WA' THEN 'WA order'
Else 'Other'
End as 'OrderType',
count(LM.OrderID) as 'Vol',
sum(case
WHEN datediff(day, S.Appt, S.Arrived) > 0 THEN 1
else 0
END) as 'TotalLates',
--PART THAT IS ONLY PULLING ZEROS, SHOULD BE NON-ZERO
CAST (
sum(case WHEN datediff(day, S.Appt, S.Arrived) > 0 THEN 1 else 0 END)
/ count(LM.OrderID)
as decimal(5,2)
) *100 as 'OTD%'
FROM ((Customers.dbo.CusOrders as LM WITH (NOLOCK) inner join
Customers.dbo.CusLocations as S WITH (NOLOCK) on LM.OrderID = S.OrderID) inner join
Customers.dbo.Loads as L with (NOLOCK) on LM.OrderID = L.OrderID)
WHERE LM.CusCode = 'Domestic'
GROUP BY case LM.DState
WHEN 'OH' THEN 'OH Order'
WHEN 'NC' THEN 'NC Order'
WHEN 'TX' THEN 'TX Order'
WHEN 'WA' THEN 'WA Order
Else 'Other'
End
It's due to rounding that occurs with integer division, since you're calculating a percentage the value is always below 1, and that gets rounded to 0, you can fix this by multiplying the numerator by 1.0:
CAST (
sum(case WHEN datediff(day, S.Appt, S.Arrived) > 0 THEN 1 else 0 END)*1.0
/ count(LM.OrderID)
as decimal(5,2)
) *100 as 'OTD%'
For example:
SELECT 5/7
--0
SELECT 5.0/7
--0.714285