Getting the percent of a number - sql-server

I think it is some funky rounding issue going on here, but I don't know how the inner sql works. So maybe someone can help.
SELECT COUNT(Id) TotalReferrals,
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1 ELSE 0 END) NHSCommNeed1Total,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1 ELSE 0 END) NHSCommNeed2Total,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1 ELSE 0 END)
NHSCommNeed3Total
INTO #Totals
FROM DDS.Referrals
Gives me the following result set
TotalReferrals NHSCommNeed1Total NHSCommNeed2Total NHSCommNeed3Total
1008 1008 508 508
Then when I do this
SELECT
SUM((NHSCommNeed1Total / TotalReferrals) * 100) NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / TotalReferrals) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / TotalReferrals) * 100) NHSCommNeed3Percent
FROM #Totals
I get this?
NHSCommNeed1Percent NHSCommNeed2Percent NHSCommNeed3Percent
100 0 0
Can someone explain what is going on and how do I fix it

The result of NHSCommNeed1Total / (TotalReferrals is calculated as integer, therefore it ends up to 0. To enforce making it decimal or double just multiply it with 1.0:
SELECT
SUM((NHSCommNeed1Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed3Percent
FROM #Totals
Yes sure, no need for two statements, you can use INSERT INTO SELECT:
WITH CTE
AS
(
SELECT COUNT(Id) TotalReferrals,
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1.0 ELSE 0 END) NHSCommNeed1Total,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1.0 ELSE 0 END) NHSCommNeed2Total,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1.0 ELSE 0 END)
NHSCommNeed3Total
FROM DDS.Referrals
)
INSERT INTO SomeOtherTable(NHSCommNeed1Total,NHSCommNeed2Total ,NHSCommNeed3Total)
SELECT
SUM((NHSCommNeed1Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed3Percent
FROM CTE;

If you want an all in one statement this should work
SELECT
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed1Percent,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed2Percent,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed3Percent
FROM DDS.Referrals

Related

How to get total and percentage of values more than 10 in SQL Server

I have written a SQL statement which yields Days taken and count of how many of each.
What I’d like to accomplish is a counts and percentages of how above and below 10 days there are.
Below sample data and what I'd like to achieve in colored texts.
Your help will be much appreciated thanks heaps.
If you just want the last two days, you can use:
select (case when days_take <= 10 then '10 or less' else 'greater than 10' end) as grp,
count(*) * 100.0 / sum(count(*)) over () as percentage,
count(*) as ratio
from t
group by (case when days_take <= 10 then '10 or less' else 'greater than 10' end);
In TSQL, you can use cross apply and aggregation:
You can do conditional aggregation:
select
x.descr,
1.0 * sum(x.how_many) / sum(sum(x.how_many)) over() as how_many_ratio,
sum(x.how_many) as how_many_value
from mytable t
cross apply (values
(
'greater than 10 days'
case when days_taken > 10 then how_many else 0 end
),
(
'less than and 10 days'
case when days_taken <= 10 then how_many else 0 end
)
) as x(descr, how_many)
group by x.descr
If you are content with having all results on a single row, conditional aggregation is simpler:
select
1.0 * sum(case when days_taken > 10 then how_many else 0 end)
/ sum(how_many) as how_many_above_10_ratio,
sum(case when days_taken > 10 then how_many else 0 end) as how_many_above_10,
1.0 * sum(case when days_taken <= 10 then how_many else 0 end)
/ sum(how_many) as how_many_below_10_ratio,
sum(case when days_taken <= 10 then how_many else 0 end) as how_many_below_10
from mytable

How to represent 0.5 or 1/2 in a AVG() Function SQL Server

I have 2 columns which have an occupancy column and contains values of 0 and 1.
Here is an example:
After group by based on other columns, these columns will be combined and the average will be 0.5 since 0+1/2 = 0.5 or 1/2.
So how do I do this: case when avg(occupancy) = 1/2 or 0.5 then '1' else '0'?
I know something about SQL round down 1/2 to 0 or something along that lines
What I have tried:
case when
Avg(occupancy) = 1.0/2.0
Here is my full view code.
CREATE VIEW [iot].[v_test]
AS
SELECT
ro.[RoomCode],
ro.[RoomName],
ro.[DeviceID],
ro.[TpID],
CAST(avg(ro.[Temperature]) AS decimal(10, 1)) AS [Temperature],
CASE
WHEN avg(cast(occupancy as decimal)) between 0.01 and 1.0
THEN '1'
WHEN avg(cast(occupancy as decimal)) = 1/2.0
THEN '1'
ELSE
'0'
END AS Occupancy,
(DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME)))) AS [Time],
ctt.[DAY],
cs.ClassroomStatus
FROM
[iot].[v_RoomOccupancy] ro
LEFT OUTER JOIN
[iot].[ClassTimeTable] ctt
ON ro.RoomCode = ctt.ROOMID
AND (ro.LocalTime
BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE
)
CROSS APPLY
(
SELECT
CASE
WHEN (ro.LocalTime BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE)
AND (cast(occupancy as decimal) between 0.1 and 1.0 )
THEN 'Booked And Occupied'
WHEN ((ro.LocalTime NOT BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE) OR ( ctt.CLASSSTARTDATE IS NULL AND ctt.CLASSENDDATE IS NULL))
AND (cast(occupancy as decimal) between 0.1 and 1.0 )
THEN 'Not Booked but Occupied'
WHEN (ro.LocalTime BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE)
AND cast(occupancy as decimal) <= 0.0
THEN 'Booked but Not Occupied'
WHEN ((ro.LocalTime NOT BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE) OR (ctt.CLASSSTARTDATE IS NULL AND ctt.CLASSENDDATE IS NULL))
AND cast(occupancy as decimal) <= 0.0
THEN 'Not Booked and Not Occupied'
ELSE
'Null'
END AS ClassroomStatus
)AS cs
GROUP BY
ro.[RoomCode],
ro.[RoomName],
ro.[DeviceID],
ro.[TpID],
(DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME)))),
ctt.[DAY],
cs.ClassroomStatus;
try this.
select case when avg(cast(occupancy as decimal)) in (.5, 1.0, 0) then 1 else 0 end
or
select case when avg(cast(occupancy as decimal)) between 0.0 and 1.0 then '1' else 'Error' end

