Как я делаю факториалы в SQL Server?

8

В PostgreSQL я часто хочу сделать что-то вроде поиска факториала 7. Я могу сделать это очень просто с

SELECT 7!;

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

Даже в Excel естьFACT ,

=FACT(7)

Как это сделать с SQL Server 2017 Enterprise?

Эван Кэрролл
источник

Ответы:

15

Я не знаю о встроенной функции для этого. Вы должны свернуть свой собственный. Вот как я это делаю:

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

Функция Math_Factorial находится в бесплатной версии библиотеки SQL # SQLCLR (которую я написал).

ИЛИ

если вам это не нужно в форме функции / UDF, то может быть эффективнее сделать следующее:

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

Оба подхода, показанные выше, учитывают «особое» условие передачи в 0качестве «BaseNumber» и его возврата 1вместо 0.

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

Для подхода T-SQL просто сделайте, @BaseNumber = 0и он вернется 1(нет необходимости копировать и вставлять его здесь только для этого).

Соломон Руцкий
источник
8

Ответ сообщества вики :

Вы можете быть разочарованы результатами в SQL Server по сравнению с PostgreSQL (который может работать с очень большими числами, такими как 30000! Без потери точности).

Уровень SQL Server 33!настолько высок, насколько вы можете идти с точной точностью, и в то же время 170!настолько высок, насколько вы можете идти вообще ( 171!это1.24E309 превышает пределы float).

Таким образом, вы можете просто пересчитать их и сохранить их в таблице со значениями 0 ... 170. Это помещается на одной странице данных, если используется сжатие.

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

Кроме того , следующий будет давать точные результаты для @N до 10 - и приближенного для 11+ (это было бы более точным , если различные функции / константы ( PI(), EXP(), POWER()) работали с DECIMALтипами , но они работают с FLOATтолько):

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
источник