Dividing 2 numbers returns 0 [duplicate] - sql

This question already has answers here:
Division of integers returns 0
(2 answers)
Closed 10 months ago.
I'm trying to divide 2 counts in order to return a percentage.
The following query is returning 0:
select (
(select COUNT(*) from saxref..AuthCycle
where endOfUse is null and addDate >= '1/1/2014') /
(select COUNT(*) from saxref..AuthCycle
where addDate >= '1/1/2014')
) as Percentage
Should I be applying a cast?

It can be done more succinctly by moving the common condition to the where clause:
select sum(case when endOfUse is null then 1 end) * 100.0 / count(*) percentage
from saxref..AuthCycle
where addDate >= '1/1/2014'
Note how you don't need the case of 0 for false either, since nulls are ignored with sum()

The issue is caused because you are dividing 2 int values, which by default will output an int as it takes the data types used in the calculation to determine the data type of the output, so effectively if you do this:
select 50/100 as result
You get 0.5 output as 0 as it rounds it to an int (no decimal places).
If you however specify decimals:
select 50.0/100.0 as result
You would get 0.5 as a decimal, which you could multiply by 100 to get 50%.
So updating your syntax to multiply by 1.0 and making the counts into decimals would give you the correct result:
select (
(select COUNT(*) from saxref..AuthCycle where endOfUse is null and addDate >= '1/1/2014')*1.0 /
(select COUNT(*) from saxref..AuthCycle where addDate >= '1/1/2014')*1.0
) as Percentage

I would do it differently, using two sums:
select sum
( case
when endOfUse is null and addDate >= '1/1/2014'
then 1
else 0
end
)
* 100.0 -- if you want the usual 0..100 range for percentages
/
sum
( case
when addDate >= '1/1/2014'
then 1
else 0
end
)
percentage
from saxref..AuthCycle

Related

Trying to divide sum/ count of date by each step in SQL - Grouping Expression Sequence is Empty

I am having an issue with this SQL code. I am trying to divide sum/ by the count of a date. I keep getting the grouping expressions sequence is empty, and 'TableBlank.rep_date' is not an aggregate function error. I have tried with ORDER BY and GROUP BY statements and I'm still getting the error.
SELECT
rep_date,
1 AS AssignRank,
StepCompSum,
'StepCompA' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompA'
UNION
SELECT
rep_date,
2 AS AssignRank,
StepCompSum,
'StepCompB' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompB'
UNION
SELECT
rep_date,
3 AS AssignRank,
StepCompSum,
'StepCompC' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompC'
GROUP BY
rep_date,
StepCompSum
Difficult to know without sample data, but this might work:
SELECT
rep_date
, 2 AS AssignRank
, StepCompSum
, FunCat
, CAST(StepCompSum / (COUNT(rep_date) OVER (PARTITION BY FunCat)) AS double) AS ParticRate
FROM TableBlank
Although, I suspect what you are wanting from the CAST is to get the result to not be an integer. Here's what you'll get:
2 / 3 = 1 (because, integer math)
cast(1 as double) = 1.000
when you probably expect 0.667
You need to either cast the values first, or just include a value of the appropriate type in the expression:
StepCompSum * 1.0 / (COUNT(rep_date) OVER (PARTITION BY FunCat))

SQL - Calculate percentage by group, for multiple groups

