Comment faire des factoriels dans SQL Server?

8

Dans PostgreSQL, je veux souvent faire quelque chose comme trouver le factoriel de 7. Je peux le faire très simplement avec

SELECT 7!;

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

Même Excel aFACT ,

=FACT(7)

Comment faire cela avec SQL Server 2017 Enterprise?

Evan Carroll
la source

Réponses:

15

Je ne connais pas de fonction intégrée pour ce faire. Vous devez rouler le vôtre. Voici comment je le fais:

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

La fonction Math_Factorial est dans la version gratuite de la bibliothèque SQL # SQLCLR (que j'ai écrite).

OU

si vous n'en avez pas besoin sous forme de fonction / UDF, alors il pourrait être plus efficace de faire ce qui suit:

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

Les deux approches présentées ci-dessus prennent en compte la condition "spéciale" de passage en 0tant que "BaseNumber" et de retour 1au lieu de 0.

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

Pour l'approche T-SQL, il suffit de faire @BaseNumber = 0et il reviendra 1(pas besoin de le copier-coller ici juste pour ça).

Solomon Rutzky
la source
8

Réponse du wiki communautaire :

Vous pouvez être déçu des résultats dans SQL Server par rapport à PostgreSQL (qui est capable de traiter de très grands nombres tels que 30000! Sans perte de précision).

Dans SQL Server 33!est aussi haut que vous pouvez aller avec une précision exacte , tout 170!est aussi haut que vous pouvez aller du tout ( 171!est1.24E309 ce qui dépasse les limites de float).

Vous pouvez donc simplement les précalculer et les stocker dans une table avec des valeurs 0 ... 170. Cela tient sur une seule page de données si la compression est utilisée.

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

Sinon, ce qui suit donnera des résultats précis pour @N jusqu'à 10 - et approximative 11+ (il serait plus exact si les différentes fonctions / constantes ( PI(), EXP(), POWER()) ont travaillé avec les DECIMALtypes , mais ils travaillent avec FLOATseulement):

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
la source