9

In PostgreSQL, I often times want to do something like find the factorial of 7. I can do that very simply with

SELECT 7!;

-- PostgreSQL is so full featured
-- it even supports a prefix-factorial
SELECT !!7;

Even Excel has FACT,

=FACT(7)

How do I do that with SQL Server 2017 Enterprise?

15

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).

8

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))
    );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.