如何使用SQL Server比较软件版本?

时间:2012-07-06 14:32:30

标签: sql sql-server tsql

当尝试比较软件版本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时会失败。

17 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:6)

我建议创建一个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
  • 开始传递完整版字符串

答案 2 :(得分:5)

这里有一个非常好的解决方案: 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

答案 3 :(得分:4)

两个步骤,首先比较小数点的左边和之后比较右边。


可能的解决方案:

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'

答案 4 :(得分:4)

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

假设:

  • 我的软件将包含一个包含当前版本号的配置设置
  • 数据驱动的配置行将包含最小版本号
  • 我需要能够选择min&lt; = current。
  • 的配置行

示例:

  • 版本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

答案 5 :(得分:2)

根据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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

你在问题​​中没有这么说,但在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,decimal不是存储版本号的好类型。最好将版本号存储在两个正整数列中,一个包含主版本号,另一个包含次版本号。

答案 8 :(得分:1)

这是基于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

答案 9 :(得分:1)

已实施的解决方案:

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

答案 10 :(得分:1)

您可以使用hierarchyid 您可以通过在结尾处放置/并将其转换为

来使用

e.g。

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

返回Y

答案 11 :(得分:1)

这种递归查询会将任何'。' - 分隔的版本号转换为可比较的字符串,将每个元素左边填充为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

答案 12 :(得分:0)

这是我通过修改我在StackOverflow上找到的一些代码并自己写一些代码所做的。这是代码的第1版,所以请让我知道你的想法。用法示例和测试用例在代码注释中。

如果不使用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

答案 13 :(得分:0)

我已经创建了此功能(从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'

答案 14 :(得分:0)

我会为您提供最最短的答案。

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)

答案 15 :(得分:0)

也许将内部版本号转换为值可以帮助理解内部版本之间的层次结构。

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]

答案 16 :(得分:0)

来自@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 < v2)进行比较,因为“1.2”将被视为“1.2.0”,它不是通常检查,但如果在一段时间内将数字添加到版本中,“1.2”实际上将被视为等于“1.2.0”。

而且它也可以很容易地针对其他版本格式进行模块化,例如 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