【问题标题】:How to create ordinal numbers (i.e. "1st" "2nd", etc.) in SQL Server如何在 SQL Server 中创建序数(即“1st”“2nd”等)
【发布时间】:2022-03-30 10:52:50
【问题描述】:

我最近在 SSRS-2008 标记中回复了to this question,要求将日期中的天数更改为序数(即“1st”、“2nd”而不是“1”、“2”)。该解决方案涉及一个 VB.Net 函数。我很好奇人们将如何在 SQL(特别是 t-sql 和 SQL Server)中执行此任务,或者是否有一些内置支持。

所以这是一个场景:假设您为 1000 名跑步者组织了一场赛跑,并将结果放在包含名称和地点列的表格中(以正常数字表示)。您想创建一个查询,以序号显示用户名和他们的位置。

【问题讨论】:

  • 没有内置支持。您必须创建一个位到后缀 (1 -> st, 2 -> nd) 的映射并覆盖十位 (12 -> th) 等。在报告/应用层中比在 SQL 中更容易做到.

标签: sql sql-server tsql


【解决方案1】:

这是一个适用于任何数字的可扩展解决方案。我以为其他人用 % 100 表示 11,12,13 但我错了。

WITH CTE_Numbers
AS
(
    SELECT 1 num
    UNION ALL
    SELECT num + 1
    FROM CTE_Numbers
    WHERE num < 1000
)

SELECT  CAST(num AS VARCHAR(10))
        +
        CASE
            WHEN num % 100 IN (11,12,13) THEN 'th' --first checks for exception
            WHEN num % 10 = 1 THEN 'st'
            WHEN num % 10 = 2 THEN 'nd'
            WHEN num % 10 = 3 THEN 'rd'
            ELSE 'th' --works for num % 10 IN (4,5,6,7,8,9,0)
        END
FROM CTE_Numbers
OPTION (MAXRECURSION 0)

【讨论】:

  • 辛苦了。我只需要在电子邮件文本中正确显示数字,这让我不必花时间在上面。谢谢。
  • 我碰巧看到了这篇文章。我不想将此作为解决方案发布,因为它与此完全相同,我只是碰巧使用提供此解决方案时不可用的功能以不同的方式编写它,我是一个超级粉丝一个衬垫大声笑:IIF(num % 100 IN (11,12,13)​​, 'th', COALESCE(CHOOSE(num % 10,'st','nd','rd'),'th'))
  • @chadwin 我认为单行应该有自己的答案。
【解决方案2】:

您可以在 SQL 中像在应用层中一样轻松地做到这一点:

DECLARE @myDate DATETIME = '2015-05-21';
DECLARE @day INT;
SELECT  @day = DAY(@myDate);
SELECT  CASE WHEN @day IN ( 11, 12, 13 ) THEN CAST(@day AS VARCHAR(10)) + 'th'
             WHEN @day % 10 = 1 THEN CAST(@day AS VARCHAR(10)) + 'st'
             WHEN @day % 10 = 2 THEN CAST(@day AS VARCHAR(10)) + 'nd'
             WHEN @day % 10 = 3 THEN CAST(@day AS VARCHAR(10)) + 'rd'
             ELSE CAST(@day AS VARCHAR(10)) + 'th'
        END

如有必要,您也可以将其放入标量函数中。

编辑

对于您的示例,它将是:

SELECT  Name ,
        CASE WHEN Place IN ( 11, 12, 13 )
             THEN CAST(Place AS VARCHAR(10)) + 'th'
             WHEN Place % 10 = 1 THEN CAST(Place AS VARCHAR(10)) + 'st'
             WHEN Place % 10 = 2 THEN CAST(Place AS VARCHAR(10)) + 'nd'
             WHEN Place % 10 = 3 THEN CAST(Place AS VARCHAR(10)) + 'rd'
             ELSE CAST(Place AS VARCHAR(10)) + 'th'
        END AS Place
FROM    FootRaceResults;

【讨论】:

  • 您的代码似乎适用于日期,但我提出的跑步者场景呢?第 111 名呢?有什么方法可以以可扩展的方式编写代码?
  • 您只需将该字段添加到您的选择语句中。
【解决方案3】:

非常害怕:

