【问题标题】:How to compare software versions using SQL Server?如何使用 SQL Server 比较软件版本?
【发布时间】:2021-07-22 12:24:49
【问题描述】:

在尝试比较软件版本 5.12 和 5.8 时,版本 5.12 较新,但从数学上讲,5.12 小于 5.8。我将如何比较这两个版本,以便较新的版本返回“Y”?

SELECT CASE WHEN 5.12 > 5.8 THEN 'Y' ELSE 'N' END

可能的解决方案

  1. 在 5.8 的小数点后添加一个 0,以便将 5.08 与 5.12 进行比较,但是这似乎需要一些代码。
  2. 只比较小数点后的值(即 12 > 8),但是当版本滚动到 6.0 时这会失败。
  3. 使用反向逻辑并假设如果 5.12 小于 5.8 则返回“Y”。我相信当版本滚动到 6.0 时这会失败。

【问题讨论】:

  • SQL Server 的版本?如果 2008+ DECLARE @V1 VARCHAR(10) = '5.12', @V2 VARCHAR(10) = '5.8';SELECT CASE WHEN CAST('/' + @V1 + '/' AS HIERARCHYID) > CAST('/' + @V2 + '/' AS HIERARCHYID) THEN 'Y' ELSE 'N' END
  • @MartinSmith 你迟到了。
  • @MartinSmith 解决方案效果很好
  • @MartinSmith 的 HierarchyID 解决方案非常适合这种情况。似乎 HierarchyID 仅支持版本号中“点”之间最多 5 位数字,因此请谨慎使用。
  • @CJBS 我不认为hierarchyid 对深度有任何限制。它适用于一般的分层数据

标签: sql sql-server tsql


【解决方案1】:

你可以使用hierarchyid 您可以通过在字符串的末尾和开头放置 / 并强制转换来使用它

例如

SELECT CASE WHEN cast('/5.12/' as hierarchyid) > cast('/5.8/' as hierarchyid) THEN 'Y' ELSE 'N' END

返回一个Y

