Using Math to calculate a percentage - sql

OK, imagine I have a single column with 200 rows, in each row is a value either 4,8,2 or 0. Is there a way to divide the COUNT of times 2 and 0 occur against the COUNT of times 4 and 8 occur? Like this:
(COUNT(2, 0) / COUNT(4, 8)) * 100
Thanks! :)
EDIT:
Got a Divide By 0 error on the following statement, although doing COUNT(*) where Completion_Event_Type_Key IN (2,0) OR (8,4) returns over 3000000 rows
SELECT DISTINCT
SD.Last_Secondary_School_Name AS 'School', ((SELECT COUNT(*) where CC.Completion_Event_Type_Key IN (2,0))/(SELECT COUNT(*) where CC.Completion_Event_Type_Key IN (4, 8)))*100
FROM [AUTDataWarehouse].[dbo].[Fact_Admission] AS FA
INNER JOIN [AUTDataWarehouse].[dbo].[Dim_Student_Demographics] AS SD ON SD.Student_Demographics_Key = FA.Student_Demographics_Key
INNER JOIN [AUTDataWarehouse].[dbo].[Fact_SDR_Course_Completion] AS CC ON FA.Student_Demographics_Key = CC.Student_Demographics_Key
GROUP BY
SD.Last_Secondary_School_Name, CC.Completion_Event_Type_Key
ORDER BY SD.Last_Secondary_School_Name

You can do it like this:
select
sum(case when value in (2,0) then 1 else 0 end) /
sum(case when value in (4,8) then 1 else 0 end) * 100
from table
Beware division by zero, though. You might need special handling if there could be no rows with 4,8.
Given the query in your question, your SQL should look something like this:
SELECT
SD.Last_Secondary_School_Name AS 'School',
sum(case when CC.Completion_Event_Type_Key IN (2,0) then 1 else 0 end) /
sum(case when CC.Completion_Event_Type_Key IN (4,8) then 1 else 0 end) * 100
FROM [AUTDataWarehouse].[dbo].[Fact_Admission] AS FA
INNER JOIN [AUTDataWarehouse].[dbo].[Dim_Student_Demographics] AS SD ON SD.Student_Demographics_Key = FA.Student_Demographics_Key
INNER JOIN [AUTDataWarehouse].[dbo].[Fact_SDR_Course_Completion] AS CC ON FA.Student_Demographics_Key = CC.Student_Demographics_Key
GROUP BY SD.Last_Secondary_School_Name, CC.Completion_Event_Type_Key
ORDER BY SD.Last_Secondary_School_Name

Related

How to count with cases without subqueries in this situation

I have a table grades[Student, Grade]
My task is to сount the amount of students, have 2 A's (2) and at least one D (2).
The most painful part is that i shouldn't use sql subqueries
That abomination i come up with would only list such nulls and students which fit by condition
Any ideas how to count them ? Or rework query?
SELECT CASE WHEN
(
sum(CASE WHEN g1.grade = 5
THEN 1
ELSE 0
END) > 2
)
AND
(
sum(CASE WHEN g1.grade = 2
THEN 1
ELSE 0
END) > 0
)
THEN g1.student_name
ELSE NULL
END as student_name
FROM grades g1
group by g1.student_name
You don't need any sub queries you can just count and filter using having and conditional sum
select Student
from Grades
group by Student
having sum(case when Grade='A' then 1 end)=2
and sum(case when Grade='D' then 1 end)>0
Edit
To get just the count of qualifying rows you can do
select distinct count(*) over()
from Grades
group by Student
having sum(case when Grade='A' then 1 end)=2
and sum(case when Grade='D' then 1 end)>0
Oh how I miss homework....
Depending on your database, you can use PIVOT:
SELECT *
FROM grades PIVOT (COUNT(*) AS CNT FOR grade IN (5 AS A, 2 AS D)) g
WHERE g.a_cnt >= 2 AND g.d_cnt > 0;
Alternate:
SELECT g.student
FROM grades g
GROUP BY g.student
HAVING COUNT(DECODE(g.grade, 5, 1, NULL)) >= 2 AND COUNT(DECODE(g.grade, 2, 1, NULL)) > 0;

Sum a column and perform more calculations on the result? [duplicate]

