SQL按版本“number”排序,这是一个长度不一的字符串

时间:2009-02-09 16:24:25

标签: sql

我正在尝试创建一个SQL查询,它将按版本号(例如1.1,4.5.10等)对结果进行排序

这是我试过的:

SELECT * FROM Requirements 
    WHERE Requirements.Release NOT LIKE '%Obsolete%' 
    ORDER BY Requirements.ReqNum

现在,ReqNum字段是一个字符串字段,遗憾的是我无法将其更改为浮点数或其类似内容,因为我的需求数字为162.1.11

当我得到结果时,我会得到这样的命令:

1.1
1.10
1.11
1.3

如何编写将按字典顺序排序的查询?

......或,

如何正确排序数据?

感谢提前输入!

22 个答案:

答案 0 :(得分:24)

在PostgreSQL中你可以这样做:

SELECT * FROM Requirements
ORDER BY string_to_array(version, '.')::int[];

这最后一个::int[]使它将字符串值转换为整数,然后进行比较。

答案 1 :(得分:24)

为获得最佳结果,请重构版本号存储,以便每个部分都有自己的列:MajorVersion,MinorVersion,Revision,Build。然后订购问题突然变得微不足道。您还可以构建计算列以便轻松检索完整字符串。

答案 2 :(得分:13)

SELECT * FROM Requirements 
WHERE Requirements.Release NOT LIKE '%Obsolete%' 
ORDER BY cast('/' + replace(Requirements.ReqNum , '.', '/') + '/' as hierarchyid);

答案 3 :(得分:7)

@ vuttipong-l答案(T-SQL)略有不同

SELECT VersionNumber
FROM (
SELECT '6.1.3' VersionNumber UNION
SELECT '6.11.3' UNION
SELECT '6.2.3' UNION
SELECT '6.1.12' 
) AS q
ORDER BY cast('/' + VersionNumber + '/' as hierarchyid)

在从2008开始的SQL Server中工作,在hierarchyid列的字符串表示中点是正常的,因此我们不需要用斜杠替换它们。 来自doc:

的引用
  

通过比较分离的整数序列进行比较   按字典顺序点。

但有一点需要注意:版本段不得以零为前缀。

答案 4 :(得分:4)

如果你在SQL Server的土地......

DECLARE @string varchar(40)
SET @string = '1.2.3.4'
SELECT PARSENAME(@string, 1), PARSENAME(@string, 2), PARSENAME(@string, 3), PARSENAME(@string, 4)

结果: 4,3,2,1

用于解析IP地址和其他虚线项目,例如版本号。 (您可以使用REPLACE()将项目转换为点分表示法......例如1-2-3-4 - > 1.2.3.4)

答案 5 :(得分:3)

如果你没有像Joel Coehoorn明智地建议的那样重新设计表格,那么你需要将版本号重新格式化为一个按你的要求排序的字符串,例如

  • 1.1 - > 0001.0001.0000
  • 162.1.11 - > 0162.0001.0011

如果您的DBMS具有这些功能,可以通过函数或使用计算/虚拟列来完成。然后,您可以在ORDER BY子句中使用该函数或列。

答案 6 :(得分:3)

以下函数将采用版本号并将每个级别格式化为3位数:

用法:

select * from TableX order by dbo.fn_VersionPad(VersionCol1)

功能:

CREATE FUNCTION [dbo].[fn_VersionPad]
(
    @version varchar(20)
)
RETURNS varchar(20)
AS
BEGIN
    /*
        Purpose:  Pads multi-level Version Number sections to 3 digits
        Example:  1.2.3.4
        Returns:  001.002.003.004
    */

    declare @verPad varchar(20)
    declare @i int
    declare @digits int

    set @verPad = ''

    set @i = len(@version)
    set @digits = 0

    while @i > 0
    begin
        if (substring(@version, @i, 1) = '.')
        begin
            while (@digits < 3)
            begin
                -- Pad version level to 3 digits
                set @verPad = '0' + @verPad
                set @digits = @digits + 1
            end

            set @digits = -1
        end

        set @verPad = substring(@version, @i, 1) + @verPad

        set @i = @i - 1
        set @digits = @digits + 1
    end

    while (@digits < 3)
    begin
        -- Pad version level to 3 digits
        set @verPad = '0' + @verPad
        set @digits = @digits + 1
    end

    return @verPad
