SUM Integer Values: Cast Before the Aggregate

I cast SUM integer values before the aggregate when the total can exceed int. Each input row can fit its type while their combined total cannot. The accumulator’s return type matters too.

Gouache painting: a leather sack tipped over a wooden chute pours golden grain into a narrow stoneware crock, which overflows across the bench
Water running from a terracotta spout along a stone trough into a basin.

Keep valid rows and a wider total

The first query supplies two amounts of 1.5 billion and one missing amount. Each known value fits int. Their expected total is three billion, which exceeds the positive int range.

The example casts each amount to bigint inside SUM. That gives the aggregate a bigint input and a bigint result. It avoids executing an intentionally overflowing int aggregate just to demonstrate the problem, while keeping the original input type explicit.

WITH Amounts AS
(
 SELECT CAST(Amount AS int) AS Amount
 FROM (VALUES (1500000000),(1500000000),(CAST(NULL AS int))) v(Amount)
)
SELECT COUNT(*) AS InputRows, COUNT(Amount) AS KnownRows,
       SUM(CAST(Amount AS bigint)) AS TotalAmount
FROM Amounts;

WITH Amounts AS
(
 SELECT CAST(Amount AS int) AS Amount
 FROM (VALUES (1500000000),(1500000000),(CAST(NULL AS int))) v(Amount)
)
SELECT COUNT(*) AS InputRows, SUM(CAST(Amount AS bigint)) AS TotalAmount
FROM Amounts
WHERE 1=0;
Native SSMS result grids for sum, widened input, including all returned rows and columns.
Widening before SUM retains the total 3000000000. The empty input returns InputRows = 0 and a NULL total. Open the result at full size.

Widen before accumulation

SUM(int) returns int and SUM(bigint) returns bigint. Casting the final result after SUM doesn’t change the type used during the earlier aggregation. The placement of the cast is therefore part of the solution.

I’d review the expression inside the aggregate rather than only its alias or destination type. A report column named LargeTotal can still use a narrow accumulator. Likewise, assigning that result to a wider consumer won’t repair an overflow that occurred before assignment.

Expose missing amounts alongside the total

InputRows counts three supplied rows, while KnownRows counts two non-NULL amounts. SUM ignores the missing amount and adds the two known values. Those counts explain the coverage of the resulting total.

I don’t replace every missing amount with zero automatically. An unknown amount can represent incomplete data rather than a confirmed zero. Keep the total and its coverage separate. A reviewer can then distinguish a complete calculation from a partial one.

Read the empty set deliberately

The second query filters out every input row. Its expected row count is zero and its total is NULL. An aggregate without GROUP BY still returns one row here, even though no source rows remain.

I’d keep that case when a consumer expects a numeric total. A zero default can be useful for display, but it changes the missing-result contract. Add that default deliberately at the appropriate boundary. An empty set doesn’t prove that known amounts sum to zero.

Review arithmetic before SUM as well

The cast protects this direct amount expression. If the aggregate instead sums a product, the multiplication’s type needs review before aggregation. Two int factors can overflow in their product before a later SUM sees the value.

I’d widen an operand early enough for that calculation too. A wide accumulator doesn’t make every preceding expression wide. Keeping the full expression visible helps identify where each intermediate value is produced and which type governs its available range.

Safe SUM checklist

Match the final contract to the requirement

bigint has a larger range, but it isn’t unlimited. The application’s allowed amounts and maximum row count still need a documented bound. Fractional money or quantities also need an appropriate exact decimal type instead of integer widening alone.

These SELECT statements create no objects and change no data. Their complete expected outputs check the type placement and the empty-set contract. I’d retain the exact inputs and actual results when validating the expression against the target SQL Server instance.

Run it on your own numbers and see where the total stops fitting.

A wider destination is not a wider accumulator, it is the input cast that changes SUM’s return type.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Datatype, SQL Function, SQL Scripts
Previous Post
SQL SERVER – Easy Sequence of SELECT FROM JOIN WHERE GROUP BY HAVING ORDER BY
Next Post
SQL SERVER – 2005 – UDF – User Defined Function to Strip HTML – Parse HTML – No Regular Expression

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.