Рекурсивный CTE для преобразования чисел в слова - PullRequest
0 голосов
/ 31 августа 2018

Я ищу простой скрипт или функцию, основанную на рекурсивном CTE (общее табличное выражение) решение для преобразования целых чисел в английские слова в T-SQL. Рекурсивные CTE бывают быстрыми, особенно когда они используются во встроенной табличной функции, и их легко понять и поддерживать. Кроме того, он должен работать во всем диапазоне BIGINT (хотя я не забочусь о негативах), то есть от 0 до 2 64 -1.

Редактировать: Например, если рекурсивный CTE помещен в скалярную функцию (для иллюстрации): SELECT dbo.NumberToWords(0) должен возвращать строку «ноль»; SELECT dbo.NumberToWords(1234) должно возвращать что-то очень похожее на «тысяча двести тридцать четыре» или «тысяча двести тридцать четыре»; и так далее для всех чисел в диапазоне BIGINT.

Большинство других (процедурных) решений, которые я видел, являются медленными, дают сбой при больших числах или возвращают просто неверные результаты или не дают результатов для определенных входных данных (например, один миллион тысяч).

Я знаю, что есть куча других процедурных решений; Я специально хочу рекурсивный CTE. Насколько я знаю, таких решений не существует.

1 Ответ

0 голосов
/ 31 августа 2018

Преобразует неотрицательные числа в диапазоне BIGINT в английские слова.

CREATE FUNCTION dbo.tvfNumberToEnglishWords (@Number BIGINT)  -- BIGINT is 64 bit signed integer

-- Converts whole numbers into English words. Returns an empty string for negative numbers.
-- Parameter @Number is a 64-bit signed integer, which allows a range of zero to 9,223,372,036,854,775,807

-- 2018-08-29 - Dave Boltman - Created

RETURNS TABLE AS
  RETURN

    WITH
      Smalls AS (
          SELECT n, Name
          FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'), (6, 'six'), (7, 'seven'),
                       (8, 'eight'), (9, 'nine'), (10, 'ten'), (11, 'eleven'), (12, 'twelve'), (13, 'thirteen'), (14, 'fourteen'),
                       (15, 'fifteen'), (16, 'sixteen'), (17, 'seventeen'), (18, 'eighteen'), (19, 'nineteen')) AS s (n, Name)
      ), 

      Decades AS (
          SELECT n, Name
          FROM (VALUES (2, 'twenty'), (3, 'thirty'), (4, 'forty'), (5, 'fifty'),
                       (6, 'sixty'), (7, 'seventy'), (8, 'eighty'), (9, 'ninety')) AS t (n, Name)
      ),

      Groups AS (
          SELECT n, Name
          FROM (VALUES (0, ''), (1, ' thousand'), (2, ' million'), (3, ' billion'), (4, ' trillion'), (5, ' quadrillion'), (6, ' quintillion')
               -- up to here is enough for 64 bit integers
               ) AS g (n, Name)
      ),

      cte AS (

          -- Level 0 : Anchor query to start the processing
          SELECT 
            CAST (0 AS INT) AS Level,
            CAST (@Number % 1000 AS BIGINT) Lowest3Digits,
            cast (@Number / 1000 AS BIGINT) AS RemainingDigits,
            CASE WHEN @Number = 0 THEN CAST ('zero' AS VARCHAR(1024)) ELSE '' END TextSoFar

          UNION ALL

          -- Recursive query based on the previous level
          SELECT
            Level + 1 AS Level,   -- increase the level
            RemainingDigits % 1000 AS Lowest3Digits,
            RemainingDigits / 1000 AS RemainingDigits,
            -- Busniess part is here
            CAST (
                CASE WHEN (RemainingDigits > 0) AND (Lowest3Digits > 0) 
                     THEN CASE WHEN (Hundreds > 0) OR (Level > 0) THEN ', ' ELSE ' and ' END  -- change ' and ' to ' ' for American English
                     ELSE '' 
                END
              + CASE WHEN Hundreds > 0 THEN (SELECT Name FROM Smalls WHERE n = Hundreds) + ' hundred' ELSE '' END
              + CASE WHEN (Hundreds > 0) AND (TensUnits > 0) THEN ' and ' ELSE '' END
              + CASE WHEN TensUnits >= 20
                     THEN (SELECT Name FROM Decades WHERE n = Tens)
                          + CASE WHEN Units > 0 THEN (SELECT '-' + Name FROM Smalls WHERE n = Units) ELSE '' END
                     ELSE CASE WHEN TensUnits > 0 THEN (SELECT Name FROM Smalls WHERE n = TensUnits) ELSE '' END
                END
              + CASE WHEN Lowest3Digits > 0 THEN (SELECT Name FROM Groups WHERE n = Level) ELSE '' END
              + TextSoFar
              AS VARCHAR(1024)) AS TextSoFar

          FROM 
            ( SELECT Level, Lowest3Digits, RemainingDigits, TextSoFar,
                Lowest3Digits / 100 AS Hundreds, Lowest3Digits % 100 AS TensUnits, (Lowest3Digits % 100) / 10 AS Tens, Lowest3Digits % 10 AS Units
              FROM cte                                                                          -- Dave Boltman made this code
            ) AS l

          WHERE  -- condition for exiting the recursion
            (Lowest3Digits > 0) OR (RemainingDigits > 0)

      )

    SELECT TOP 1 TextSoFar AS EnglishText FROM cte ORDER BY Level DESC

GO

Ограниченные данные испытаний

SELECT n, dbo.fnNumberToEnglishWords(n) AS AsText
FROM (VALUES (0),(-1),(1),(19),(63),(100),(101),(147),(1000),(1001),(1056),(1100),(1110),(900456),(76543),(1000000),(1000001),(1001001),(1001000),(1234567),(1234567890),(9223372036854775807)) x (n)

Результаты

n                     AsText
--------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0                     zero
-1                    
1                     one
19                    nineteen
63                    sixty-three
100                   one hundred
101                   one hundred and one
147                   one hundred and forty-seven
1000                  one thousand
1001                  one thousand and one
1056                  one thousand and fifty-six
1100                  one thousand, one hundred
1110                  one thousand, one hundred and ten
900456                nine hundred thousand, four hundred and fifty-six
76543                 seventy-six thousand, five hundred and forty-three
1000000               one million
1000001               one million and one
1001001               one million, one thousand and one
1001000               one million, one thousand
1234567               one million, two hundred and thirty-four thousand, five hundred and sixty-seven
1234567890            one billion, two hundred and thirty-four million, five hundred and sixty-seven thousand, eight hundred and ninety
9223372036854775807   nine quintillion, two hundred and twenty-three quadrillion, three hundred and seventy-two trillion, thirty-six billion, eight hundred and fifty-four million, seven hundred and seventy-five thousand, eight hundred and seven

Редактировать: (перенесено сюда) Используется текущий британский английский. Если вы предпочитаете американский английский, просто измените один из строковых литералов выше с ' and ' на ' ' (в коде есть комментарий, который описывает, как). Под текущим британским английским я подразумеваю короткую шкалу , которая всегда использовалась в американском английском (т. Е. «Миллиард» означает 1 000 000 000), а не более старую длинную шкалу (используется в британском английском). примерно до 70-х годов, где «миллиард» означает миллион миллионов, или 1 000 000 000 000).

Я включил запятые в результат, хотя я не уверен, что это стандарт. Просто удалите запятую в строковом литерале, если вы этого не хотите.

...