END

答案 7 :(得分:2)

不是美国代码

Insert into @table
Select 'A1' union all
Select 'A3' union all
Select 'A5' union all
Select 'A15' union all
Select 'A11' union all
Select 'A10' union all
Select 'A2' union all
Select 'B2' union all
Select 'C2' union all
Select 'C22' union all
Select 'C221' union all
Select 'A7' 

Select cod from @table
Order by LEN(cod),cod 

结果:

A1
A2
A3
A5
A7
B2
C2
A10
A11
A15
C22
C221

这很简单:

Declare @table table(id_ int identity(1,1), cod varchar(10))

Insert into @table
Select 'A1' union all
Select 'A3' union all
Select 'A5' union all
Select 'A15' union all
Select 'A11' union all
Select 'A10' union all
Select 'A2' union all
Select 'A7' 

Select cod from @table
Order by LEN(cod),cod  

答案 8 :(得分:2)

在PostgreSQL上,它不容易:

SELECT ver_no FROM version ORDER BY string_to_array(ver_no, '.', '')::int[]

答案 9 :(得分:2)

您可以使用CHARINDEX / SUBSTR和ORDER BY将不同部分拆分为字符串(您已经知道分隔符:“。”)。在功能中执行或按部分执行。

它不会很漂亮而且不会很快:所以如果您需要快速查询,请关注Tony或Joel。

答案 10 :(得分:1)

如果您使用的是Microsoft SQL Server,则可以使用此功能:

create function fnGetVersion (@v AS varchar(50)) returns bigint as
begin
declare @n as bigint;
declare @i as int;
select @n = 0;
select @i = charindex('.',@v);
while(@i > 0)
begin
    select @n = @n * 1000;
    select @n = @n + cast(substring(@v,1,@i-1) as bigint); 
    select @v = substring(@v,@i+1,len(@v)-@i);
    select @i = charindex('.',@v);
end
return @n * 1000 + cast(@v as bigint);
end

运行此命令进行测试:

select dbo.fnGetVersion('1.2.3.4')

这将返回数字1002003004,它是完全可排序的。你需要9.0.1大于2.1.2.3然后你需要稍微改变逻辑。在我的例子中,9.0.1将在2.1.2.3之前进行排序。

答案 11 :(得分:1)

PostgreSQL的功能

只需使用

select *
  from sample_table
 order by _sort_version(column_version);




CREATE FUNCTION _sort_version (
  p_version text
)
RETURNS text AS
$body$
declare 
  v_tab text[];
begin
  v_tab := string_to_array(p_version, '.');  

  for i in 1 .. array_length(v_tab, 1) loop
    v_tab[i] := lpad(v_tab[i], 4, '0');
  end loop;

  return array_to_string(v_tab, '.');
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY DEFINER
COST 1;

答案 12 :(得分:1)

FİXEDTHİSWAY。

<pre>
00000001    1
00000001.00000001   1.1
00000001.00000001.00000001  1.1.1
00000001.00000002   1.2
00000001.00000009   1.9
00000001.00000010   1.10
00000001.00000011   1.11
00000001.00000012   1.12
00000002    2
00000002.00000001   2.1
00000002.00000001.00000001  2.1.1
00000002.00000002   2.2
00000002.00000009   2.9
00000002.00000010   2.10
00000002.00000011   2.11
00000002.00000012   2.12

select * from (select '000000001' as tCode,'1' as Code union
select '000000001.000000001' as tCode,'1.1'as Code union
select '000000001.000000001.000000001' as tCode,'1.1.1'as Code union
select '000000001.000000002' as tCode,'1.2'  union
select '000000001.000000010' as tCode,'1.10'as Code union
select '000000001.000000011' as tCode,'1.11'as Code union
select '000000001.000000012' as tCode,'1.12'as Code union
select '000000001.000000009' as tCode,'1.9' as Code
union
select '00000002' as tCode,'2'as Code union
select '00000002.00000001' as tCode,'2.1'as Code union
select '00000002.00000001.00000001' as tCode,'2.1.1'as Code union
select '00000002.00000002' as tCode,'2.2'as Code union
select '00000002.00000010' as tCode,'2.10'as Code union
select '00000002.00000011' as tCode,'2.11'as Code union
select '00000002.00000012' as tCode,'2.12'as Code union
select '00000002.00000009' as tCode,'2.9'as Code ) as t
order by t.tCode

