Best way to find number of account within certain value ranges - sql

I am having trouble what would be the best/most efficient way to find all accounts that fall within a certain value range without having 20 different selects.
So for example I need to find the following:
Account Value Number of plans average value
$100-$10000
$11000-$15000
$16000-$20000
$21000-$30000
$30000+
So right now my query is basically this for every value range:
SELECT COUNT (acct) AS 'Number of Plans'
, AVG (Value) AS 'Average Value'
FROM #RT1
WHERE Value
BETWEEN 0 AND 249999.99
AND InstaCode = 'S'
and there are three different charts that needs to be populated in SSRS. The only way I can figure it out is writing 15 different select statements but i feel there should be an easier and more effective way to do this.
Thanks!

I like to use cross apply for this:
SELECT v.grp, COUNT(acct) AS num_plans, AVG(value) as avg_value
FROM #RT1 t CROSS APPLY
(VALUES (CASE WHEN value >= 100 and value < 10000 THEN '$100-$10000'
WHEN value < 15000 THEN '$11000-$15000'
WHEN value < 20000 THEN '$16000-$20000'
WHEN value < 30000 THEN '$21000-$30000'
ELSE '$30000+'
END) as grp
) v(grp)
GROUP BY v.grp;
I'm not sure what InstaCode = 'S' has to do with the results. It is easy enough to add, either to the CASE expression or to a WHERE clause.

Use conditional aggregation for each group:
SELECT COUNT (case when Value
BETWEEN 0 AND 249999.99
then value else null end) AS 'Number of Plans group 1',
COUNT (case when Value
BETWEEN 2500000 AND 3000000
then value else null end) AS 'Number of Plans group 2',
AVG (case when Value
BETWEEN 0 AND 249999.99
then value else null end) AS 'Average Value 1st group', AVG (case when Value
BETWEEN 2500000 AND 3000000
then value else null end) AS 'Average Value 2nd group'...
from #RT1
where instacode='s'

SELECT
Case
When Value between 100 and 10000 Then '100 to 10000'
When Value between 11000 and 15000 Then '11000 to 15000'
When Value between 16000 and 20000 Then '16000 to 20000'
When Value between 21000 and 30000 Then '21000 to 30000'
When Value > 30000 Then '30000+'
End as AccountValue
COUNT (acct) AS NumberofPlans
, AVG (Value) AS AverageValue
FROM #RT1
WHERE Value
BETWEEN 0 AND 249999.99
AND InstaCode = 'S'
Group by
Case
When Value between 100 and 10000 Then '100 to 10000'
When Value between 11000 and 15000 Then '11000 to 15000'
When Value between 16000 and 20000 Then '16000 to 20000'
When Value between 21000 and 30000 Then '21000 to 30000'
When Value > 30000 Then '30000+'
End

Related

Calculate rate based on condition using postgresql

I want to find the rate of negative and zero profits from a column. I tried to do it using aggregate and subquery but it doesn't seem to work as both method return 0 values.
The code is as follows
SELECT
COUNT(CASE WHEN profit < 0 THEN 1
END) AS negative_profits,
COUNT(CASE WHEN profit < 0 THEN 1
END) / COUNT(profit),
COUNT(CASE WHEN profit = 0 THEN 1
END) AS zero_profits,
COUNT(CASE WHEN profit = 0 THEN 1
END) / COUNT(profit)
FROM sales;
SELECT (SELECT COUNT(*)
FROM sales
WHERE profit <= 0)/COUNT(profit) AS n_negative_profit
FROM sales;
Both query return 0 in values
enter image description here
Avoid integer division, which truncates (like Adrian pointed out).
Also, simplify with an aggregate FILTER expression:
SELECT count(*) FILTER (WHERE profit <= 0)::float8
/ count(profit) AS n_negative_profit
FROM sales;
If profit is defined NOT NULL, or to divide by the total count either way, optimize further:
SELECT count(*) FILTER (WHERE profit <= 0)::float8
/ count(*) AS n_negative_profit
FROM sales;
See:
Aggregate columns with additional (distinct) filters
Because you are doing integer division per docs Math operators/functions.
numeric_type / numeric_type → numeric_type
Division (for integral types, division truncates the result towards zero)
So:
select 2/5;
0
You need to make one of the numbers float or numeric:
select 2/5::numeric;
0.40000000000000000000
and to make it cleaner round:
select round(2/5::numeric, 2);
0.40

How to make multiple aggregate function without break / divided the fields

