¿Cómo hago factoriales en SQL Server?

8

En PostgreSQL, muchas veces quiero hacer algo como encontrar el factorial de 7. Puedo hacerlo de manera muy simple con

SELECT 7!;

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

Incluso Excel tieneFACT ,

=FACT(7)

¿Cómo hago eso con SQL Server 2017 Enterprise?

Evan Carroll
fuente

Respuestas:

15

No conozco una función incorporada para hacer esto. Necesitas rodar el tuyo. Así es como lo hago:

SELECT SQL#.Math_Factorial(5); -- 120

La función Math_Factorial se encuentra en la versión gratuita de la biblioteca SQL # SQLCLR (que escribí).

O

Si no lo necesita en forma de función / UDF, entonces podría ser más eficiente hacer lo siguiente:

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

Ambos enfoques mostrados arriba tienen en cuenta la condición "especial" de pasar 0como "BaseNumber" y regresar en 1lugar de 0.

SELECT SQL#.Math_Factorial(0); -- 1

Para el enfoque T-SQL, simplemente cree @BaseNumber = 0y volverá 1(no es necesario copiarlo y pegarlo nuevamente aquí solo por eso).

Solomon Rutzky
fuente
8

Respuesta wiki comunitaria :

Puede estar decepcionado con los resultados en SQL Server en comparación con PostgreSQL (que es capaz de manejar números muy grandes como 30000 sin pérdida de precisión).

En SQL Server 33!es tan alto como puede llegar con precisión exacta, mientras que 170!es tan alto como puede llegar ( 171!es lo1.24E309 que excede los límites de float).

Por lo tanto, podría precalcularlos y almacenarlos en una tabla con valores 0 ... 170. Esto encaja en una sola página de datos si se usa compresión.

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

Por otra parte, lo siguiente será dar resultados exactos para @N hasta el 10 - y aproximada de 11+ (sería más exacto si las diversas funciones / constantes ( PI(), EXP(), POWER()) trabajaron con DECIMALtipos pero trabajan con FLOATsolamente):

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))
    );
Martin Smith
fuente