I have a table in GBQ in the following format :
UserId Orders Month
XDT 23 1
XDT 0 4
FKR 3 6
GHR 23 4
... ... ...
It shows the number of orders per user and month.
I want to calculate the percentage of users who have orders, I did it as following :
SELECT
HasOrders,
ROUND(COUNT(*) * 100 / CAST( SUM(COUNT(*)) OVER () AS float64), 2) Parts
FROM (
SELECT
*,
CASE WHEN Orders = 0 THEN 0 ELSE 1 END AS HasOrders
FROM `Table` )
GROUP BY
HasOrders
ORDER BY
Parts
It gives me the following result:
HasOrders Parts
0 35
1 65
I need to calculate the percentage of users who have orders, by month, in a way that every month = 100%
Currently to do this I execute the query once per month, which is not practical :
SELECT
HasOrders,
ROUND(COUNT(*) * 100 / CAST( SUM(COUNT(*)) OVER () AS float64), 2) Parts
FROM (
SELECT
*,
CASE WHEN Orders = 0 THEN 0 ELSE 1 END AS HasOrders
FROM `Table` )
WHERE Month = 1
GROUP BY
HasOrders
ORDER BY
Parts
Is there a way execute a query once and have this result ?
HasOrders Parts Month
0 25 1
1 75 1
0 45 2
1 55 2
... ... ...
SELECT
SIGN(Orders),
ROUND(COUNT(*) * 100.000 / SUM(COUNT(*), 2) OVER (PARTITION BY Month)) AS Parts,
Month
FROM T
GROUP BY Month, SIGN(Orders)
ORDER BY Month, SIGN(Orders)
Demo on Postgres:
https://dbfiddle.uk/?rdbms=postgres_10&fiddle=4cd2d1455673469c2dfc060eccea8020
You've stated that it's important for the total to be 100% so you might consider rounding down in the case of no orders and rounding up in the case of has orders for those scenarios where the percentages falls precisely on an odd multiple of 0.5%. Or perhaps rounding toward even or round smallest down would be better options:
WITH DATA AS (
SELECT SIGN(Orders) AS HasOrders, Month,
COUNT(*) * 10000.000 / SUM(COUNT(*)) OVER (PARTITION BY Month) AS PartsPercent
FROM T
GROUP BY Month, SIGN(Orders)
ORDER BY Month, SIGN(Orders)
)
select HasOrders, Month, PartsPercent,
PartsPercent - TRUNCATE(PartsPercent) AS Fraction,
CASE WHEN HasOrders = 0
THEN FLOOR(PartsPercent) ELSE CEILING(PartsPercent)
END AS PartsRound0Down,
CASE WHEN PartsPercent - TRUNCATE(PartsPercent) = 0.5
AND MOD(TRUNCATE(PartsPercent), 2) = 0
THEN FLOOR(PartsPercent) ELSE ROUND(PartsPercent) -- halfway up
END AS PartsRoundTowardEven,
CASE WHEN PartsPercent - TRUNCATE(PartsPercent) = 0.5 AND PartsPercent < 50
THEN FLOOR(PartsPercent) ELSE ROUND(PartsPercent) -- halfway up
END AS PartsSmallestTowardZero
from DATA
It's usually not advisable to test floating-point values for equality and I don't know how BigQuery's float64 will work with the comparison against 0.5. One half is nevertheless representable in binary. See these in a case where the breakout is 101 vs 99. I don't have immediate access to BigQuery so be aware that Postgres's rounding behavior is different:
https://dbfiddle.uk/?rdbms=postgres_10&fiddle=c8237e272427a0d1114c3d8056a01a09
Consider below approach
select hasOrders, round(100 * parts, 2) as parts, month from (
select month,
countif(orders = 0) / count(*) `0`,
countif(orders > 0) / count(*) `1`,
from your_table
group by month
)
unpivot (parts for hasOrders in (`0`, `1`))
with output like below

Snowflake SQL UDFs: SELECT TOP N, LIMIT, ROW_NUMBER() AND RANK() not working in subqueries?

I've been experimenting with Snowflake SQL UDF solutions to add a desired number of working days to a timestamp. I've been tryinig to define a function that takes a timestamp and desired number of working days to add as parameters and returns a date. The function uses date dimension table. The function works when I pass it a single date as a parameter, but whenever I try to give it a full column of dates, it throws error "Unsupported subquery type cannot be evaluated". It seems that this happens whenever I try to use SELECT TOP N, LIMIT, ROW_NUMBER() or RANK() in a subquery.
Here is an example of an approach I tried:
CREATE OR REPLACE FUNCTION "ADDWORKINGDAYSTOWORKINGDAY"(STARTDATE TIMESTAMP_NTZ, DAYS NUMBER)
RETURNS DATE
LANGUAGE SQL
AS '
WITH CTE AS (
SELECT PAIVA
FROM EDW_DEV.REPORTING_SCHEMA."D_PAIVA"
WHERE ARKIPAIVA = 1 AND ARKIPYHA_FI = FALSE
AND 1 = CASE WHEN DAYS < 0 AND P.PAIVA < TO_DATE(STARTDATE) THEN 1
WHEN DAYS < 0 AND P.PAIVA >= TO_DATE(STARTDATE) THEN 0
WHEN DAYS >= 0 AND P.PAIVA > TO_DATE(STARTDATE) THEN 1
ELSE 0
END),
CTE2 AS (
SELECT
PAIVA
,CASE WHEN DAYS >= 0 THEN RANK() OVER
(ORDER BY PAIVA)
ELSE RANK() OVER
(ORDER BY PAIVA DESC)
END AS RANK
FROM CTE
ORDER BY RANK)
SELECT TOP 1
ANY_VALUE (CASE WHEN DAYS IS NULL OR TO_DATE(STARTDATE) IS NULL THEN NULL
WHEN DAYS = 0 THEN TO_DATE(STARTDATE)
ELSE PAIVA
END) AS PAIVA
FROM CTE2
WHERE CASE WHEN DAYS IS NULL OR TO_DATE(STARTDATE) IS NULL THEN 1 = 1
WHEN DAYS > 0 THEN RANK = DAYS
WHEN DAYS = 0 THEN 1 = 1
ELSE RANK = -DAYS
END
';
UDFs are scalar. They will return only one value of a specified type, in this case a date. If you want to return a set of values for a column, you may want to investigate UDTFs, User Defined Table Functions, which return a table.
https://docs.snowflake.net/manuals/sql-reference/udf-table-functions.html
With a bit of modification to your UDF, you can convert it to a UDTF. You can pass it columns instead of scalar values. You can then join the table resulting from the UDTF with the base table to get the work day addition values.