Is it possible to make an aggregate function without break / divided the grouping fields? I make a query but it will divided into duplicate value in the first field, here is my query:
SELECT TOP 5 empname AS 'EMP Name',
SUM (CASE WHEN prod = 'P' THEN 1 ELSE 0 END) AS 'Count of Prod',
COUNT (prod) AS 'Total Account',
FORMAT (COALESCE (SUM (CASE WHEN prod = 'P' THEN 1 ELSE 0 END) / COUNT (prod), 0), 'P') AS '% Prod',
DATEDIFF(DAY, t_start, t_end) as 'Duration Trip'
FROM Sampletable
WHERE empname NOT IN ('NA') AND
empname IS NOT NULL AND
t_end IS NOT NULL
GROUP BY empname,
prod,
t_end,
t_start
ORDER BY [Count of Prod] DESC
My expected result:
Emp. Name
Count of Prod
Total Account
% Prod
Duration Trip
Emp.1
62
63
98,41%
30
Emp.2
45
48
93,75%
28
Emp.3
20
22
90,91%
25
Emp.4
20
24
83,33%
22
Emp.5
15
19
78,95%
20
Thank you in advance.
If you want one row per empname, then that should be the only column in the group by (or perhaps other columns that describe each employee without multiplying the number of rows).
That suggests something like this:
SELECT TOP 5 empname,
SUM(CASE WHEN prod = 'P' THEN 1 ELSE 0 END) AS prod_count,
COUNT prod) AS Total_Account,
FORMAT(AVG(CASE WHEN prod = 'P' THEN 1.0 ELSE 0 END), '%P') AS prod_ratio,
SUM(DATEDIFF(DAY, t_start, t_end)) as trip_duration
FROM Sampletable
WHERE empname NOT IN ('NA') AND
empname IS NOT NULL AND
t_end IS NOT NULL
GROUP BY empname
ORDER BY prod_count DESC;
Note some of the changes to the query:
The column aliases are simplified so no escape characters are needed.
The GROUP BY has only empname.
The logic for the proportion of products is simplified using AVG().
I assume that the trip duration should be a sum of the individual durations.
I did not remove it, but empname IS NOT NULL is redundant, because the NOT IN handles this.

Calculate average for specific values

I'd like to calculate the average of a column but I'm getting stuck because, if a value is within a certain range than only I'd like to include that.
So if I had the following values:
100, 150, 500, 450, 300, 750
Now, I want to include only the values which is greater than 400 as a part of average.
then the query would calculate the average of these values:
500, 450, 750
and the output will be (500+450+750)/3
I tried
1)
select avg(case when value > 400 then value else 0 end) avg_val
from test
This is giving output as (0+0+500+450+0+750)/6. This is what not I wanted!
2)
select SUM(case when value > 400 then value else 0 end) /
SUM(case when value > 400 then 1 else 0 end) avg_val
from test
this is throwing error saying divide by zero if there is no value greater than 400.
Can anybody help ? I am using PostgreSQL.
Thanks!
Well, AVG() doesn't calculate null values, so you can use your query and replace 0 with NULL:
select avg(case when value > 400 then value else null end) avg_val
from test
Which can be formatted without the else part as well, since the default of the else is NULL
select avg(case when value > 400 then value end) avg_val
from test
Simply:
select avg(value)
from test
where value > 400
SELECT AVG(Price) AS PriceAverage FROM Products where price between 10 and 30;
SELECT AVG(Price) AS PriceAverage FROM bidding where price >=2500 ;

Count the number of Purchase Orders that occur within a Range