Show value when CASE expression does not return rows

This code returns a fixed column 198 and then another one with a CASE. This CASE may return 0 rows (because there's also 0 row with TRL_ORIGQTY in the database, so it's not even evaluated)
If there are rows, the result is (tested with other organizations):
ORG SumTotal
198 98.51
If there are no rows in the database, the result shows nothing, but it should be:
ORG SumTotal
198 NULL
or whatever:
ORG SumTotal
198 00001
This is the full code:
declare #startdate varchar(30);
declare #enddate varchar(30);
declare #count int;
set #startdate = '2017-12-01'
set #enddate = '2018-01-01'
select #count = COUNT(*)
from R5TRANSLINES
where TRL_TYPE = 'STTK'
and TRL_TRANS in (select TRA_CODE from R5TRANSACTIONS
where TRA_FROMCODE in ('198')
and TRA_STATUS in ('A')
and TRA_DATE between #startdate and #enddate)
select distinct
'198' as 'ORG',
(SUM(CASE
WHEN (TRL_ORIGQTY = 0 AND TRL_ORIGQTY > 0) THEN 0
WHEN ((TRL_ORIGQTY - TRL_QTY) = 0 AND TRL_ORIGQTY = 0) THEN 100
WHEN ((TRL_ORIGQTY - TRL_QTY) > TRL_ORIGQTY) THEN ((TRL_ORIGQTY / (TRL_ORIGQTY - TRL_QTY)) * 100)
ELSE (((TRL_ORIGQTY - TRL_QTY) / TRL_ORIGQTY) * 100)
END) OVER ()/#count) AS 'SumTotal'
from
R5TRANSLINES
where
TRL_TYPE = 'STTK'
and TRL_TRANS in (select TRA_CODE from R5TRANSACTIONS
where TRA_FROMCODE in ('198')
and TRA_STATUS in ('A')
and TRA_DATE between #startdate and #enddate)
Tried with ISNULL, or NOT EXISTS, or even with another CASE = NULL, but none of them work... no rows returned.
UPDATED CODE:
select distinct
'ESREQ1' as 'ORG',
CASE COUNT(*)
WHEN 0 THEN 0
ELSE (SUM(CASE
WHEN (TRL_ORIGQTY = 0 AND TRL_ORIGQTY > 0) THEN 0
WHEN ((TRL_ORIGQTY - TRL_QTY) = 0 AND TRL_ORIGQTY = 0) THEN 100
WHEN ((TRL_ORIGQTY - TRL_QTY) > TRL_ORIGQTY) THEN ((TRL_ORIGQTY / (TRL_ORIGQTY - TRL_QTY)) * 100)
ELSE (((TRL_ORIGQTY - TRL_QTY) / TRL_ORIGQTY) * 100)
END) OVER ()/#count)
END AS 'SumTotal'
from
R5TRANSLINES
where
TRL_TYPE = 'STTK'
and TRL_TRANS in (select TRA_CODE from R5TRANSACTIONS
where UPPER(TRA_DESC) like '%AUDITESREQ1%'
and TRA_FROMCODE in ('138-05')
and TRA_STATUS in ('A')
and TRA_DATE between #startdate and #enddate)
group by
TRL_ORIGQTY, TRL_QTY
The reason you have 0 rows returning is that a SUM of 0 rows will return no rows (thus no dataset). The only exception to this is when using COUNT; which returns a 0 when there are no rows.
You could, therefore, use a further CASE expression before hand with a COUNT(*), and then put the SUM in the ELSE.
SELECT '198' AS ORG, --DISTINCT achieves nothing here, so I have removed it.
CASE COUNT(*) WHEN 0 THEN 0
ELSE ... --your SUM
END AS SumTotal
FROM R5TRANSLINES ...
Also, please think about using White Space when writing SQL. Your Code is very difficult to read to the complete lack of indentation. It makes your life easier, and anyone else that needs to read your statements. (this is why I haven't rewritten your query, as i'd spend more time formatting it than anything else).
Edit: Based on the OPs latest SQL:
SELECT 'ESREQ1' AS ORG,
CASE COUNT(*) WHEN 0 THEN 0
ELSE SUM(CASE WHEN (TRL_ORIGQTY = 0 AND TRL_ORIGQTY > 0) THEN 0
WHEN ((TRL_ORIGQTY - TRL_QTY) = 0 AND TRL_ORIGQTY = 0) THEN 100
WHEN ((TRL_ORIGQTY - TRL_QTY) > TRL_ORIGQTY) THEN ((TRL_ORIGQTY / (TRL_ORIGQTY - TRL_QTY)) * 100)
ELSE (((TRL_ORIGQTY - TRL_QTY) / TRL_ORIGQTY) * 100)
END) / #count
END AS SumTotal
FROM R5TRANSLINES
WHERE TRL_TYPE ='STTK'
AND TRL_TRANS IN (SELECT TRA_CODE
FROM R5TRANSACTIONS
WHERE UPPER(TRA_DESC) LIKE '%AUDITESREQ1%'
AND TRA_FROMCODE IN ('138-05')
AND TRA_STATUS = 'A'
AND TRA_DATE BETWEEN #startdate AND #enddate);
Note that this is untested, as I don't have any sample data to run this against. If this still doesn't work, please provide some DDL and consumable Sample data, that we can run against.
Thanks.

SQL replace duplicate record with value based on condition

I am trying to create a report that shows a single item(store_Product) purchased by store location(store_ID).
There are total 3 types of store_Product: product_A, product_B, and product_C.
There are over a thousand unique store_ID.
My issue is that some unique store_ID have multiple store_Products and I need to shrink the query so it returns only unique store_IDs
These are the rules of condition:
if a store has product_B and any other product, one unique store_ID record would have product_B as the store_Product value
if a store has product_A and product_C, one unique store_ID record would have product_A as the store_Product value
if a store has only one record then store_Product remains unchanged.
So the example below explains what I have on the left and what I want it to look like on the right:
I am using SQL server 2014 building the report in SSRS.
Really appreciate any help!
EDIT: What i have so far:
SELECT [store_ID]
,MIN(CASE WHEN store_product = 'product_B' THEN '1_product_B'
WHEN store_product = 'product_A' THEN '2_product_A'
ELSE '3_product_C' END) AS 'Prob_Group'
,CASE
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '24'
THEN '0 - 24 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '24'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '48'
THEN '24 - 48 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '48'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '72'
THEN '48 - 72 HRs'
ELSE 'Over 168 HRs'
END AS 'Hour_Range'
FROM myTable
WHERE *filters*
GROUP BY [store_ID]
,CASE
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '24'
THEN '0 - 24 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '24'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '48'
THEN '24 - 48 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '48'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '72'
THEN '48 - 72 HRs'
ELSE 'Over 168 HRs'
END
ORDER BY [store_ID]
I'm being very sneaky here:
SELECT store_id,
MIN( CASE WHEN store_Product = "product_B" THEN "1_product_B"
WHEN store_Product = "product_A" THEN "2_product_A"
ELSE "3_product_C"
END) as Product
FROM YourTable
GROUP BY store_id
So if the store has product_B, the result would be 1_product_B, not matter if are product_A and product_C.
Same if not product_B the minimum between 2_product_A and 3_product_C is 2_product_A also works if only has product_A.
After you have the result you can remove the first two characters
WITH cte as (
SELECT store_id,
MIN( CASE WHEN store_Product = "product_B" THEN "1_product_B"
WHEN store_Product = "product_A" THEN "2_product_A"
ELSE "3_product_C"
END) as Product
FROM yourTable
GROUP BY store_id
)
SELECT store_id, RIGHT(Product, LEN(Product) - 2) AS fixProductName
FROM CTE

SQL Server calculate percentage or decimal in complex expression

SELECT
person,
(CAST( /* I tried with and without CAST */
COUNT(`something`) / /* success count is numerator */
(COUNT(`something`) +
SUM(case when `something` is null then 1 else 0 end)) AS DECIMAL(3,1))
/* sum of success + failure counts is denominator. */
) success_ratio,
SUM(case when `something` is null then 1 else 0 end) fail_count,
COUNT(`something`) success_count
FROM
`table`
GROUP BY
person
ORDER BY
person
In my output, the success_count and fail_count both output integers.
When I first tried to create the fraction without CAST, I got mostly 0, with 1 a few times. I tried CASTing, but now it's just 0.0 and 1.1.
What am I doing wrong?
I think your issue is you are casting the value of the integer division rather than the the individual parts
You can use a subquery to make things a little easier to read, and by using 1.0 or 0.0 in your case statement you can force the fields to be floating point
SELECT person,
success_count / ( success_count + fail_count) success_rate,
fail_count, success_count
FROM (
SELECT person,
(
SUM(case when `something` is null then 1.0 else 0.0 end) fail_count,
SUM(case when `something` is null then 1.0 else 0.0 end) success_count
FROM `table`
GROUP BY person
) x
ORDER BY person

Resources