How do I do factorials in SQL Server?
I am not aware of a built-in function to do this. You need to roll your own. Here is how I do it:
SELECT SQL#.Math_Factorial(5); -- 120
The Math_Factorial function is in the Free version of the SQL# SQLCLR library (that I wrote).
OR
if you do not need it in function / UDF form, then it might be more efficient to do the following:
DECLARE @BaseNumber INT = 5,
@Result BIGINT = 1;
;WITH cte AS
(
SELECT TOP (@BaseNumber) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
FROM sys.columns
ORDER BY Num
)
SELECT @Result *= [Num]
FROM cte;
SELECT @Result;
-- 120
Both approaches shown above take into account the "special" condition of passing in 0
as the "BaseNumber" and it returning 1
instead of 0
.
SELECT SQL#.Math_Factorial(0); -- 1
For the T-SQL approach, just make @BaseNumber = 0
and it will return 1
(no need to copy and paste it again here just for that).
Community wiki answer:
You may be disappointed with the results in SQL Server compared to PostgreSQL (which is able to deal with very large numbers such as 30000! without loss of precision).
In SQL Server 33!
is as high as you can go with exact precision whilst 170!
is as high as you can go at all (171!
is 1.24E309
which exceeds the limits of float
).
So you could just precalculate them and store them in a table with values 0 ... 170
. This fits on a single data page if compression is used.
CREATE TABLE dbo.Factorials
(
N TINYINT PRIMARY KEY WITH (DATA_COMPRESSION = ROW),
FactorialExact NUMERIC(38, 0) NULL,
FactorialApprox FLOAT NOT NULL
);
WITH R(N, FactorialExact, FactorialApprox)
AS (SELECT 0,
CAST(1 AS NUMERIC(38, 0)),
1E0
UNION ALL
SELECT R.N + 1,
CASE WHEN R.N < 33 THEN ( R.N + 1 ) * R.FactorialExact END,
CASE WHEN R.N < 170 THEN ( R.N + 1 ) * R.FactorialApprox END
FROM R
WHERE R.N < 170)
INSERT INTO dbo.Factorials
(N,
FactorialExact,
FactorialApprox)
SELECT N,
FactorialExact,
FactorialApprox
FROM R
OPTION (MAXRECURSION 170);
Alternatively, the following will give accurate results for @N up to 10 - and approximate for 11+ (it would be more accurate if the various functions/constants (PI()
, EXP()
, POWER()
) worked with DECIMAL
types but they work with FLOAT
only):
DECLARE @N integer = 10;
SELECT
CONVERT
(
DECIMAL(38,0),
SQRT(2 * PI() * @N) *
POWER(@N/EXP(1), @N) *
EXP(1.0/12.0/@N + 1.0/360.0/POWER(@N, 3))
);