递归CTE将数字转换为单词

时间:2018-08-31 10:41:40

标签: sql-server tsql text data-conversion

我正在寻找基于recursive CTE(公用表表达式)解决方案的简单脚本或函数,用于在T-SQL中将整数转换为英文单词。递归CTE速度很快,尤其是在内联表值函数中使用时,并且易于理解和维护。另外,它应该在整个BIGINT范围内运行(尽管我不在乎否定值),即从0到2 64 -1。

编辑:例如,如果将递归CTE放在标量函数中(用于说明):SELECT dbo.NumberToWords(0)应该返回字符串'zero'; SELECT dbo.NumberToWords(1234)应该返回非常类似于“一千零二十四”或“一千二百三十四”的内容;依此类推,对于BIGINT范围内的所有数字,

我见过的大多数其他(过程性)解决方案都很慢,崩溃导致大数目,或者对于某些输入(例如,一百万),返回纯错误或无结果。

我知道那里还有很多其他程序解决方案;我特别想要递归CTE。据我所知,目前还没有这样的解决方案。

1 个答案:

答案 0 :(得分:4)

这会将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 '更改为' '(代码中有描述方式的注释)。所谓 current 英式英语,是指short scale一直在美式英语中使用(即“十亿”表示1,000,000,000),而不是较旧的长期使用语言(直到大约在70年代,“十亿”表示一百万或一万亿)。

我在结果中包括了逗号,尽管我不确定这是否是标准的。如果不想,只需删除字符串文字中的逗号即可。