with
  ArabicRomanConversions as (
    select *
      from ( values
        ( 0, '', '', '', '' ), ( 1, 'I', 'X', 'C', 'M' ), ( 2, 'II', 'XX', 'CC', 'MM' ), ( 3, 'III', 'XXX', 'CCC', 'MMM' ), ( 4, 'IV', 'XL', 'CD', '?' ),
        ( 5, 'V', 'L', 'D', '?' ), ( 6, 'VI', 'LX', 'DC', '?' ), ( 7, 'VII', 'LXX', 'DCC', '?' ), ( 8, 'VIII', 'LXXX', 'DCCC', '?' ), ( 9, 'IX', 'XC', 'CM', '?' )
        ) as Placeholder ( Arabic, Ones, Tens, Hundreds, Thousands )
      ),
  OrdinalConversions as (
    select *
      from ( values
        ( 1, 'st' ), ( 2, 'nd' ), ( 3, 'rd' ), ( 11, 'th' ), ( 12, 'th' ), ( 13, 'th' )
        ) as Placeholder2 ( Number, Suffix )
      ),
  Numbers as (
    select 1 as Number
    union all
    select Number + 1
      from Numbers
      where Number < 3999 )
  select Number as Arabic,
    ( select Thousands from ArabicRomanConversions where Arabic = Number / 1000 ) +
    ( select Hundreds from ArabicRomanConversions where Arabic = Number / 100 % 10 ) +
    ( select Tens from ArabicRomanConversions where Arabic = Number / 10 % 10 ) +
    ( select Ones from ArabicRomanConversions where Arabic = Number % 10 ) as Roman,
    Cast( Number as VarChar(4) ) + Coalesce( (
      select top 1 Suffix from OrdinalConversions where Number = Numbers.Number % 100 or Number = Numbers.Number % 10 order by Number desc ), 'th' ) as Ordinal
    from Numbers option ( MaxRecursion 3998 );

【讨论】:

  • 有趣的方法和很好的答案。我注意到罗马数字位 - 这是来自预先存在的来源吗?
  • @TPhe - 我刚刚抓取了我为序数添加了一列的数字表 CTE。如果赛跑发生在很久以前,XLIInd 可能已经有人进来了。
  • 考虑到它比它需要的复杂得多,接受这个作为答案是很奇怪的。 Dave Gugg 的答案完美无缺,只是复杂性的一小部分......
  • @Stephan 我同意这个答案对于其中多余的罗马数字内容来说是不必要的笨重——这在我的问题的上下文中没有用,最好删掉。但是,Dave Gugg 的答案没有这个答案那么可扩展和完整。我专门要求解决方案达到 1000;戴夫的答案最多只能达到 110 (111 给你第 111 名而不是第 111 名)。他提出的扩展(只需添加 111,112,113,211,212,213 等的特殊情况)将比这个答案更有效且可扩展性更差。两个答案都很有用;两者都赞成。
【解决方案4】:

您可以使用 case 语句,即,

更新:考虑到青少年,正如 TPhe 所提到的并略有重构。

SELECT
    Name,
    CASE 
        WHEN Place in(11, 12, 13) then CAST(Place as VARCHAR(20)) + 'th'
        WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '1' then CAST(Place as VARCHAR(20)) + 'st'
        WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '2' then CAST(Place as VARCHAR(20)) + 'nd'
        WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '3' then CAST(Place as VARCHAR(20)) + 'rd'
        ELSE CAST(Place as VARCHAR(20)) + 'th'  
    END as Place
FROM
    RunnerTable

【讨论】:

  • 抱歉,我没有刷新看到 Daves 的回答。但是,是的,真的是一样的。
  • 我认为您的回答缺乏对青少年(11,12 和 13 岁)的支持,这很奇怪。在您的示例中,您将获得“11st”
【解决方案5】:
  DECLARE @Number int = 94
  SELECT 
    CONVERT(VARCHAR(10),@NUMBER) + CASE WHEN @Number % 100 IN (11, 12, 13) THEN 'th'
    ELSE 
        CASE @Number % 10
            WHEN 1 THEN 'st'
            WHEN 2 THEN 'nd'
            WHEN 3 THEN 'rd'
        ELSE 'th'
        END
    END 

【讨论】:

  • 漂亮而简单。喜欢它。偷了它。
【解决方案6】:

这对于任何数字都会好得多

 create Function dbo.fn_Numbers_Ordinal (@N as bigint) returns varchar(50) 
 as Begin
 Declare @a as varchar(50)= CAST(@N AS VARCHAR(50))
return(
SELECT  CAST(@N AS VARCHAR(50))
    +
    CASE
        WHEN Right(@a,2)='11' or Right(@a,2)='12' or Right(@a,2)='13'  Then 'th'
        WHEN @N % 10 = 1 THEN 'st'
        WHEN @N % 10 = 2 THEN 'nd'
        WHEN @N % 10 = 3 THEN 'rd'
        ELSE 'th' --for @N % 10 IN (4,5,6,7,8,9,0)
    END

)
end