</pre>

<pre>


public static string GenerateToCodeOrder(this string code)
    {
        var splits = code.Split('.');
        var codes = new List<string>();
        foreach (var str in splits)
        {
            var newStr = "";
            var zeroLength = 10 - str.Length;
            for (int i = 1; i < zeroLength; i++)
            {
                newStr += "0";
            }
            newStr += str;
            codes.Add(newStr);
        }
        return string.Join(".", codes);
    }

</pre>

答案 13 :(得分:0)

对于一体化查询纯粹主义者,假设Oracle,一些instr / substr / decode / to_number voodoo可以解决它:

SELECT *
FROM Requirements
WHERE Release NOT LIKE '%Obsolete%'
ORDER BY
    to_number(
      substr( reqnum, 1, instr( reqnum, '.' ) - 1 )
    )
  , to_number(
      substr( 
          reqnum
        , instr( reqnum, '.' ) + 1 -- start: after first occurance
        , decode( 
              instr( reqnum, '.', 1, 2 )
            , 0, length( reqnum )
            , instr( reqnum, '.', 1, 2 ) - 1 
          ) -- second occurance (or end)
          - instr( reqnum, '.', 1, 1) -- length: second occurance (or end) less first
      )
    )
  , to_number(
      decode( 
          instr( reqnum, '.', 1, 2 )
        , 0, null
        , substr( 
              reqnum
            , instr( reqnum, '.', 1, 2 ) + 1 -- start: after second occurance
            , decode( 
                  instr( reqnum, '.', 1, 3 )
                , 0, length( reqnum )
                , instr( reqnum, '.', 1, 3 ) - 1 
              ) -- third occurance (or end)
              - instr( reqnum, '.', 1, 2) -- length: third occurance (or end) less second
          ) 
      )
    )
  , to_number(
      decode( 
          instr( reqnum, '.', 1, 3 )
        , 0, null
        , substr( 
              reqnum
            , instr( reqnum, '.', 1, 3 ) + 1 -- start: after second occurance
            , decode( 
                  instr( reqnum, '.', 1, 4 )
                , 0, length( reqnum )
                , instr( reqnum, '.', 1, 4 ) - 1 
              ) -- fourth occurance (or end)
              - instr( reqnum, '.', 1, 3) -- length: fourth occurance (or end) less third
          ) 
      )
    )
;

我怀疑有很多警告包括:

  • 假设存在次要版本(第二个)
  • 仅限于问题评论中指定的四个版本

答案 14 :(得分:0)

好的,如果高性能是一个问题,那么您唯一的选择就是将您的值更改为数字。

但是,如果这是一个低使用率的查询,那么您可以按这些分割您的数字和顺序。

此查询仅假设主要版本号和次要版本号,并且它们仅包含数字。

SELECT
    *
FROM
    Requirements
WHERE
    Requirements.Release NOT LIKE '%Obsolete%'
ORDER BY
    CONVERT(int, RIGHT(REPLICATE('0', 10) + LEFT(Requirements.ReqNum, CHARINDEX('.', Requirements.ReqNum)-1), 10)),
    CONVERT(int, SUBSTRING(Requirements.ReqNum, CHARINDEX('.', Requirements.ReqNum )+1, LEN(Requirements.ReqNum) - CHARINDEX('.', Requirements.ReqNum )))

答案 15 :(得分:0)

这是一个提取字符串的示例查询。您应该能够在数据库的UPDATE重构中使用它,或者只是在查询中使用它。但是,我不确定它是如何准时的;只需要注意和测试。

