T-SQL average rounded to the closest integer - sql

I'm not sure if this has been asked before, but how do I get the average rounded to the closest integer in T-SQL?

This should do it. You might need a GROUP BY on the End depending on what you are looking for the average of.
SELECT CONVERT(int,ROUND(AVG(ColumnName),0))
FROM
TableName
EDIT: This question is more interesting than I first thought.
If we set up a dummy table like so...
WITH CTE
AS
(
SELECT 3 AS Rating
UNION SELECT 4
UNION SELECT 7
)
SELECT AVG(Rating)
FROM
CTE
We get an integer average of 4
However if we do this
WITH CTE
AS
(
SELECT 3.0 AS Rating
UNION SELECT 4.0
UNION SELECT 7.0
)
SELECT AVG(Rating)
FROM
CTE
We get a decimal average of 4.666..etc
So it looks like the way to go is
WITH CTE
AS
(
SELECT 3 AS Rating
UNION SELECT 4
UNION SELECT 7
)
SELECT CONVERT(int,ROUND(AVG(CONVERT(decimal,Rating)),0))
FROM CTE
Which will return an integer value of 5 which is what you are looking for.

If you are in SQL Server, just use round(avg(column * 1.0), 0).
The reason for * 1.0 is because sql server in some cases returns calculations using the same datatype of the values used in the calculation. So, if you calculate the average of 3, 4 and 4, the result is 3.66..., but the datatype of the result is integer, therefore the sql server will truncate 3.66... to 3, using * 1.0 implicit convert the input to a decimal.
Alternatively, you can convert or cast the values before the average calculation, like cast(column as decimal) instead of using the * 1.0 trick.
If your column it's not a integer column, you can remove the * 1.0.
PS: the result of round(avg(column * 1.0), 0) still is a decimal, you can explicit convert it using convert(int, round(avg(column * 1.0), 0), 0) or just let whatever language you are using do the job (it's a implicit conversion)

Select cast(AVG(columnname) as integer)

This worked for it:
CONVERT(int,ROUND(AVG(CAST(COLUMN-NAME AS DECIMAL)) ,0))
Isn't there a shorter way of doing it though?

T-SQL2018.
CAST(ROUND(COLUMN, 0) AS INT) This code does the job for me and gives the output I require so a 4.8 becomes 5.
whereas
CAST(AVG(COLUMN) AS INT) This code almost does the job but rounds down, so 4.8 becomes a 4 and not 5.

select cast(avg(a+.5) as int) from
(select 1 a union all select 2) b
If you don't like shortcuts, you could use the long way:
select round(avg(cast(a as real)), 0)
from (select 1 a union all select 2) b

The following statements are equivalent:
-- the original code
CONVERT(int, ROUND(AVG(CAST(mycolumn AS DECIMAL)) ,0))
-- using '1e0 * column' implicitly converts mycolumn value to float
CONVERT(int, ROUND(AVG(1e0 * mycolumn) ,0))
-- the conversion to INT already rounds the value
CONVERT(INT, AVG(1e0 * mycolumn))

On SQL 2014,
select round(94,-1)
select round(95,-1)

Related

How to SELECT / Query Whole Numbers only

Would you know If i can select only whole numbers?
I don't want to round off the values.
For example this is select * from Table_1 below
Numbers
Team
10.5
A
12.12
B
23
C
I would do like
select * from Table_1
where NUMBERS is ;
Expected output below
Numbers
Team
23
C
Thank you very much!
The function TRUNC() truncates a number without rounding:
SELECT *
FROM Table_1
WHERE TRUNC("Numbers") = "Numbers";
See the demo.
You can use the ROUND function to filter (rather than to display) the values:
SELECT *
FROM table_1
WHERE ROUND(numbers) = numbers;
You could also use the FLOOR, CEIL or TRUNC functions instead of ROUND.
Or, you could use the MOD function:
SELECT *
FROM table_1
WHERE MOD(numbers, 1) = 0;
(And you could apply a function-based index to MOD(number, 1) if you wanted to improve performance.)
Which, for the sample data:
CREATE TABLE table_1 (Numbers, Team) AS
SELECT 10.5, 'A' FROM DUAL UNION ALL
SELECT 12.12, 'B' FROM DUAL UNION ALL
SELECT 23, 'C' FROM DUAL;
All the options output:
NUMBERS
TEAM
23
C
db<>fiddle here

How can I Roundoff in SQL with a sum function

In My SQL Code I am trying to round the value to 2 decimal point with sum
select ((SUM(Round((CAST(PE.GstTotal as float) * PE.Quantity) / 2 ),2))) FROM [dbo].[PharmacyEntry] PE
But I am getting an error. Could someone correct me on this.
Error
It's sometimes helpful to vertically align all your parenthesis pairs to see where you've got one wrong:
select
(
(
SUM
(
Round
(
(
CAST
(
PE.GstTotal as float
)
*
PE.Quantity
)
/
2
),
2
)
)
)
FROM [dbo].[PharmacyEntry] PE
You're providing 2 as a second parameter to sum instead of round. Try this:
select SUM(Round((CAST(PE.GstTotal as float) * PE.Quantity) / 2 , 2))
FROM [dbo].[PharmacyEntry] PE

Get column sum and use to calculate percent of total, why doesn't work with CTEs

I did this following query, however it gave the the result of 0 for each orderStatusName, does anyone know where is the problem?
with tbl as (
select s.orderstatusName, c.orderStatusId,count(c.orderId) counts
from [dbo].[ci_orders] c left join
[dbo].[ci_orderStatus] s
on s.orderStatusId = c.orderStatusId
where orderedDate between '2018-10-01' and '2018-10-29'
group by orderStatusName, c.orderStatusId
)
select orderstatusName, counts/(select sum(counts) from tbl as PofTotal) from tbl
the result is :0
You're using what is known as integer math. When using 2 integers in SQL (Server) the return value is an integer as well. For example, 2 + 2 = 4, 5 * 5 = 25. The same applies to division 8 / 10 = 0. That's because 0.8 isn't an integer, but the return value will be one (so the decimal points are lost).
The common way to change this behaviour is to multiply one of the expressions by 1.0. For example:
counts/(select sum(counts) * 1.0 from tbl) as PofTotal
If you need more precision, you can increase the precision of the decimal value of 1.0 (i.e. to 1.000, 1.0000000, etc).
Use window functions and proper division:
select orderstatusName, counts * 1.0 / total_counts
from (select t.*, sum(counts) over () as total_counts
from tbl
) t;
The reason you are getting 0 is because SQL Server does integer division when the operands are integers. So, 1/2 = 0, not 0.5.

Why does this SQL query fail

The query below is failing unexpectedly with an arithmatic overflow error.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
"Arithmetic overflow error converting int to data type numeric."
Strangely if the query is modified to remove the NULL and replace it with the same value as in the null coalesce (5005), it runs without issue
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
Also, omitting the SELECT NULL line entirely allows the query to run without issue
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
If the coalesce value in the IsNull function is changed to an integer which is small enough to convert to the decimal in the subquery without widening, the query runs
select IsNull(t2.val, 500)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
Tested this in both SQL Server 2005 and SQL Server 2008.
Ordinarily combining integers with decimals is seamless and SQL Server will convert both the integer and the decimal into a decimal type large enough to accommodate both. But for some reason running a query where the cast occurrs from both the UNION and the IsNull, causes the cast to fail.
Does anyone know why this is?
Try doing this
select * into t2
from(
SELECT 336.6 as val
UNION ALL
SELECT NULL
) as x
If you now look at the columns, you see a numeric with numeric precision of 4 and scale of 1
select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T2'
SQL made that decision based on the smallest numeric precision to hold 336.6. Now, when you ask it to convert the NULL to 5005, you are saying, convert any NULL values to a number too big to fit in a numeric with the precision of 4 and a scale of 1. The error message indicates that 5005 won't fit in Numeric(4,1)
This will work because the table will now generate a larger numeric field, since SQL needs to accommodate 5005. Create the table using the new contents of T2 from below, and the field type should go to Numeric(5,1) allowing the 5005 to fit.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
When you run the statement without a NULL in your inner query, SQL never evaluates the 5005, so it doesn't reach a condition where it needs to put 5005 into a numeric(4,1) field.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
I think the problem is that when SQL Server resolves the union, it decides on a decimal type that is only large enough to fit 333.6 (which is decimal(4,1)). Trying to put 5005 into that results in an overflow.
You can get around that specifying the precision of decimal yourself:
select IsNull(t2.val, 5005)
from(
SELECT CONVERT(DECIMAL(5,1), 336.6) as val UNION ALL
SELECT NULL
) as t2
I believe its because your 336.6 value is being inferred to be of data type NUMERIC. If you want to be more specific, then explicity cast it to DECIMAL;
SELECT IsNull(t2.val, CAST(5005 AS DECIMAL))
FROM (
SELECT CAST(336.6 AS DECIMAL) AS val
UNION ALL
SELECT NULL
) AS t2

is there a PRODUCT function like there is a SUM function in Oracle SQL?

I have a coworker looking for this, and I don't recall ever running into anything like that.
Is there a reasonable technique that would let you simulate it?
SELECT PRODUCT(X)
FROM
(
SELECT 3 X FROM DUAL
UNION ALL
SELECT 5 X FROM DUAL
UNION ALL
SELECT 2 X FROM DUAL
)
would yield 30
select exp(sum(ln(col)))
from table;
edit:
if col always > 0
DECLARE #a int
SET #a = 1
-- re-assign #a for each row in the result
-- as what #a was before * the value in the row
SELECT #a = #a * amount
FROM theTable
There's a way to do string concat that is similiar:
DECLARE #b varchar(max)
SET #b = ""
SELECT #b = #b + CustomerName
FROM Customers
Here's another way to do it. This is definitely the longer way to do it but it was part of a fun project.
You've got to reach back to school for this one, lol. They key to remember here is that LOG is the inverse of Exponent.
LOG10(X*Y) = LOG10(X) + LOG10(Y)
or
ln(X*Y) = ln(X) + ln(Y) (ln = natural log, or simply Log base 10)
Example
If X=5 and Y=6
X * Y = 30
ln(5) + ln(6) = 3.4
ln(30) = 3.4
e^3.4 = 30, so does 5 x 6
EXP(3.4) = 30
So above, if 5 and 6 each occupied a row in the table, we take the natural log of each value, sum up the rows, then take the exponent of the sum to get 30.
Below is the code in a SQL statement for SQL Server. Some editing is likely required to make it run on Oracle. Hopefully it's not a big difference but I suspect at least the CASE statement isn't the same on Oracle. You'll notice some extra stuff in there to test if the sign of the row is negative.
CREATE TABLE DUAL (VAL INT NOT NULL)
INSERT DUAL VALUES (3)
INSERT DUAL VALUES (5)
INSERT DUAL VALUES (2)
SELECT
CASE SUM(CASE WHEN SIGN(VAL) = -1 THEN 1 ELSE 0 END) % 2
WHEN 1 THEN -1
ELSE 1
END
* CASE
WHEN SUM(VAL) = 0 THEN 0
WHEN SUM(VAL) IS NOT NULL THEN EXP(SUM(LOG(ABS(CASE WHEN SIGN(VAL) <> 0 THEN VAL END))))
ELSE NULL
END
* CASE MIN(ABS(VAL)) WHEN 0 THEN 0 ELSE 1 END
AS PRODUCT
FROM DUAL
The accepted answer by tuinstoel is correct, of course:
select exp(sum(ln(col)))
from table;
But notice that if col is of type NUMBER, you will find tremendous performance improvement when using BINARY_DOUBLE instead. Ideally, you would have a BINARY_DOUBLE column in your table, but if that's not possible, you can still cast col to BINARY_DOUBLE. I got a 100x improvement in a simple test that I documented here, for this cast:
select exp(sum(ln(cast(col as binary_double))))
from table;
Is there a reasonable technique that would let you simulate it?
One technique could be using LISTAGG to generate product_expression string and XMLTABLE + GETXMLTYPE to evaluate it:
WITH cte AS (
SELECT grp, LISTAGG(l, '*') AS product_expression
FROM t
GROUP BY grp
)
SELECT c.*, s.val AS product_value
FROM cte c
CROSS APPLY(
SELECT *
FROM XMLTABLE('/ROWSET/ROW/*'
PASSING dbms_xmlgen.getXMLType('SELECT ' || c.product_expression || ' FROM dual')
COLUMNS val NUMBER PATH '.')
) s;
db<>fiddle demo
Output:
+------+---------------------+---------------+
| GRP | PRODUCT_EXPRESSION | PRODUCT_VALUE |
+------+---------------------+---------------+
| b | 2*6 | 12 |
| a | 3*5*7 | 105 |
+------+---------------------+---------------+
More roboust version with handling single NULL value in the group:
WITH cte AS (
SELECT grp, LISTAGG(l, '*') AS product_expression
FROM t
GROUP BY grp
)
SELECT c.*, s.val AS product_value
FROM cte c
OUTER APPLY(
SELECT *
FROM XMLTABLE('/ROWSET/ROW/*'
passing dbms_xmlgen.getXMLType('SELECT ' || c.product_expression || ' FROM dual')
COLUMNS val NUMBER PATH '.')
WHERE c.product_expression IS NOT NULL
) s;
db<>fiddle demo
*CROSS/OUTER APPLY(Oracle 12c) is used for convenience and could be replaced with nested subqueries.
This approach could be used for generating different aggregation functions.
There are many different implmentations of "SQL". When you say "does sql have" are you referring to a specific ANSI version of SQL, or a vendor specific implementation. DavidB's answer is one that works in a few different environments I have tested but depending on your environment you could write or find a function exactly like what you are asking for. Say you were using Microsoft SQL Server 2005, then a possible solution would be to write a custom aggregator in .net code named PRODUCT which would allow your original query to work exactly as you have written it.
In c# you might have to do:
SELECT EXP(SUM(LOG([col])))
FROM table;