I have one table (order_lines) that contains (order_lines_order_header_id) which is the PO Number and I have (order_lines.accounting_total) which is the value of the specific PO line.
I need to group the sum of each 'order_header_id' into four ranges. The first range is Purchase Orders under $500. The second is Purchase Orders between $501 and $1000. The third range between $1001 and $10,000. The forth is all POs over $10,000.
I need the results to look like this:
Count of POs under $500 -- ####
Count of POs over $501 Under $1000-- ####
Count of POs over $1,001 Under $10,000 --####
Count of POs over $10,000-- ####
Here's what I have so far but it's not working:
SELECT COUNT(order_lines.order_header_id) where SUM(order_lines.accounting_total) <= 500 as Orders_Under_500
From order_lines
Any ideas?
First, you need a subquery to calculate the total amount per order. Then you need another query to get the counts you are looking for:
select (case when total < 500 then 'Less than $500'
when total < 1000 then 'Between $500 and $1,000'
when total < 10000 then 'Between $1,000 and $10,000'
else 'Over $10,000'
end) as grp,
count(*) as Numorders
from (select ol.order_header_id, sum(accounting_total) as total
from order_lines ol
group by ol.order_header_id
) ol
group by (case when total < 500 then 'Less than $500'
when total < 1000 then 'Between $500 and $1,000'
when total < 10000 then 'Between $1,000 and $10,000'
else 'Over $10,000'
end);
Your query is not in order, the correct form would be:
SELECT COUNT(order_header_id)
FROM order_lines
WHERE SUM(accounting_total) <= 500
However, a different query is needed to select all that you need in one query:
SELECT
SUM(CASE WHEN accounting_total <500 THEN 1 ELSE 0 END) AS `500`,
SUM(CASE WHEN accounting_total BETWEEN 500 AND 1000 THEN 1 ELSE 0 END) AS `500-1000`
SUM(CASE WHEN accounting_total BETWEEN 1000 AND 10000 THEN 1 ELSE 0 END) AS `1000-10000`
SUM(CASE WHEN accounting_total >10000 THEN 1 ELSE 0 END) AS `10000`
FROM order_lines
Alternatively (not tested):
SELECT COUNT(order_header_id)
FROM order_lines
WHERE SUM(accounting_total) <= 500
OR (SUM(accounting_total) > 500 AND SUM(accounting_total) <= 1000 )
OR (SUM(accounting_total) > 1000 AND SUM(accounting_total) <= 10000 )
OR SUM(accounting_total) > 10000
GROUP BY COUNT(order_header_id)
Hope that helped.

SQL - Can I calculate a new column based on another new one?

I'm creating a query in which there is a new column calculated similar to the following:
CASE WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND
turnover >= 3000 THEN '500'
WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND
turnover < 3000 THEN '200'
ELSE '0' END as OfferAmountEuro,
This works fine. I now want to create another calculated field on the same table which uses the 'OfferAmountEuro' field in it's calculation. I tried something like this:
CASE WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND
turnover >= 3000 THEN '500'
WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND
turnover < 3000 THEN '200'
ELSE '0' END as OfferAmountEuro,
CASE WHEN OfferAmountEuro = 500 AND Currency ='USD' then '600'
WHEN OfferAmountEuro = 200 AND Currency ='USD' then '250'
ELSE '0' END as OfferAmountLocal
But I get an error stating that 'OfferAmountEuro' is an invalid column name. I assume this means that I can't use the newly calculated 'OfferAmountEuro' field in the calculation for 'OfferAmountLocal'?
The actual calculations for the 'OfferAmountEuro' field are much more complex and numerous than the above in reality and I'd rather not repeat each of these calcs for the 'OfferAmountLocal' field.
Does anybody have any suggestions for a quick way to use this 'OfferAmountEuro' field in the calcs for another new field?
Your column is not recognized, because of natural sql statement execution order. Read more here:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/70efeffe-76b9-4b7e-b4a1-ba53f5d21916/order-of-execution-of-sql-queries
If you're using SQL Server, you can use CROSS APPLY as a workaround.
SELECT CASE
WHEN D.OfferAmountEuro = 500 AND T.Currency ='USD' then '600'
WHEN D.OfferAmountEuro = 200 AND T.Currency ='USD' then '250'
ELSE '0'
END AS OfferAmountLocal
FROM YourTable AS T
CROSS APPLY (
SELECT CASE
WHEN DATEDIFF(DAY, T.LastPurchase, GETDATE()) < 31 AND T.TurnOver >= 3000 THEN '500'
WHEN DATEDIFF(DAY, T.LastPurchase, GETDATE()) < 31 AND T.TurnOver < 3000 THEN '200'
ELSE '0'
END
) AS D(OfferAmountEuro)
You can either put the original query in a WITH statement and reference it in a second query, or replace all of the logic in your first computed column anywhere you want to use it.
Something like:
WITH C1 AS (
SELECT *,
CASE WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND turnover >= 3000 THEN '500'
WHEN DATEDIFF(day,lastpurchase,getdate())< 31 AND turnover < 3000 THEN '200'
ELSE '0' END as OfferAmountEuro
FROM myTable
)
SELECT *,
CASE WHEN OfferAmountEuro = 500 AND Currency ='USD' then '600'
WHEN OfferAmountEuro = 200 AND Currency ='USD' then '250'
ELSE '0' END as OfferAmountLocal
FROM C1