SELECT SUBSTRING_INDEX("1.5.32",'.',1) AS MajorVersion,
  SUBSTRING_INDEX(SUBSTRING_INDEX("1.5.32",'.',-2),'.',1) AS MinorVersion,
  SUBSTRING_INDEX("1.5.32",'.',-1) AS Revision;

这将返回:

MajorVersion | MinorVersion | Revision
1            | 5            | 32

答案 16 :(得分:0)

这里是PostgreSQL的比较函数,它将比较任意字符串,以便数字序列在数字上进行比较。换句话说,&#34; ABC123&#34; &GT; &#34; ABC2&#34;,但&#34; AB123&#34; &LT; &#34; ABC2&#34 ;.它返回-1,0或+1,因为这样的比较函数通常会这样做。

CREATE FUNCTION vercmp(a text, b text) RETURNS integer AS $$
DECLARE
   ar text[];
   br text[];
   n integer := 1;
BEGIN
   SELECT array_agg(y) INTO ar FROM (SELECT array_to_string(regexp_matches(a, E'\\d+|\\D+|^$', 'g'),'') y) x;
   SELECT array_agg(y) INTO br FROM (SELECT array_to_string(regexp_matches(b, E'\\d+|\\D+|^$', 'g'),'') y) x;
   WHILE n <= array_length(ar, 1) AND n <= array_length(br, 1) LOOP
      IF ar[n] ~ E'^\\d+$' AND br[n] ~ E'^\\d+$' THEN
         IF ar[n]::integer < br[n]::integer THEN
            RETURN -1;
         ELSIF ar[n]::integer > br[n]::integer THEN
            RETURN 1;
         END IF;
      ELSE
         IF ar[n] < br[n] THEN
            RETURN -1;
         ELSIF ar[n] > br[n] THEN
            RETURN 1;
         END IF;
      END IF;
      n := n + 1;
   END LOOP;

   IF n > array_length(ar, 1) AND n > array_length(br, 1) THEN
      RETURN 0;
   ELSIF n > array_length(ar, 1) THEN
      RETURN 1;
   ELSE
      RETURN -1;
   END IF;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

然后,您可以创建一个运算符类,以便可以使用ORDER BY field USING <#的比较函数完成排序:

CREATE OR REPLACE FUNCTION vernum_lt(a text, b text) RETURNS boolean AS $$
BEGIN
   RETURN vercmp(a, b) < 0;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION vernum_lte(a text, b text) RETURNS boolean AS $$
BEGIN
   RETURN vercmp(a, b) <= 0;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION vernum_eq(a text, b text) RETURNS boolean AS $$
BEGIN
   RETURN vercmp(a, b) = 0;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION vernum_gt(a text, b text) RETURNS boolean AS $$
BEGIN
   RETURN vercmp(a, b) > 0;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION vernum_gte(a text, b text) RETURNS boolean AS $$
BEGIN
   RETURN vercmp(a, b) >= 0;
END;
$$ IMMUTABLE LANGUAGE plpgsql;

CREATE OPERATOR <# ( PROCEDURE = vernum_lt, LEFTARG = text, RIGHTARG = text);
CREATE OPERATOR ># ( PROCEDURE = vernum_gt, LEFTARG = text, RIGHTARG = text);
CREATE OPERATOR =# ( PROCEDURE = vernum_lte, LEFTARG = text, RIGHTARG = text);
CREATE OPERATOR <=# ( PROCEDURE = vernum_lte, LEFTARG = text, RIGHTARG = text);
CREATE OPERATOR >=# ( PROCEDURE = vernum_gte, LEFTARG = text, RIGHTARG = text);

CREATE OPERATOR CLASS vernum_ops FOR TYPE varchar USING btree AS
  OPERATOR 1 <# (text, text),
  OPERATOR 2 <=# (text, text),
  OPERATOR 3 =#(text, text),
  OPERATOR 4 >=# (text, text),
  OPERATOR 5 ># (text, text),
  FUNCTION 1 vercmp(text, text)
;

答案 17 :(得分:0)

我遇到了同样的问题,虽然我的公寓号码如A1,A2,A3,A10,A11等,他们想要“正确”排序。如果将版本号拆分为单独的列不起作用,请尝试此PL / SQL。它需要一个像A1或A10这样的字符串,并将其扩展为A0000001,A0000010等,所以它排序很好。只需在ORDER BY子句中调用它,如