【讨论】:

    【解决方案2】:
    declare @v1 varchar(100) = '5.12'
    declare @v2 varchar(100) = '5.8'
    
    select 
        case 
        when CONVERT(int, LEFT(@v1, CHARINDEX('.', @v1)-1)) < CONVERT(int, LEFT(@v2, CHARINDEX('.', @v2)-1)) then 'v2 is newer'
        when CONVERT(int, LEFT(@v1, CHARINDEX('.', @v1)-1)) > CONVERT(int, LEFT(@v2, CHARINDEX('.', @v2)-1)) then 'v1 is newer'
        when CONVERT(int, substring(@v1, CHARINDEX('.', @v1)+1, LEN(@v1))) < CONVERT(int, substring(@v2, CHARINDEX('.', @v2)+1, LEN(@v1))) then 'v2 is newer'
        when CONVERT(int, substring(@v1, CHARINDEX('.', @v1)+1, LEN(@v1))) > CONVERT(int, substring(@v2, CHARINDEX('.', @v2)+1, LEN(@v1))) then 'v1 is newer'
        else 'same!'
    
        end
    

    【讨论】:

    • 并且这可以根据需要轻松扩展到 n 部分的版本号。
    • 感谢您的回答,我没有找到轻松将其扩展到 n 部分的方法,然后我发布了自己的答案:stackoverflow.com/a/65698263/11159476 与此您也可以例如比较版本“1.2 " 与 "1.2.1" (将返回 -1, v1
    【解决方案3】:

    我建议创建一个 SQL CLR 函数:

    public partial class UserDefinedFunctions
    {
        [SqlFunction(Name = "CompareVersion")] 
        public static bool CompareVersion(SqlString x, SqlString y)
        {
            return Version.Parse(x) > Version.Parse(y);
        }
    }
    

    注意事项:

    • SqlString 有explicit cast 到字符串。
    • a.b.c.d 开始传递完整版本字符串

    【讨论】:

      【解决方案4】:

      这里的重复问题有一个很好的解决方案: How to compare SQL strings that hold version numbers like .NET System.Version class?

      在玩了一段时间后,我了解到当有 4 个或更多部分时,它无法比较最后一个部分(例如,如果版本号是 1.2.3.4,它总是会处理最后一个为 0)。我已经解决了这个问题,并提出了另一个比较两个版本号的函数。

      CREATE Function [dbo].[VersionNthPart](@version as nvarchar(max), @part as int) returns int as
      Begin
      
      Declare
          @ret as int = null,
          @start as int = 1,
          @end as int = 0,
          @partsFound as int = 0,
          @terminate as bit = 0
      
        if @version is not null
        Begin
          Set @ret = 0
          while @partsFound < @part
          Begin
            Set @end = charindex('.', @version, @start)
            If @end = 0 -- did not find the dot. Either it was last part or the part was missing.
            begin
              if @part - @partsFound > 1 -- also this isn't the last part so it must bail early.
              begin
                  set @terminate = 1
              end
              Set @partsFound = @part
              SET @end = len(@version) + 1; -- get the full length so that it can grab the whole of the final part.
            end
            else
            begin
              SET @partsFound = @partsFound + 1
            end
            If @partsFound = @part and @terminate = 0
            begin
                  Set @ret = Convert(int, substring(@version, @start, @end - @start))
            end
            Else
            begin
                  Set @start = @end + 1
            end
          End
        End
        return @ret
      End
      GO
      
      CREATE FUNCTION [dbo].[CompareVersionNumbers]
      (
          @Source nvarchar(max),
          @Target nvarchar(max),
          @Parts int = 4
      )
      RETURNS INT
      AS
      BEGIN
      /*
      -1 : target has higher version number (later version)
      0 : same
      1 : source has higher version number (later version)
      */ 
          DECLARE @ReturnValue as int = 0;
          DECLARE @PartIndex as int = 1;
          DECLARE @SourcePartValue as int = 0;
          DECLARE @TargetPartValue as int = 0;
          WHILE (@PartIndex <= @Parts AND @ReturnValue = 0)
          BEGIN
              SET @SourcePartValue = [dbo].[VersionNthPart](@Source, @PartIndex);
              SET @TargetPartValue = [dbo].[VersionNthPart](@Target, @PartIndex);
              IF @SourcePartValue > @TargetPartValue
                  SET @ReturnValue = 1
              ELSE IF @SourcePartValue < @TargetPartValue
                  SET @ReturnValue = -1
              SET @PartIndex = @PartIndex + 1;
          END
          RETURN @ReturnValue
      END
      

      使用/测试用例:

      declare @Source as nvarchar(100) = '4.9.21.018'
      declare @Target as nvarchar(100) = '4.9.21.180'
      SELECT [dbo].[CompareVersionNumbers](@Source, @Target, DEFAULT) -- default version parts are 4
      
      SET @Source = '1.0.4.1'
      SET @Target = '1.0.1.8'
      SELECT [dbo].[CompareVersionNumbers](@Source, @Target, 4) -- typing out # of version parts also works
      
      SELECT [dbo].[CompareVersionNumbers](@Source, @Target, 2) -- comparing only 2 parts should be the same
      
      SET @Target = '1.0.4.1.5'
      SELECT [dbo].[CompareVersionNumbers](@Source, @Target, 4) -- only comparing up to parts 4 so they are the same
      SELECT [dbo].[CompareVersionNumbers](@Source, @Target, 5) -- now comparing 5th part which should indicate that the target has higher version number
      

      【讨论】:

        【解决方案5】:

        我在尝试基于 semantic versioning 过滤 SQL 行时遇到了这个问题。我的解决方案有点不同,因为我想存储带有语义版本号标记的配置行,然后选择与我们软件的运行版本兼容的行。

        假设:

        • 我的软件将包含一个包含当前版本号的配置设置
        • 数据驱动的配置行将包含最低版本号
        • 我需要能够选择 min

        例子:

        • 1.0.0 版本应包括:1.0.0、1.0.0-*、1.0.0-beta.1
        • 版本 1.0.0 应排除:1.0.1、1.1.0、2.0.0
        • 版本 1.1.0-beta.2 应包括:1.0.0、1.0.1、1.1.0-beta.1、1.1.0-beta.2
        • 版本 1.1.0-beta.2 应排除:1.1.0、1.1.1、1.2.0、2.0.0、1.1.1-beta.1

        MSSQL UDF 是:

        CREATE FUNCTION [dbo].[SemanticVersion] (
            @Version nvarchar(50)
        )
        RETURNS nvarchar(255)
        
        AS
        BEGIN
        
            DECLARE @hyphen int = CHARINDEX('-', @version)
            SET @Version = REPLACE(@Version, '*', ' ')
            DECLARE 
                @left nvarchar(50) = CASE @hyphen WHEN 0 THEN @version ELSE SUBSTRING(@version, 1, @hyphen-1) END,
                @right nvarchar(50) = CASE @hyphen WHEN 0 THEN NULL ELSE SUBSTRING(@version, @hyphen+1, 50) END,
                @normalized nvarchar(255) = '',
                @buffer int = 8
        
            WHILE CHARINDEX('.', @left) > 0 BEGIN
                SET @normalized = @normalized + CASE ISNUMERIC(LEFT(@left, CHARINDEX('.', @left)-1))
                    WHEN 0 THEN LEFT(@left, CHARINDEX('.', @left)-1)
                    WHEN 1 THEN REPLACE(STR(LEFT(@left, CHARINDEX('.', @left)-1), @buffer), SPACE(1), '0')
                END  + '.'
                SET @left = SUBSTRING(@left, CHARINDEX('.', @left)+1, 50)
            END
            SET @normalized = @normalized + CASE ISNUMERIC(@left)
                WHEN 0 THEN @left
                WHEN 1 THEN REPLACE(STR(@left, @buffer), SPACE(1), '0')
            END
        
            SET @normalized = @normalized + '-'
            IF (@right IS NOT NULL) BEGIN
                WHILE CHARINDEX('.', @right) > 0 BEGIN
                    SET @normalized = @normalized + CASE ISNUMERIC(LEFT(@right, CHARINDEX('.', @right)-1))
                        WHEN 0 THEN LEFT(@right, CHARINDEX('.', @right)-1)
                        WHEN 1 THEN REPLACE(STR(LEFT(@right, CHARINDEX('.', @right)-1), @buffer), SPACE(1), '0')
                    END  + '.'
                    SET @right = SUBSTRING(@right, CHARINDEX('.', @right)+1, 50)
                END
                SET @normalized = @normalized + CASE ISNUMERIC(@right)
                    WHEN 0 THEN @right
                    WHEN 1 THEN REPLACE(STR(@right, @buffer), SPACE(1), '0')
                END
            END ELSE 
                SET @normalized = @normalized + 'zzzzzzzzzz'
        
            RETURN @normalized
        
        END
        

        SQL 测试包括:

        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha') < dbo.SemanticVersion('1.0.0-alpha.1') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha.1') < dbo.SemanticVersion('1.0.0-alpha.beta') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-alpha.beta') < dbo.SemanticVersion('1.0.0-beta') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta') < dbo.SemanticVersion('1.0.0-beta.2') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta.2') < dbo.SemanticVersion('1.0.0-beta.11') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-beta.11') < dbo.SemanticVersion('1.0.0-rc.1') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-rc.1') < dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        
        
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        
        SELECT CASE WHEN dbo.SemanticVersion('1.0.0-*') <= dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.1-*') > dbo.SemanticVersion('1.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.0.1-*') <= dbo.SemanticVersion('1.0.1') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.1.*') > dbo.SemanticVersion('1.0.9') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.1.*') <= dbo.SemanticVersion('1.2.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.*') <= dbo.SemanticVersion('2.0.0') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('1.*') > dbo.SemanticVersion('0.9.9-beta-219') THEN 'Success' ELSE 'Failure' END
        SELECT CASE WHEN dbo.SemanticVersion('*') <= dbo.SemanticVersion('0.0.1-alpha-1') THEN 'Success' ELSE 'Failure' END
        

        【讨论】:

          【解决方案6】:

          两步,先比较小数点左边,再比较右边。


          可能的解决方案:

          declare @v1 varchar(100) = '5.12'
          declare @v2 varchar(100) = '5.8'
          
          select case 
              when CONVERT(int, LEFT(@v1, CHARINDEX('.', @v1)-1)) < CONVERT(int, LEFT(@v2, CHARINDEX('.', @v2)-1)) then 'v2 is newer'
              when CONVERT(int, LEFT(@v1, CHARINDEX('.', @v1)-1)) > CONVERT(int, LEFT(@v2, CHARINDEX('.', @v2)-1)) then 'v1 is newer'
              when CONVERT(int, RIGHT(@v1, LEN(@v1) - CHARINDEX('.', @v1))) < CONVERT(int, RIGHT(@v2, LEN(@v2) - CHARINDEX('.', @v2))) then 'v2 is newer'
              when CONVERT(int, RIGHT(@v1, LEN(@v1) - CHARINDEX('.', @v1))) > CONVERT(int, RIGHT(@v2, LEN(@v2) - CHARINDEX('.', @v2))) then 'v1 is newer'
              else 'same!' end as 'Version Test'
          

          【讨论】:

          • 数据类型为float时,版本以.0结尾时不起作用。
          【解决方案7】:

          不要在字符串中存储不是字符串的内容。另一种方法是创建您自己的数据类型(在 C# 中 - 允许一段时间),将版本存储为字节序列并实现适当的比较逻辑。

          【讨论】:

          • 您能否详细说明“不要将不是字符串的内容存储在字符串中”是什么意思?
          • Simple 5.12 不是字符串 - 它是 2 个数字 (5, 12) 的序列。将它们存储在 varchar 中会强制对其进行字符串语义 - 这是行不通的。将它们存储在自定义类型中意味着您可以放入适当的语义。msdn.microsoft.com/en-us/library/ms131120.aspx 对此有详细说明。
          • 也许我应该补充一点,我将这些存储为小数。
          • 同样糟糕 - 它们也不是小数,因此您 (a) 浪费空间并 (b) 对它们施加错误的语义。
          【解决方案8】:

          按照 AF 的建议,您可以比较 int 部分,然后是小数部分。除了给出的所有答案之外,还有另一种使用 parsename 的方法。您可以尝试这样的方法

           case when cast(@var as int)>cast(@var2 as int) then 'Y' 
           when cast(PARSENAME(@var,1) as int) > cast(PARSENAME(@var2,1) as int) THEN 'Y'
          
          
           Declare @var float
           Declare @var2 float
           set @var=5.14
           set @var2=5.8
           Select case when cast(@var as int)>cast(@var2 as int) then 'Y' 
           when cast(PARSENAME(@var,1) as int)> cast(PARSENAME(@var2,1) as int) THEN 'Y'
           else 'N' END
          

          【讨论】:

          • 我不确定我是否没有正确测试这个,但是当 @var = 5.14 和 @var2 = 5.8 时它似乎失败了。
          • 我还没有执行查询,但是您可以将 parsename 转换为 int example cast(PARSENAME(@var,1) as int 。我已经更新了我的答案!!
          【解决方案9】:

          您在问题中没有这么说,但 Tomtom 的回答下的 your comment 建议您将版本号存储为 [decimals][d]。我猜你有一张这样的桌子:

          CREATE TABLE ReleaseHistory (
            VersionNumber DECIMAL(6,3) NOT NULL
          );
          GO
          
          INSERT INTO ReleaseHistory (
            VersionNumber
          )
          VALUES
            (5.12),
            (5.8),
            (12.34),
            (3.14),
            (0.78),
            (1.0);
          GO
          

          以下查询尝试按发布顺序对版本进行排名:

          SELECT
            VersionNumber,
            RANK() OVER (ORDER BY VersionNumber) AS ReleaseOrder
          FROM ReleaseHistory;
          

          它产生以下结果集:

          VersionNumber                           ReleaseOrder
          --------------------------------------- --------------------
          0.780                                   1
          1.000                                   2
          3.140                                   3
          5.120                                   4
          5.800                                   5
          12.340                                  6
          

          这不是我们所期望的。 5.8版本早于5.12版本发布!

          将版本号拆分为其主要和次要组件以正确排列版本号。一种方法是将十进制值转换为字符串并在句点上拆分。用于此的 T-SQL 语法很难看(该语言不是为字符串处理而设计的):

          WITH VersionStrings AS (
            SELECT CAST(VersionNumber AS VARCHAR(6)) AS VersionString
            FROM ReleaseHistory
          ),
          VersionNumberComponents AS (
            SELECT
              CAST(SUBSTRING(VersionString, 1, CHARINDEX('.', VersionString) - 1) AS INT) AS MajorVersionNumber,
              CAST(SUBSTRING(VersionString, CHARINDEX('.', VersionString) + 1, LEN(VersionString) - CHARINDEX('.', VersionString)) AS INT) AS MinorVersionNumber
            FROM VersionStrings
          )
          SELECT
            CAST(MajorVersionNumber AS VARCHAR(3)) + '.' + CAST(MinorVersionNumber AS VARCHAR(3)) AS VersionString,
            RANK() OVER (ORDER BY MajorVersionNumber, MinorVersionNumber) AS ReleaseOrder
          FROM VersionNumberComponents;
          

          但它提供了预期的结果:

          VersionString ReleaseOrder
          ------------- --------------------
          0.780         1
          1.0           2
          3.140         3
          5.120         4
          5.800         5
          12.340        6
          

          作为Tomtom replied,十进制不是存储版本号的好类型。最好将版本号存储在两个正整数列中,一个包含主要版本号,另一个包含次要版本号。

          【讨论】:

          • 当前版本号与连续版本比较,以小数形式存储。
          【解决方案10】:

          这是基于 SeanW 的回答,但此解决方案允许以下格式 [major].[minor].[build]。它可能用于 SQL 2K 并且当光标不是一个选项时。

          declare @v1 varchar(100) = '1.4.020'
          declare @v2 varchar(100) = '1.4.003'
          
          declare @v1_dot1_pos smallint   /*position - 1st version - 1st dot */
          declare @v1_dot2_pos smallint   /*position - 1st version - 2nd dot */
          declare @v2_dot1_pos smallint   /*position - 2nd version - 1st dot */
          declare @v2_dot2_pos smallint   /*position - 2nd version - 2nd dot */
          
          -------------------------------------------------
          -- get the pos of the first and second dots
          -------------------------------------------------
          SELECT 
          @v1_dot1_pos=CHARINDEX('.', @v1),
          @v2_dot1_pos=CHARINDEX('.', @v2),
          @v1_dot2_pos=charindex( '.', @v1, charindex( '.', @v1 ) + 1 ),
          @v2_dot2_pos=charindex( '.', @v2, charindex( '.', @v2 ) + 1 )
          
          
          -------------------------------------------------
          -- break down the parts
          -------------------------------------------------
          DECLARE @v1_major int, @v2_major int
          DECLARE @v1_minor int, @v2_minor int
          DECLARE @v1_build int, @v2_build int 
          
          SELECT 
              @v1_major = CONVERT(int,LEFT(@v1,@v1_dot1_pos-1)),
              @v1_minor = CONVERT(int,SUBSTRING(@v1,@v1_dot1_pos+1,(@v1_dot2_pos-@v1_dot1_pos)-1)),
              @v1_build = CONVERT(int,RIGHT(@v1,(LEN(@v1)-@v1_dot2_pos))),
              @v2_major = CONVERT(int,LEFT(@v2,@v2_dot1_pos-1)),
              @v2_minor = CONVERT(int,SUBSTRING(@v2,@v2_dot1_pos+1,(@v2_dot2_pos-@v2_dot1_pos)-1)),
              @v2_build = CONVERT(int,RIGHT(@v2,(LEN(@v2)-@v2_dot2_pos)))
          
          
          -------------------------------------------------
          -- return the difference
          -------------------------------------------------
          SELECT
              Case    
                  WHEN @v1_major < @v2_major then 'v2 is newer'
                  WHEN @v1_major > @v2_major then 'v1 is newer'
                  WHEN @v1_minor < @v2_minor then 'v2 is newer'
                  WHEN @v1_minor > @v2_minor then 'v1 is newer'
                  WHEN @v1_build < @v2_build then 'v2 is newer'
                  WHEN @v1_build > @v2_build then 'v1 is newer'
                  ELSE '!Same'
              END
          

          【讨论】:

            【解决方案11】:

            实施的解决方案:

            CREATE FUNCTION [dbo].[version_compare]
            (
                @v1 VARCHAR(5), @v2 VARCHAR(5)
            )
            RETURNS tinyint
            AS
            BEGIN
                DECLARE @v1_int tinyint, @v1_frc tinyint, 
                        @v2_int tinyint, @v2_frc tinyint, 
                        @ResultVar tinyint
            
                SET @ResultVar = 0
            
                SET @v1_int = CONVERT(tinyint, LEFT(@v1, CHARINDEX('.', @v1) - 1))
                SET @v1_frc = CONVERT(tinyint, RIGHT(@v1, LEN(@v1) - CHARINDEX('.', @v1)))
                SET @v2_int = CONVERT(tinyint, LEFT(@v2, CHARINDEX('.', @v2) - 1))
                SET @v2_frc = CONVERT(tinyint, RIGHT(@v2, LEN(@v2) - CHARINDEX('.', @v2)))
            
                SELECT @ResultVar = CASE
                    WHEN @v2_int > @v1_int THEN 2
                    WHEN @v1_int > @v2_int THEN 1
                    WHEN @v2_frc > @v1_frc THEN 2
                    WHEN @v1_frc > @v2_frc THEN 1
                ELSE 0 END
            
                -- Return the result of the function
                RETURN @ResultVar
            END
            GO
            

            【讨论】:

              【解决方案12】:

              此递归查询会将任何以“.”分隔的版本号转换为可比较的字符串,将每个元素左填充为 10 个字符,从而允许比较具有或不具有内部版本号的版本并适应非数字字符:

              WITH cte (VersionNumber) AS (
                SELECT '1.23.456' UNION ALL
                SELECT '2.3'      UNION ALL
                SELECT '0.alpha-3'
                ),
                parsed (VersionNumber, Padded) AS (
                SELECT
                  CAST(SUBSTRING(VersionNumber, CHARINDEX('.', VersionNumber) + 1, LEN(VersionNumber)) + '.' AS NVARCHAR(MAX)),
                  CAST(RIGHT(REPLICATE('0', 10) + LEFT(VersionNumber, CHARINDEX('.', VersionNumber) - 1), 10) AS NVARCHAR(MAX))
                FROM cte
                UNION ALL
                SELECT
                  SUBSTRING(VersionNumber, CHARINDEX('.', VersionNumber) + 1, LEN(VersionNumber)),
                  Padded + RIGHT(REPLICATE('0', 10) + LEFT(VersionNumber, CHARINDEX('.', VersionNumber) - 1), 10)
                FROM parsed WHERE CHARINDEX('.', VersionNumber) > 0
                )
              SELECT Padded
              FROM parsed
              WHERE VersionNumber = ''
              ORDER BY Padded;
              

              Padded
              ------------------------------
              0000000000000alpha-3
              000000000100000000230000000456
              00000000020000000003
              

              【讨论】:

                【解决方案13】:

                这是我通过修改在 StackOverflow 上找到的一些代码并自己编写一些代码所做的。这是代码的第 1 版,所以请告诉我您的想法。用法示例和测试用例在代码cmets中。

                如果不使用 SQL 2016 或更高版本并且您无权访问 STRING_SPLIT,请首先创建此函数:

                SET ANSI_NULLS ON
                GO
                SET QUOTED_IDENTIFIER ON
                GO
                -- =============================================
                -- Author:      <Author,,Name>
                -- Create date: <Create Date,,>
                -- Description: modified from https://stackoverflow.com/questions/10914576/t-sql-split-string/42000063#42000063
                -- =============================================
                CREATE FUNCTION [dbo].[SplitStringToRows]
                (   
                    @List VARCHAR(4000) 
                    , @Delimiter VARCHAR(50)
                )
                RETURNS TABLE 
                AS
                RETURN 
                (
                    --For testing
                    -- SELECT * FROM SplitStringToRows ('1.0.123','.')
                    -- DECLARE @List VARCHAR(MAX) = '1.0.123', @Delimiter VARCHAR(50) = '.';
                
                    WITH Casted AS
                    (
                        SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@List,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
                    )
                    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Index]
                    , x.value(N'.',N'nvarchar(max)') AS Part 
                    FROM Casted
                    CROSS APPLY SplitMe.nodes(N'/x') AS A(x)
                )
                

                然后创建这个函数:

                SET ANSI_NULLS ON
                GO
                SET QUOTED_IDENTIFIER ON
                GO
                -- =============================================
                -- Author:      Soenhay
                -- Create date: 7/1/2017
                -- Description: Returns -1 if VersionStringA is less than VersionStringB.
                --              Returns 0 if VersionStringA equals VersionStringB.
                --              Returns 1 if VersionSTringA is greater than VersionStringB.
                -- =============================================
                CREATE FUNCTION dbo.CompareVersionStrings
                (   
                    @VersionStringA VARCHAR(50)
                    ,@VersionStringB VARCHAR(50)
                )
                RETURNS TABLE 
                AS
                RETURN 
                (
                    --CurrentVersion should be of the form:
                    --major.minor[.build[.revision]] 
                    --This is the same as the versioning system used in c#.
                    --For applications the build and revision numbers will by dynamically set based on the current date and time of the build. 
                    --Example: [assembly: AssemblyFileVersion("1.123.*")]//http://stackoverflow.com/questions/15505841/the-version-specified-for-the-file-version-is-not-in-the-normal-major-minor-b
                    --Each component should be between 0 and 65534 ( UInt16.MaxValue - 1 )
                    --Max version number would be 65534.65534.65534.65534
                
                    --For Testing 
                    -- SELECT * FROM dbo.CompareVersionStrings('', '')
                    -- SELECT * FROM dbo.CompareVersionStrings('asdf.asdf', 'asdf.asdf') --returns 0
                    -- SELECT * FROM dbo.CompareVersionStrings('asdf', 'fdas') --returns -1 
                    -- SELECT * FROM dbo.CompareVersionStrings('zasdf', 'fdas') --returns 1 
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.1.123.123')  --Should return -1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.123.123')  --Should return 0
                    -- SELECT * FROM dbo.CompareVersionStrings('1.1.123.123', '1.0.123.123')  --Should return 1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.124.123')  --Should return -1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.124.123', '1.0.123.123')  --Should return 1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.123.123', '1.0.123.124')  --Should return -1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0.123.124', '1.0.123.123')  --Should return 1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0', '1.1')  --Should return -1
                    -- SELECT * FROM dbo.CompareVersionStrings('1.0', '1.0')  --Should return 0
                    -- SELECT * FROM dbo.CompareVersionStrings('1.1', '1.0')  --Should return 1
                    -- Declare @VersionStringA VARCHAR(50) = '' ,@VersionStringB VARCHAR(50) = '' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.0.123.123' ,@VersionStringB VARCHAR(50) = '1.1.123.123' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.1.123.123' ,@VersionStringB VARCHAR(50) = '1.1.123.123' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.2.123.123' ,@VersionStringB VARCHAR(50) = '1.1.123.123' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.1.123' ,@VersionStringB VARCHAR(50) = '1.1.123.123' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.1.123.123' ,@VersionStringB VARCHAR(50) = '1.1.123' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.1' ,@VersionStringB VARCHAR(50) = '1.1' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.2' ,@VersionStringB VARCHAR(50) = '1.1' ;
                    -- Declare @VersionStringA VARCHAR(50) = '1.1' ,@VersionStringB VARCHAR(50) = '1.2' ;
                
                    WITH 
                    Indexes AS
                    (
                        SELECT 1 AS [Index]
                            , 'major' AS Name
                        UNION
                        SELECT 2
                            , 'minor'
                        UNION
                        SELECT 3
                            , 'build'
                        UNION
                        SELECT 4
                            , 'revision'
                    )
                    , SplitA AS
                    (
                        SELECT * FROM dbo.SplitStringToRows(@VersionStringA, '.')
                    )
                    , SplitB AS
                    (
                        SELECT * FROM dbo.SplitStringToRows(@VersionStringB, '.')
                    )
                    SELECT
                        CASE WHEN major = 0 THEN
                                CASE WHEN minor = 0 THEN
                                                    CASE WHEN build = 0 THEN
                                                                        CASE WHEN revision = 0 THEN 0
                                                                        ELSE revision END
                                                        ELSE build END
                                    ELSE minor END
                            ELSE major END AS Compare
                    FROM
                    (
                        SELECT 
                             MAX(CASE WHEN [Index] = 1 THEN Compare ELSE NULL END) AS major
                            ,MAX(CASE WHEN [Index] = 2 THEN Compare ELSE NULL END) AS minor
                            ,MAX(CASE WHEN [Index] = 3 THEN Compare ELSE NULL END) AS build
                            ,MAX(CASE WHEN [Index] = 4 THEN Compare ELSE NULL END) AS revision
                        FROM(
                            SELECT [Index], Name, 
                                CASE WHEN A = B THEN 0
                                    WHEN A < B THEN -1
                                    WHEN A > B THEN 1
                                    END AS Compare
                            FROM
                            (
                                SELECT 
                                     i.[Index]
                                    ,i.Name
                                    ,ISNULL(a.Part, 0) AS A
                                    ,ISNULL(b.Part, 0) AS B
                                FROM Indexes i
                                    LEFT JOIN SplitA a
                                ON  a.[Index] = i.[Index]
                                    LEFT JOIN SplitB b
                                ON  b.[Index] = i.[Index]
                            ) q1
                        ) q2
                    ) q3
                
                )
                GO
                

                【讨论】:

                  【解决方案14】:

                  我创造了这个功能(灵感来自 Eva Lacy(上图)):

                  CREATE or alter function dbo.IsVersionNewerThan
                  (
                      @Source nvarchar(max),
                      @Target nvarchar(max)
                  )
                  RETURNS table
                  as 
                  /*
                  -1 : target has higher version number (later version)
                  0 : same
                  1 : source has higher version number (later version)
                  
                  test harness:
                  ; WITH tmp
                  AS
                  (
                      SELECT '1.0.0.5' AS Version
                      UNION ALL SELECT '0.0.0.0'
                      UNION ALL SELECT '1.5.0.6'
                      UNION ALL SELECT '2.0.0'
                      UNION ALL SELECT '2.0.0.0'
                      UNION ALL SELECT '2.0.1.1'
                      UNION ALL SELECT '15.15.1323.22'
                      UNION ALL SELECT '15.15.622.55'
                  )
                  SELECT tmp.version, isGreather from tmp
                  outer apply (select * from dbo.IsVersionNewerThan(tmp.Version, '2.0.0.0')) as IsG
                  
                  */ 
                      return (
                          select CASE 
                              when cast('/' + @Source + '/' as hierarchyid) > cast('/' + @Target + '/' as hierarchyid) THEN 1 
                              when @Source = @Target then 0
                              else -1 
                          end as IsGreather
                      )
                  go
                  

                  测试脚本作为注释包含在内。 它可以工作,只要您没有像“1.5.06.2”这样的版本(注意零)。
                  SQL Server 认为这个函数有is_inlineable = 1,这对性能来说是个好兆头。

                  那么我的SQL代码可以是这样的:

                  declare @version varchar(10) = '2.30.1.12'
                  set @version = '2.30.1.1'
                  if exists(select * from dbo.IsVersionNewerThan(@version,'2.30.1.12') where IsGreather >= 0)
                  BEGIN
                      print 'yes'
                  end
                  else print 'no'
                  

                  【讨论】:

                    【解决方案15】:

                    我会给你这个最最短的答案。

                    with cte as (
                        select  7.11 as ver
                        union all
                        select 7.6
                    )
                    
                    select top 1 ver from cte
                          order by parsename(ver, 2), parsename(cast(ver as float), 1)
                    

                    【讨论】:

                    • 这不适用于超过两位数的版本。例如:1.3.0.
                    【解决方案16】:

                    也许将内部版本号转换为值有助于了解内部版本之间的层次结构。

                    DECLARE @version VARCHAR(25), @dot1 AS TINYINT, @dot2 AS TINYINT, @dot3 AS TINYINT, @MaxPower AS TINYINT, @Value AS BIGINT
                    SELECT @version = CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR) --'14.0.1000.169' --'10.50.1600'
                    SELECT @dot1 = CHARINDEX('.', @version, 1)
                    SELECT @dot2 = CHARINDEX('.', @version, @dot1 + 1)
                    SELECT @dot3 = CHARINDEX('.', @version, @dot2 + 1)
                    SELECT @dot3 = CASE
                        WHEN @dot3 = 0 THEN LEN(@version) + 1
                        ELSE @dot3
                    END
                    
                    SELECT @MaxPower = MAX(DotColumn) FROM (VALUES (@dot1-1), (@dot2-@dot1-1), (@dot3-@dot2-1)) AS DotTable(DotColumn)
                    SELECT @Value = POWER(10, @MaxPower)
                    --SELECT @version, @dot1, @dot2, @dot3, @MaxPower, @Value
                    
                    SELECT 
                    --  @version AS [Build], 
                        CAST(LEFT(@version, @dot1-1) AS INT) * POWER(@Value, 3) +
                        CAST(SUBSTRING(@version, @dot1+1, @dot2-@dot1-1) AS INT) * POWER(@Value, 2) +
                        CAST(SUBSTRING(@version, @dot2+1, @dot3-@dot2-1) AS INT) * @Value +
                        CASE
                            WHEN @dot3 = LEN(@version)+1 THEN CAST(0 AS INT)
                            ELSE CAST(SUBSTRING(@version, @dot3+1, LEN(@version)-@dot3) AS INT)
                        END AS [Value]
                    

                    【讨论】:

                      【解决方案17】:

                      灵感来自@Sean 的回答,因为我需要 4 个部分,所以我写了这个(而且它很容易模块化,在代码末尾评论函数):

                      CREATE OR REPLACE FUNCTION compareversions(v1 text,v2 text)
                          RETURNS smallint
                          LANGUAGE 'plpgsql'
                          VOLATILE
                          PARALLEL UNSAFE
                          COST 100
                      AS $$
                      declare res int;
                      -- Set parts into variables (for now part 1 to 4 are used)
                      -- IMPORTANT: if you want to add part(s) think to add:
                      --   - Setting of part(s) to 0 in "Convert all empty or null parts to 0" below
                      --   - Proper tests in select/case below
                      -- IMPORTANT: do not use CAST here since it will lead to syntax error if a version or part is empty
                      -- v1
                      declare v1_1 text := split_part(v1, '.', 1);
                      declare v1_2 text := split_part(v1, '.', 2);
                      declare v1_3 text := split_part(v1, '.', 3);
                      declare v1_4 text := split_part(v1, '.', 4);
                      -- v2
                      declare v2_1 text := split_part(v2, '.', 1);
                      declare v2_2 text := split_part(v2, '.', 2);
                      declare v2_3 text := split_part(v2, '.', 3);
                      declare v2_4 text := split_part(v2, '.', 4);
                      
                      begin
                          -- Convert all empty or null parts to 0
                          -- v1
                          if v1_1 = '' or v1_1 is null then v1_1 = '0'; end if;
                          if v1_2 = '' or v1_2 is null then v1_2 = '0'; end if;
                          if v1_3 = '' or v1_3 is null then v1_3 = '0'; end if;
                          if v1_4 = '' or v1_4 is null then v1_4 = '0'; end if;
                          -- v2
                          if v2_1 = '' or v2_1 is null then v2_1 = '0'; end if;
                          if v2_2 = '' or v2_2 is null then v2_2 = '0'; end if;
                          if v2_3 = '' or v2_3 is null then v2_3 = '0'; end if;
                          if v2_4 = '' or v2_4 is null then v2_4 = '0'; end if;
                      
                          select
                              case
                              -------------
                              -- Compare first part:
                              --  - If v1_1 is inferior to v2_1 return -1 (v1 < v2),
                              --  - If v1_1 is superior to v2_1 return 1 (v1 > v2).
                              when CAST(v1_1 as int) < cast(v2_1 as int) then -1
                              when CAST(v1_1 as int) > cast(v2_1 as int) then 1
                              -------------
                      
                              -------------
                              -- v1_1 is equal to v2_1, compare second part:
                              --  - If v1_2 is inferior to v2_2 return -1 (v1 < v2),
                              --  - If v1_2 is superior to v2_2 return 1 (v1 > v2).
                              when CAST(v1_2 as int) < cast(v2_2 as int) then -1
                              when CAST(v1_2 as int) > cast(v2_2 as int) then 1
                              -------------
                      
                              -------------
                              -- v1_1 is equal to v2_1 and v1_2 is equal to v2_2, compare third part:
                              --  - If v1_3 is inferior to v2_3 return -1 (v1 < v2),
                              --  - If v1_3 is superior to v2_3 return 1 (v1 > v2).
                              when CAST(v1_3 as int) < cast(v2_3 as int) then -1
                              when CAST(v1_3 as int) > cast(v2_3 as int) then 1
                              -------------
                      
                              -------------
                              -- Etc..., continuing with fourth part:
                              when CAST(v1_4 as int) < cast(v2_4 as int) then -1
                              when CAST(v1_4 as int) > cast(v2_4 as int) then 1
                              -------------
                      
                              -- All parts are equals, meaning v1 == v2, return 0
                              else 0
                              end
                              into res;
                      
                              return res;
                      end;
                      $$;
                      ;
                      
                      COMMENT ON FUNCTION compareversions(v1 text,v2 text)
                          IS 'Function to compare 2 versions as strings, versions can have from 1 to 4 parts (e.g. "1", "2.3", "3.4.5", "5.6.78.9") but it is easy to add a part.
                      A version having less than 4 parts is considered having its last part(s) set to 0, i.e. "2.3" is considered as "2.3.0.0" so that comparing "1.2.3" to "1.2.3.0" returns "equal"). Indeed we consider first part is always major, second minor, etc ... whatever the number of part for any version.
                      Function returns:
                          - -1 when v1 < v2
                          - 1 when v1 > v2
                          - 0 when v1 = v2
                      And, according to return value:
                          - To compare if v1 < v2 check compareversions(v1, v2) == -1
                          - To compare if v1 > v2 check compareversions(v1, v2) == 1
                          - To compare if v1 == v2 check compareversions(v1, v2) == 0
                          - To compare if v1 <= v2 check compareversions(v1, v2) <= 0
                          - To compare if v1 >= v2 check compareversions(v1, v2) >= 0'
                      ;
                      

                      例如,您还可以将版本“1.2”与“1.2.1”(将返回-1,v1

                      而且它也很容易适应另一种版本格式,例如 X.Y-Z,v1_1 等...将是(未经测试,但你明白了):

                      -- v1_1 = X
                      declare v1_1 text := split_part(v1, '.', 1);
                      -- tmp = Y-Z
                      declare tmp text := split_part(v1, '.', 2);
                      -- v1_2 = Y
                      declare v1_2 text := split_part(tmp, '-', 1);
                      -- v1_3 = Z
                      declare v1_3 text := split_part(tmp, '-', 2);
                      -- do the same for v2
                      

                      【讨论】:

                        【解决方案18】:

                        @MartinSmith 答案最适合最多 5 位小数,但如果超过 5 位(这可能很少见)。这是我可以做的:

                        DECLARE @AppVersion1 VARCHAR(20) = '2.7.2.2.3.1'
                        DECLARE @AppVersion2 VARCHAR(20) = '2.7.2.2.4'
                        
                        DECLARE @V1 AS INT = CASE WHEN LEN(@AppVersion1) < LEN(@AppVersion2) THEN CAST(REPLACE(@AppVersion2,'.','') AS INT) ELSE CAST(REPLACE(@AppVersion1,'.','') AS INT) END;
                        DECLARE @V2 AS INT = CASE WHEN LEN(@AppVersion1) < LEN(@AppVersion2) THEN CAST(REPLACE(@AppVersion1,'.','') AS INT) ELSE CAST(REPLACE(@AppVersion2,'.','') AS INT) END;
                        
                        IF(LEN(@V2)< LEN(@V1))
                            BEGIN
                                SET @V2 = CAST( LTRIM(CAST(@V2 AS VARCHAR)) + ISNULL(REPLICATE('0',LEN(@V1)-LEN(@V2)),'') AS INT); 
                            END;
                        
                        SELECT CASE WHEN @V1 > @V2 THEN 'Y' ELSE 'N' END
                        

                        【讨论】:

                          猜你喜欢
                          • 2011-06-24
                          • 2011-10-13
                          • 2022-11-14
                          • 1970-01-01
                          • 1970-01-01
                          • 2013-04-22
                          相关资源
                          最近更新 更多