【讨论】:

    【解决方案7】:

    声明@Number int = 113, @上标整数 如果@Number 不为空 开始

    IF LEN(@Number) >= 2
        SELECT @Superscript = RIGHT(@Number, 2)
    ELSE
        SELECT @Superscript = RIGHT(@Number, 1)
    
    SELECT @Number as Number,
     CASE WHEN @Superscript in (11,12,13) THEN 'th'
     ELSE CASE WHEN @Superscript = 1 THEN 'st'
                    WHEN @Superscript = 2 THEN 'nd'
                    WHEN @Superscript = 3 THEN 'rd'
                    ELSE 'th'
          END 
    END as Superscript
    

    结束其他 选择 0 作为数字,'th' 作为上标

    【讨论】:

      【解决方案8】:

      只是想我会添加各种选项。这是一个单线。大约一年前,我将此作为评论留下。但有人建议我提交它作为答案。所以你去吧:

      SELECT OrdinalRank = CONCAT(num, IIF(num % 100 IN (11,12,13),'th',COALESCE(CHOOSE(num % 10,'st','nd','rd'),'th')))
      FROM (
          VALUES (1),(2),(3),(4),(5),(10),(11),(20),(21),(22),(23),(24),(101),(102),(103)
      ) x(num)
      
      --Result:
      --1st
      --2nd
      --3rd
      --4th
      --5th
      --10th
      --11th
      --20th
      --21st
      --22nd
      --23rd
      --24th
      --101st
      --102nd
      --103rd
      

      这利用了 IIFCHOOSE 函数,它们仅在 SQL 2012+ 中可用。

      【讨论】:

        【解决方案9】:

        使用 FORMAT 函数解决此问题的另一个选项(您也可以显示其他语言的月份名称):

        ;WITH cte AS (
        SELECT 1 AS dayordinal ,'st' AS suffix
        UNION 
        SELECT 2 AS dayordinal ,'nd' AS suffix
        UNION 
        SELECT 3 AS dayordinal ,'rd' AS suffix
        
        )
        
        , YourTable AS --this is just for example
        (SELECT CAST('1/1/2022' AS DATE) DateColumn
        UNION 
        SELECT CAST('1/14/2022' AS DATE) DateColumn
        UNION 
        SELECT CAST('4/4/2022' AS DATE) DateColumn
        UNION 
        SELECT CAST('2/2/2022' AS DATE) DateColumn
        UNION 
        SELECT CAST('3/13/2022' AS DATE) DateColumn
        )
        
        SELECT CAST(DATEPART(DAY, DateColumn) AS NVARCHAR(2))+ISNULL(c.suffix, 'th')+' '+ FORMAT(DateColumn, 'MMMM yyyy', 'fr-FR')
        FROM YourTable t
        LEFT JOIN cte c ON c.dayordinal=RIGHT(DATEPART(DAY, DateColumn),1)
        

        【讨论】:

          【解决方案10】:

          使用下面的 SSRS 表达式:

          = DAY(Globals!ExecutionTime) & 
              SWITCH(
              DAY(Globals!ExecutionTime)= 1 OR DAY(Globals!ExecutionTime) = 21 OR DAY(Globals!ExecutionTime)=31, "st",
              DAY(Globals!ExecutionTime)= 2 OR DAY(Globals!ExecutionTime) = 22 , "nd",
              DAY(Globals!ExecutionTime)= 3 OR DAY(Globals!ExecutionTime) = 23 , "rd",
              true, "th"
              )
          

          这很容易实现并且效果很好。

          【讨论】:

            【解决方案11】:
            Public Function OrdinalNumberSuffix(ByVal InNumber As Integer) As String
            Dim StrNumber As String, _
                Digit     As Byte, _
                Suffix    As String
            
                StrNumber = Trim(Str(InNumber))
                If Val(StrNumber) > 3 And Val(StrNumber) < 14 Then
                    Digit = Val(Right(StrNumber, 2))
                Else
                    Digit = Val(Right(StrNumber, 1))
                End If
                Select Case Digit
                    Case 1:    Suffix = "st"
                    Case 2:    Suffix = "nd"
                    Case 3:    Suffix = "rd"
                    Case Else: Suffix = "th"
                End Select
                OrdinalNumberSuffix = " " & StrNumber & Suffix & " "
            End Function
            

            【讨论】:

            • OP 询问 “我很好奇如何在 SQL(特别是 t-sql 和 SQL Server)中执行此任务”。您的答案在 VB 中,因此不回答 OP 的问题。
            猜你喜欢
            • 2022-11-04
            • 2011-03-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-27
            • 1970-01-01
            • 2012-01-14
            • 2020-09-07
            相关资源
            最近更新 更多