How to Group By in SQL Server Query

I'm using this query to get the Sum of SaleAmount for each type (SOType) of Sale Invoices.
I am getting the result but the result is not grouped by SOType. Have tried to use Group by Outside the query after where condition but getting an error as
"Column 'SaleInvoices.InvoiceID' is invalid because it is not
contained in either aggregate or group by function".
DECLARE #fromDate Datetime = '2019/05/23'
DECLARE #toDate Datetime = '2019/10/25'
DECLARE #isKpi int = '1'
SELECT (
(Select Sum((Isnull(I.Quantity,0)*Isnull(I.SalePrice,0))+((Isnull(I.Quantity,0)*Isnull(I.SalePrice,0) - I.Discount) *(I.TAX/100)))
from ItemsSold as I
where I.InvoiceId= S.InvoiceID and I.InvoiceType='Sale Invoice'
) -
(Select isnull(Sum((Isnull(I.Quantity,0)*Isnull(I.SalePrice,0))+((Isnull(I.Quantity,0)*Isnull(I.SalePrice,0) - I.Discount)*(I.TAX/100))),0)
from ItemsSold as I
where I.InvoiceId= S.InvoiceID and I.InvoiceType='Sale Return'
)) as Total
,S.SOType as SOType
FROM SaleInvoices AS S
where S.OrderDate>=Convert(VARCHAR,#fromDate,111) and S.OrderDate<=Convert(varchar,#toDate,111)
You want conditional aggregation. The logic should look something like this:
select s.SOType,
sum(case when i.invoicetype = 'Sale Invoice'
then (I.Quantity * I.SalePrice) * (1 - i.discount) * i.tax / 100.0
when i.invoicetype = 'Sale Return'
then - (I.Quantity * I.SalePrice) * (1 - i.discount) * i.tax / 100.0
end) as Total
from SaleInvoices s join
ItemsSold i
on i.InvoiceId= s.InvoiceID
where s.OrderDate >= #fromDate and
s.OrderDate <= #toDate
group by s.SOType ;
I'm not sure I got the arithmetic correct.
Notes:
The group by clause defines the rows being returned by the query. If you want one row per SOType then you want to GROUP BY SOType.
Use date comparisons and functions for dates. It is absurd to convert a date to a string to compare to a date.
You probably don't need COALESCE() or ISNULL() to handle NULL values. These are generally ignored by aggregation functions.

Set negative cumulative sum to zero Can someone advise the best approach to do this? [duplicate]

This question already has an answer here:
Return zero if value less than zero [duplicate]
(1 answer)
Closed 4 years ago.
I have table with a amount column which has both negative and positive values.I want to calculate the sum of values and set negative cumulative sum to zero.
date amount actu_cusum exp_cusum
22-06-2018 60.626 60.626 60.626
29-06-2018 -78.309 -17.683 0
02-07-2018 -0.824 -18.507 0
09-07-2018 -0.822 -19.329 0
10-07-2018 14.79 -4.539 14.79
29-07-2018 20 15.461 34.79
30-07-2018 -30 -24.539 4.79
31-07-2018 15 -9.539 19.79
update a set a.calamount= z.cusum
from #temp a,
( select CustomerCode, date ,amount,
SUM(totaumamount) over ( partition by CustomerCode order by date )
as cusum from #temp
) z
where a.customercode = z.customercode and a.date = z.date
(You are missing columns and tables in your question)
You can use:
... set a.calamount = case when z.cusum < 0 then 0 else z.cusum end ...
EDIT: For individual negative values:
update a set a.calamount= z.cusum
from #temp a,
( select CustomerCode, date ,amount,
SUM(case when totaumamount < 0 then 0 else totaumamount end) over ( partition by CustomerCode order by date )
as cusum from #temp
) z
where a.customercode = z.customercode and a.date = z.date