This question already has an answer here:
How to use an Alias in a Calculation for Another Field
(1 answer)
Closed 3 years ago.
In my query below I am counting occurrences in a table based on the Status column. I also want to perform calculations based on the counts I am returning. For example, let's say I want to add 100 to the Snoozed value... how do I do this? Below is what I thought would do it:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
Snoozed + 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
I get this error:
Invalid column name 'Snoozed'.
How can I take the value of the previous SUM statement, add 100 to it, and return it as another column? What I was aiming for is an additional column labeled Test that has the Snooze count + 100.
You can't use one column to create another column in the same way that you are attempting. You have 2 options:
Do the full calculation (as #forpas has mentioned in the comments above)
Use a temp table or table variable to store the data, this way you can get the first 5 columns, and then you can add the last column or you can select from the temp table and do the last column calculations from there.
You can not use an alias as a column reference in the same query. The correct script is:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+100 AS Snoozed
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
MSSQL does not allow you to reference fields (or aliases) in the SELECT statement from within the same SELECT statement.
To work around this:
Use a CTE. Define the columns you want to select from in the CTE, and then select from them outside the CTE.
;WITH OurCte AS (
SELECT
5 + 5 - 3 AS OurInitialValue
)
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM OurCte
Use a temp table. This is very similar in functionality to using a CTE, however, it does have different performance implications.
SELECT
5 + 5 - 3 AS OurInitialValue
INTO #OurTempTable
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM #OurTempTable
Use a subquery. This tends to be more difficult to read than the above. I'm not certain what the advantage is to this - maybe someone in the comments can enlighten me.
SELECT
5 + 5 - 3 AS OurInitialValue
FROM (
SELECT
OurInitialValue / 2 AS OurFinalValue
) OurSubquery
Embed your calculations. opinion warning This is really sloppy, and not a great approach as you end up having to duplicate code, and can easily throw columns out-of-sync if you update the calculation in one location and not the other.
SELECT
5 + 5 - 3 AS OurInitialValue
, (5 + 5 - 3) / 2 AS OurFinalValue
You can't use a column alias in the same select. The column alias do not precedence / sequence; they are all created after the eval of the select result, just before group by and order by.
You must repeat code :
SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+ 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
If you don't want to repeat the code, use a subquery
SELECT
ID, Name, LeadCount, Working, Uninterested,Converted, Snoozed, Snoozed +100 AS test
FROM
(SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed
FROM Prospects p
INNER JOIN ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE p.Store = '108'
GROUP BY pu.Name, pu.Id) t
ORDER BY Name
or a view

How to write case when statement to see if sum of 2 values equal?

I am still learning sql so I apologize if this is easy stuff.
I need the sql code to sum the sales amount and sum the returnamount, and if they equal I want to mark that accountnumber with a 1 to know that it is fully cancelled.
i.[status], i.accountnumber,
case when sum(i.saleamount) = sum(r.returnamount) then 1 else 0 end as full_return_flag
from [idtable] i
join [returntable] r on r.id = i.id
> Account Number Sale Amount Return Amount Full_return_flag Status
> 1 500 250 1 Open
> 2 500 1500 1 Open
> 3 2000 0 0 Neutral
> 4 100 0 0 Closed
You want aggregation with case expression :
select i.status, i.accountnumber, sum(i.saleamount) as saleamount, sum(r.returnamount) as returnamount,
(case when sum(i.saleamount - r.returnamount) = 0 then 1 else 0 end) as Full_return_flag
from idtable] i inner join
returntable r
on r.id = t.id
group by i.status, i.accountnumber;
You need a full query:
select i.accountnumber,
sum(i.saleamount) as sale_amount, sum(r.returnamount) as return_amount,
(case when sum(i.saleamount) = sum(r.returnamount) then 1 else 0 end) as full_return_flag
from [idtable] i left join
[returntable] r
on r.id = i.id
group by i.accountnumber;
status is not in the result set so I don't think it is needed in the query. But you can include it both in the select and group by if you need it.

Change sub query to join

all I have used subquery[Below] to identify the percentage. But I need a query without subquery. Can anyone please help me, how to use joins to calculate the percentage?
Query used
SELECT 'Dropping_Percentage',
( Cast(dropped_count AS DECIMAL(16, 9)) / Cast(new_count AS DECIMAL(16, 9
)) ) *
100
FROM (SELECT count AS New_count,
'1' a
FROM new_count)a,
(SELECT Count(*) Dropped_count,
'1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o
ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL)c
WHERE a.a = c.b
"I need a query without subquery."
Answer:
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
Explanation:
Reviewing the main query with assumptions about the content:
SELECT Count(*) Dropped_count, '1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL
should give you the number of records in pfo_bhi_old that do not appear in pfo_bhi_new. Assumption here is that you need to do the total based on the existing right join. All the matching and non matching records.
Therefore It's possible to count all the existing records by removing the where clause,
COUNT(*) would give you that total.
Next you want to count the ones where there's no match which will give you the "Dropping count" the value you had before with the where clause, that is where ones where the id_membid_claimid_c was null that is SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end). Said differently it will add 1 to the sum only when the id_membid_claimid_c field is null, otherwise it will add zero (0).
I've multiplied the numerator by 1.0 to force SQL Server to use decimal values and make the query easier to read.
Here's what it should look like if you needed to use decimals(16,9) as the result.
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
, CAST(SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) AS DECIMAL(16,9)) / CAST(Count(*) AS DECIMAL(16,9)) as Dropping_Pct_16_9
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c

Count rows for two columns using two different clauses

I'm after a CTE which I want to return two columns, one with the total number of 1's and one with the total number of 0's. Currently I can get it to return one column with the total number of 1's using:
WITH getOnesAndZerosCTE
AS (
SELECT COUNT([message]) AS TotalNo1s
FROM dbo.post
WHERE dbo.checkletters([message]) = 1
--SELECT COUNT([message]) AS TotalNo0s
--FROM dbo.post
--WHERE dbo.checkletters([message]) = 0
)
SELECT * FROM getOnesAndZerosCTE;
How do I have a second column called TotalNo0s in the same CTE which I have commented in there to show what I mean.
Using conditional aggregation:
WITH getOnesAndZerosCTE AS(
SELECT
TotalNo1s = SUM(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 ELSE 0 END),
TotalNo0s = SUM(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 ELSE 0 END)
FROM post
)
SELECT * FROM getOnesAndZerosCTE;
For using COUNT() directly just be aware that it counts any NON-NULL values. You can omit the ELSE condition which implicitly returns NULL if not stated
SELECT
COUNT(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 END) TotalNo1s
, COUNT(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 END) TotalNo0s
FROM post
or, explicitly state NULL
SELECT
COUNT(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 ELSE NULL END) TotalNo1s
, COUNT(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 ELSE NULL END) TotalNo0s
FROM post
You can do it without CTE
select count(message) total,
dbo.checkletters(message) strLength
from post
group by dbo.checkletters(message)
having dbo.checkletters(message) in (1, 2) //All the messages with length 1 or 2