SQL Server Rounding Issue Looking for Explanation - sql

I've solved this issue but I'm just wondering why this works the way it does. I have a temporary table I am selecting from and am looking to display a a name, the number of records that match this name, and the percentage for that name of the total records. This is the way I originally had it:
SELECT name, number,
CASE WHEN number = 0 THEN 0 ELSE
convert(Numeric(10,2), number / CONVERT(decimal(5,2),SUM(number)) * 100)
END as "Percentage of Total"
FROM #names
group by name, number
The results I received were:
name number Percentage of Total
------------------------- ----------- ---------------------------------------
Test 1 0 0.00
Test 2 22 100.00
Test 3 28 100.00
When I change the query to this, the results are correct:
declare #total decimal(5,2)
select #total = SUM(number) FROM #names
SELECT name, number, convert(Numeric(10,2), number/ #total * 100) as "Percentage of Total"
FROM #names
group by name, number
Correct Results:
name number Percentage of Total
------------------------- ----------- ---------------------------------------
Test 1 22 44.00
Test 2 0 0.00
Test 3 28 56.00
Can someone explain what is going on, I would like to understand this better. Thanks!
Jon

You first query groups by number.
Since you don't have duplicates of numbers, number / SUM(number) is equivalent to the 1 / COUNT (except when the number is 0).
You second query does not group by number, it calculates total sum.
Use this instead:
SELECT name, number * 100.0 / SUM(number) OVER ()
FROM #names
When used with OVER clause, SUM becomes the analytical function rather than the aggregate one.
It does not shrink several records into one: instead, it returns the total value along with each record:
-- This is an aggregate function. It shrinks all records into one record and returns the total sum
WITH q (name, number) AS
(
SELECT 'test1', 0
UNION ALL
SELECT 'test2', 22
UNION ALL
SELECT 'test3', 28
)
SELECT SUM(number)
FROM q
--
50
-- This is an analytical function. It calcuates the total sum as well but does not shrink the records.
WITH q (name, number) AS
(
SELECT 'test1', 0
UNION ALL
SELECT 'test2', 22
UNION ALL
SELECT 'test3', 28
)
SELECT SUM(number) OVER ()
FROM q
--
50
50
50

Related

SQL making column with % of total

I am making a table with amount of transactions from other banks.
First row will be the total one. First column with absolute numbers, second with % of the total amount and third, last column, will be with name of the senders bank.
eg:
TRN
%
BANK
8000
100%
ALL
4000
50%
BANK1
2000
25%
BANK2
2000
25%
BANK3
I have trouble getting the % column as in my script the data are groupped already.
SELECT COUNT(*)
,'100%' as %,
,'ALL' as BANK
FROM table A
UNION ALL
SELECT COUNT(*)
,**IDK**
,BANK_ID BANK
FROM TABLE2 B
GROUP BY A.BANK_ID
ORDER BY 1 DESC
I know that there should be possibility with the over partition, but I can't figure out how.
Thanks for any tips!
Hi I think this should do the trick.
You can use the over partition with specifying anything in the brackets.
SELECT COUNT(*)
,'100%' as '%',
,'ALL' as BANK
UNION ALL
SELECT COUNT(*)
,cast(count(*)*100/count(*) over () as varchar) +'%'
,BANK_ID BANK
FROM TABLE2 B
GROUP BY B.BANK_ID
ORDER BY 1 DESC
Sample data:
create table demo (trn number, bank_id varchar2(10));
insert all
into demo values (1000, 'BANK1')
into demo values (1000, 'BANK1')
into demo values (1000, 'BANK1')
into demo values (1000, 'BANK1')
into demo values (1000, 'BANK2')
into demo values (1000, 'BANK2')
into demo values (1000, 'BANK3')
into demo values (1000, 'BANK3')
select * from dual;
Query:
select sum(trn)
, 100 * ratio_to_report(sum(trn)) over () * 2 as percent
, nvl(bank_id,'ALL') as bank
from demo
group by rollup (bank_id);
SUM(TRN) PERCENT BANK
---------- ---------- ----------
4000 50 BANK1
2000 25 BANK2
2000 25 BANK3
8000 100 ALL
group by rollup() generates a total row.
ratio_to_report() returns a value between 0 and 1, so I multiply by 100 to present it as a percentage.
The * 2 is because ratio_to_report() includes the whole column including the rollup total, so every value is halved.
I've assumed bank_id cannot be null and so a null value in the report must be the total row. If you need to distinguish between the total row and other null values, you can use grouping(bank_id) in a case expression, and it will return 1 for the total row and 0 for the rest. (You could also use this in an order by clause if you want to display the total row first.)
I am not overly familiar with Oracle syntax so the below may need some adjustments, but it should be portable enough. This is in MS SQL. Apologies for not being able to provide you a solution in the direct syntax you need. If someone is more familiar, please feel free to edit.
DECLARE #totalCount BIGINT = (SELECT Count(*) FROM table);
SELECT
#TotalCount AS [Count],
FORMAT(1, 'P') AS [%],
'ALL' AS [BANK]
--removed table here as the only derived value is the count and we already have it in #totalCount
UNION ALL
SELECT
COUNT(*) AS [Count],
FORMAT(CAST(Count(*) AS DECIMAL) / #TotalCount,'P') AS [%],
BANK_ID AS [BANK]
FROM [tableName]
GROUP BY [tableName].BANK_ID
--ORDER BY 1
I got to this solution and it works thankfully,
with alll as
(
SELECT
COUNT(*) trn
,'100%' AS prcnt
,'ALL' AS BANK_ID
FROM table
)
, bank as
(
SELECT distinct
count(1) over (partition by BANK_ID) cnt_B
,to_char(round(count(1) over (partition by BANK_ID)/count(*) over (partition by 1),3)*100) || '%' as prcnt
,BANK_ID
FROM table
)
select * from alll
UNION ALL
select * from bank
The MODEL clause could be used for this:
-- S a m p l e d a t a
WITH
tbl (BANK_ID, TRN) AS
(
Select 'Bank 1', 500 From Dual Union All
Select 'Bank 1', 3500 From Dual Union All
Select 'Bank 2', 1200 From Dual Union All
Select 'Bank 2', 800 From Dual Union All
Select 'Bank 3', 2000 From Dual
)
-- M a i n S Q L
SELECT TRN, PCT, BANK_ID
FROM ( SELECT BANK_ID "BANK_ID", Sum(TRN) "TRN"
FROM tbl
GROUP BY BANK_ID
ORDER BY BANK_ID
)
MODEL Dimension By(BANK_ID )
Measures(TRN, 0 as PCT)
RULES
( TRN['ALL'] = Sum(TRN)[BANK_ID != 'ALL'],
TRN[ANY] = Sum(TRN)[CV()],
PCT['ALL'] = 100,
PCT[ANY] = Sum(TRN)[CV()] * 100 / Sum(TRN)[BANK_ID != 'ALL']
)
ORDER BY BANK_ID
-- R e s u l t
TRN PCT BANK_ID
---------- ---------- -------
8000 100 ALL
4000 50 Bank 1
2000 25 Bank 2
2000 25 Bank 3

I need a support for sql query

I have a table with 3 columns lower range,upper range, discount amount
I am passing a number and finding in which range it belongs to and retrieving its discount amount
But i am passing a number that not in this table in this case i need the last range discount amount from table
I need a sql query for the same
0-10 100
11-20 200
21-30 300
if i am passing 5 need to get 100
if i am passing 15 200 as result
but if i am passing 50 i need to get 300 as result
Ie. If the value that is passing not in the range need to get the highest ranges discount amount.
Plzz help. Mee
Try this. You can directly pass/use #value in the script as well.
DECLARE #Value INT
SET #Value = 35
SELECT SUM(DISCOUNT) Discount
FROM
(
SELECT
CASE
WHEN upper_range = (SELECT MAX(upper_range) FROM your_table) AND #Value > upper_range THEN DISCOUNT
WHEN #Value BETWEEN lower_range AND upper_range THEN DISCOUNT
ELSE 0
END
DISCOUNT
FROM your_table
) A
Output for value 35 is-
300
With UNION ALL:
select discount
from tablename
where x between lowerrange and upperrange
union all
select max(discount) from tablename
where not exists (
select 1 from tablename
where x between lowerrange and upperrange
)
If the 1st query does not return a result, then the value will be fetched by the 2nd query.
If the 1st query returns a result, then the 2nd will not return anything.
Applies to any major rdbms.

hoe to make sum of one sql table column with ignoring duplicate values?

quoteId price
1 50
1 50
2 10
3 40
3 40
3 40
4 10
In this table I always get the same price for each quoteId.
Example: quoteId = 1 has multiple entries with the price of 50.
By using SUM I get the total sum of the price column which is:50 + 50 + 10 + 40 + 40 + 40 + 10 = 240
However, I only want to sum the unique price for quoteId which is:
50+10+40+10 = 110
How can I approch this?
Another option is DISTINCT
Example
Select MyTotal = sum(price)
from (Select Distinct quoteId,price From YourTable) A
Returns
MyTotal
110
Following query will work:
select sum(price)
from yourTablename
group by quoteId,price;
You need a nested query to compute an intermediate value by quoteId using avg ( or max or min with your data)
But you need know why you have duplicate value by quotedId, may be you have a mistake before.
select sum(price) from (
select
quoteId,
avg(price) price,
from
your_table
group by
quoteId
) as x
This query is compliant with ISO standard SQL and will works with several database engine

Two select count queries and then calculate percentage

I'd like to combine two queries
SELECT COUNT(*) FROM abc;
SELECT COUNT(Status) FROM ABC WHERE Status='Active';
And then calculate the percentage (by taking the 2nd query divided by first query). I'd like to achieve this in one single query. What i've attempted so far:
SELECT COUNT(*) AS A FROM abc
UNION
SELECT COUNT(Status) AS B FROM ABC WHERE Status='Active';
UNION
SELECT(COUNT(Status)*100/SELECT COUNT(*) FROM abc)) AS %ofAB FROM abc WHERE Status='Active'
What I get:
A
--
31
36
86,11111111
What I want:
A | B | %ofAB
---------------------
36 | 31 | 86,1111111%
This should give you what you want:
SELECT
COUNT(*) AS TotalCount,
SUM(IIF(Status = 'Active', 1, 0)) AS ActiveCount,
ROUND((SUM(IIF(Status = 'Active', 1, 0)) * 100/ COUNT(*)),2) AS PctActive
FROM
Abc
EDIT: Didn't notice that this was for Access. I don't know if CAST is available in Access, so you may need to use an equivalent function to make sure that the integers don't simply yield 1 or 0. It's possible that Access will convert a division into a decimal automatically, but in SQL Server it does not.

How do I aggregate numbers from a string column in SQL

I am dealing with a poorly designed database column which has values like this
ID cid Score
1 1 3 out of 3
2 1 1 out of 5
3 2 3 out of 6
4 3 7 out of 10
I want the aggregate sum and percentage of Score column grouped on cid like this
cid sum percentage
1 4 out of 8 50
2 3 out of 6 50
3 7 out of 10 70
How do I do this?
You can try this way :
select
t.cid
, cast(sum(s.a) as varchar(5)) +
' out of ' +
cast(sum(s.b) as varchar(5)) as sum
, ((cast(sum(s.a) as decimal))/sum(s.b))*100 as percentage
from MyTable t
inner join
(select
id
, cast(substring(score,0,2) as Int) a
, cast(substring(score,charindex('out of', score)+7,len(score)) as int) b
from MyTable
) s on s.id = t.id
group by t.cid
[SQLFiddle Demo]
Redesign the table, but on-the-fly as a CTE. Here's a solution that's not as short as you could make it, but that takes advantage of the handy SQL Server function PARSENAME. You may need to tweak the percentage calculation if you want to truncate rather than round, or if you want it to be a decimal value, not an int.
In this or most any solution, you have to count on the column values for Score to be in the very specific format you show. If you have the slightest doubt, you should run some other checks so you don't miss or misinterpret anything.
with
P(ID, cid, Score2Parse) as (
select
ID,
cid,
replace(Score,space(1),'.')
from scores
),
S(ID,cid,pts,tot) as (
select
ID,
cid,
cast(parsename(Score2Parse,4) as int),
cast(parsename(Score2Parse,1) as int)
from P
)
select
cid, cast(round(100e0*sum(pts)/sum(tot),0) as int) as percentage
from S
group by cid;