SQL сортировка по версии «число», строка различной длины - PullRequest
27 голосов
/ 09 февраля 2009

Я пытаюсь создать запрос 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

Как мне написать запрос, который будет сортировать по лексикографическому порядку?

... или

Как правильно отсортировать данные?

Спасибо за ввод заранее!

Ответы [ 21 ]

1 голос
/ 02 августа 2013

НАСТОЯЩИЙ ЭТОТ ПУТЬ.

<code><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



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);
    }

1 голос
/ 16 августа 2012

Функция для 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;
1 голос
/ 09 февраля 2009

Это будет работать, если вы используете 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.

0 голосов
/ 08 августа 2018

В M $ SQL у меня были проблемы с иерархией с некоторыми данными ...

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

Чтобы обойти это, я использовал pasename (полагается на '.' Как разделитель) ...

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)))
0 голосов
/ 09 февраля 2009

Я бы поступил так, как сказал Джоэл Кохорн. Затем, чтобы перестроить структуру данных, вам не нужно делать это вручную. Вы можете написать простой скрипт, который выполнит эту работу для всех 600 записей.

0 голосов
/ 04 ноября 2014

Вот функция сравнения для PostgreSQL, которая будет сравнивать произвольные строки так, чтобы последовательности цифр сравнивались численно. Другими словами, «ABC123»> «ABC2», но «AB123» <«ABC2». Возвращает -1, 0 или +1, как обычно делают такие функции сравнения. </p>

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)
;
0 голосов
/ 10 декабря 2013

Просто удалите точки (Inline, замените пустой строкой) приведите результат как int и order by результат. Прекрасно работает:

a.Version = 1.4.18.14

select...
Order by cast( replace (a.Version,'.','') as int) 
0 голосов
/ 09 февраля 2009

У меня была такая же проблема, хотя у меня были номера квартир типа А1, А2, А3, А10, А11 и т. Д., Которые они хотели отсортировать «правильно». Если разделение номера версии на отдельные столбцы не работает, попробуйте этот 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;     
0 голосов
/ 10 февраля 2009

Для пуристов «все в одном», если предположить, что 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
          ) 
      )
    )
;

Я подозреваю, что есть много предостережений, включая:

  • Предположение о наличии минорной версии (второй)
  • ограничено четырьмя версиями, как указано в комментариях к вопросу
0 голосов
/ 09 февраля 2009

Хорошо, если проблема связана с высокой производительностью, единственный вариант - изменить значения на числовые.

Однако, если это запрос с низким уровнем использования, вы можете просто разделить свои числа и порядок по ним.

В этом запросе используются только основные и вспомогательные номера версий, и они содержат только цифры.

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 )))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...