选择apt_num 从公寓 按PAD(apt_num)订购

function pad(inString IN VARCHAR2)
   return VARCHAR2

--This function pads the numbers in a alphanumeric string.
--It is particularly useful in sorting, things like "A1, A2, A10"
--which would sort like "A1, A10, A2" in a standard "ORDER BY name" clause
--but by calling "ORDER BY pkg_sort.pad(name)" it will sort as "A1, A2, A10" because this
--function will convert it to "A00000000000000000001, A00000000000000000002, A00000000000000000010" 
--(but since this is in the order by clause, it will
--not be displayed.

--currently, the charTemplate variable pads the number to 20 digits, so anything up to 99999999999999999999 
--will work correctly.
--to increase the size, just change the charTemplate variable.  If the number is larger than 20 digits, it will just
--appear without padding.


   is
      outString VARCHAR2(255);
      numBeginIndex NUMBER;
      numLength NUMBER;
      stringLength NUMBER;
      i NUMBER;
      thisChar VARCHAR2(6);
      charTemplate VARCHAR2(20) := '00000000000000000000';
      charTemplateLength NUMBER := 20;


   BEGIN
      outString := null;
      numBeginIndex := -1;
      numLength := 0;
      stringLength := length(inString);

      --loop through each character, get that character
      FOR i IN 1..(stringLength) LOOP
         thisChar := substr(inString, i, 1);

         --if this character is a number
         IF (FcnIsNumber(thisChar)) THEN

            --if we haven't started a number yet
            IF (numBeginIndex = -1) THEN
               numBeginIndex := i;
               numLength := 1;

            --else if we're in a number, increase the length
            ELSE 
               numLength := numLength + 1;
            END IF;

            --if this is the last character, we have to append the number
            IF (i = stringLength) THEN
               outString:= FcnConcatNumber(inString, outString, numBeginIndex, numLength, charTemplate, charTemplateLength);
            END IF;

         --else this is a character
         ELSE

            --if we were previously in a number, concat that and reset the numBeginIndex
            IF (numBeginIndex != -1) THEN
               outString:= FcnConcatNumber(inString, outString, numBeginIndex, numLength, charTemplate, charTemplateLength);
               numBeginIndex := -1;
               numLength := 0;
            END IF;

            --concat the character
            outString := outString || thisChar;
         END IF;
      END LOOP;

      RETURN outString;

   --any exception, just return the original string
   EXCEPTION WHEN OTHERS THEN
      RETURN inString;

   END;     

答案 18 :(得分:0)

在M $ SQL中,我遇到了带有一些数据的hierachyid问题...

select Convert(hierarchyid, '/' + '8.3.0000.1088' + '/')

要解决此问题,我使用了别名(取决于'。'作为分隔符)...

Order by
convert(int, reverse (Parsename( reverse(tblSoftware.softwareVersion) , 1))),
convert(int, reverse (Parsename( reverse(tblSoftware.softwareVersion) , 2))),
convert(int, reverse (Parsename( reverse(tblSoftware.softwareVersion) , 3))),
convert(int, reverse (Parsename( reverse(tblSoftware.softwareVersion) , 4))),
convert(int, reverse (Parsename( reverse(tblSoftware.softwareVersion) , 5)))

答案 19 :(得分:0)

以下是您可以在ORDER BY中使用的ORACLE表达式:

select listagg(substr('0000000000' || column_value,-9), '.') within group(order by rownum) from xmltable(replace(version, '.',','))

假设您的版本列只有点作为分隔符(任意数量的级别)。 (如果没有,则由您决定替换为translate(version, '.-', ',,')

答案 20 :(得分:-2)

我会像Joel Coehoorn所说的那样做。然后重新安排您的数据结构,您不必手动执行。您可以编写一个简单的脚本来完成所有600条记录的工作。

答案 21 :(得分:-5)

只需删除点(内联,替换为空字符串),将结果转换为int并将结果order by。效果很好:

a.Version = 1.4.18.14

select...
Order by cast( replace (a.Version,'.','') as int)