ORDER BY в SQL, где column - это синтетическая строка со встроенным целым числом - PullRequest
0 голосов
/ 27 ноября 2018

У меня есть следующая таблица:

CREATE TABLE TEST 
(
     name VARCHAR(10), 
     date_of_entry DATE, 
     flag1 INT, 
     flag2 INT,
     salary FLOAT, 
     flag3 INT,
     id INT
);

со следующими строками:

name    date_of_entry   flag1   flag2   salary      flag3   id
--------------------------------------------------------------
AGMA    2018-11-08      0       1       265466940   1       1
AGMA    2018-11-08      0       1       220737125   1       2
AGMA    2018-11-08      0       1       181270493   0       3
AGMA    2018-11-08      0       1       8584205     0       4

Я хотел бы выполнить следующий SQL, чтобы упорядочить строки определенным образом:

SELECT 
    name
    + '.' + CONVERT(varchar(8), date_of_entry, 112) 
    + '.' + CONVERT(varchar(1), flag1)
    + '.' + CONVERT(varchar(1), flag2)
    + '.' + CONVERT(varchar(2555), salary)
    + '.' + CONVERT(varchar(1), flag3)
    + '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM 
    TEST
ORDER BY 
    SYNTHETIC_ORDER DESC

Однако столбец зарплаты неправильно сортируется в строке.Таким образом, мой конечный результат (при выполнении в Microsoft SQL Server):

SYNTHETIC_ORDER
-----------------------------------
AGMA.20181108.0.1.8.58421e+006.0.4
AGMA.20181108.0.1.2.65467e+008.1.1
AGMA.20181108.0.1.2.20737e+008.1.2
AGMA.20181108.0.1.1.8127e+008.0.3

Как можно заметить, результатом является то, что идентификатор 4 идет первым, когда я хочу, чтобы идентификатор 1 шел первым.

Ожидаемый результат:

SYNTHETIC_ORDER
-----------------------------------
AGMA.20181108.0.1.2.65467e+008.1.1
AGMA.20181108.0.1.2.20737e+008.1.2
AGMA.20181108.0.1.1.8127e+008.0.3
AGMA.20181108.0.1.8.58421e+006.0.4

Есть ли способ убедиться, что зарплата правильно упорядочена в этом SQL?

Ответы [ 8 ]

0 голосов
/ 28 ноября 2018

Фикс фиксированной ширины и использует только функции, доступные как в H2 (без тегов), так и в SQLS (с тегами):

SELECT 
    CONCAT(
      CAST(name as CHAR(10)), --right pad to 10, 

      YEAR(date_of_entry),
      RIGHT(CONCAT('0',MONTH(date_of_entry)),2),
      RIGHT(CONCAT('0',DAY(date_of_entry)),2), --yyyymmdd

      CAST(flag1 as CHAR(1)), --rpad to 1, doesn't need cast if never null/0 length

      CAST(flag2 as CHAR(1)), --maybe doesn't need cast, see above

      RIGHT(CONCAT('0000000000', CAST(salary AS INT)),10), --lpad with 0 to 10 wide

      CAST(flag3 as CHAR(1)), --maybe doesn't need cast, see above

      RIGHT(CONCAT('0000000000', id), 10) --lpad with 0 to 10 wide

    ) AS SYNTHETIC_ORDER
FROM 
    TEST
ORDER BY 
    SYNTHETIC_ORDER DESC

Примечания:

  • Ваш оператор CREATE TABLE не упоминает ID, но ваш запрос делает;включенный идентификатор

  • В вашем запросе не упоминается NAME, но в вашем примере вывода данных;включенное ИМЯ

  • Возможно, вам не нужно так много указывать ID или зарплату

  • Приходите к приведениям к символам (например, к столбцам флагов)может быть отброшен (если столбец флага гарантированно на 100% всегда будет длиной 1 символ)

  • Если максимальное значение зарплаты в таблице больше, чем может удерживать int, рассмотрите приведение к чему-либоиначе

  • С заполнением зарплаты ведущими нулями, сортировка сработает.Нормализация его между 0 и 1 также может работать, если все значения были дополнены до одинаковой ширины, но вы, возможно, столкнетесь с проблемой того, что потеря точности (деление 10-значного оклада на 0,12456) приведет к тому, что два разных оклада будутобъединить, потому что не хватает цифр для полного представления.С любой точностью деления-квантования-на-ниже вы рискуете неправильно отсортировать исходные значения (например, если зарплаты 1000000000 и 1000000001 с идентификаторами 2 и 1 соответственно оба нормализуются до 0.123456, они в итоге будут отсортированы неправильно.вам, вероятно, понадобится столько цифр для ответа деления, сколько было вначале зарплаты, дополненной до фиксированной ширины, но если вы зашли так далеко, вы могли бы просто выложить все зарплаты либо на ширину самой широкойили до некоторой ширины, которая будет содержать их все. Здесь может быть полезно использовать приведение к int, если int будет переполнено. Вы можете принять решение добавить к одной цифре шире, чем int, и затем, если кто-то вставит большойзначение в будущем, и ваш запрос начинает терпеть неудачу из-за переполнения, он по крайней мере не будет молча давать неправильные результаты, потому что пэд обрезает цифры от левого края. При обращении к приведениям к битам вы можете выбрать, добавлять ли некоторую логику, которая дополняетДЛИНА () изстроковая форма зарплаты SELECT MAX

CONCAT хорош, потому что вы можете передавать на него большинство типов без предварительного приведения к varchar, и он не обнуляет все, если вы констатируете нульв отличие от обычных операций с конкататами строк с + или ||

0 голосов
/ 28 ноября 2018

Сэр,

Как только вы преобразуете его в огромную строку (varchar), он следует в алфавитном порядке, а не в том порядке, в котором вы ожидаете.

Разве вы не можете просто использовать row_number в качестве своего "синтетического заказа".Другими словами, вместо этого:

SELECT 
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112) 
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM 
   TEST
ORDER BY 
   SYNTHETIC_ORDER DESC

это:

SELECT 
    id,
    row_number() over (order by date_of_entry,flag1,flag2,salary,flag3,id) as SYNTHETIC_ORDER
FROM 
   TEST
ORDER BY 
   SYNTHETIC_ORDER DESC

Удачи!

0 голосов
/ 28 ноября 2018

Попробуйте использовать следующую процедуру.

В своем коде вы пишете CONVERT(varchar(2555), salary).Это не работает, потому что когда вы конвертируете число с плавающей точкой в ​​строку, используя команду convert, порядок сортировки результата не совпадает с порядком сортировки числа с плавающей точкой.например, 3 < 20, но '20' < '3'.

Процедура FloatToSortable решает эту проблему.Если вы пропустите последовательность операций и выполните сортировку по результатам, вы получите тот же порядок, что и при сортировке.например, FloatToSortable(3) < FloatToSortable(20).

И поэтому в вашем коде, где вы пишете

CONVERT(varchar(2555), salary)

, замените его на

dbo.FloatToSortable(salary).

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

-- FloatToSortable takes a FLOAT parameter and returns a string
-- such that the sort order of FLOATs X and Y will match the
-- sort order of strings F(X) and F(Y).
--
-- The internal format of FLOAT is an 8-byte double-precision
-- float, starting with the SIGN where 0=positive and 1=negative,
-- followed by the EXPONENT and then the MANTISSA. 
-- If it weren't for the SIGN we could just sort by the binary
-- value.  Because of the sign we need to XOR the binary
-- before we can sort on it.  
--
-- If the parameter is positive, XOR with 8000000000000000
-- If the parameter is negative, XOR with FFFFFFFFFFFFFFFF
--
-- Then we convert each byte to a Sortable string. We could
-- use hexidecimal, but it's simpler just use letters A..O
--
-- This function is working with salaries, so we don't have
-- to worry about NANs and Infinities, but it should work
-- with all values.

-- NybbleToSortable
-- Given an integer in range 0..15 return a character
-- We just map the number to a letter, 0 -> 'A', 15 -> 'O'

create function NybbleToSortable ( @a tinyint )
returns varchar(16)
as
begin
  return char(@a + ascii('A'))
end

go

-- XorToSortable
-- Take the nth byte of @a, XOR it with the nth byte of @b,
-- and convert that byte to a Sortable string.

create function dbo.XorToSortable ( @a varbinary(8), 
                                    @b varbinary(8), 
                                    @n int )
returns varchar(16)
as
begin
  declare @aa tinyint, @bb tinyint, @x tinyint
  set @aa = cast ( substring ( @a, @n, 1 ) as tinyint )
  set @bb = cast ( substring ( @b, @n, 1 ) as tinyint )
  set @x = @aa ^ @bb
  return   dbo.NybbleToSortable ( @x / 16 ) 
         + dbo.NybbleToSortable ( @x % 16 )
end

go

create function dbo.FloatToSortable ( @x float )
returns varchar(16)
as
begin
  declare @m varbinary(8), @b varbinary(8)

  set @b = cast(@x as varbinary(8))

  if @x < 0 
    set @m = 0xFFFFFFFFFFFFFFFF
  else
    set @m = 0x8000000000000000

  return   dbo.XorToSortable ( @b, @m, 1 )
         + dbo.XorToSortable ( @b, @m, 2 )
         + dbo.XorToSortable ( @b, @m, 3 )
         + dbo.XorToSortable ( @b, @m, 4 )
         + dbo.XorToSortable ( @b, @m, 5 )
         + dbo.XorToSortable ( @b, @m, 6 )
         + dbo.XorToSortable ( @b, @m, 7 )
         + dbo.XorToSortable ( @b, @m, 8 ) 
end

go

-- Create some test data

create  table dbo.sal ( salary float, salbin as dbo.FloatToSortable(salary)) ;
go

declare @x float
set @x = pi()/9876543

while abs(@x) < 170
  begin
    insert into sal ( salary ) values ( @x )
    set @x=@x * -2.014159265
  end

select * from sal order by salbin

-- result is:
--  salary                 salbin
--  ---------------------- ----------------
--  -51.6508818660658      DPLGCMKPOHCLNIAP
--  -12.7318092715982      DPNGIJFAELIPCGOM
--  -3.1383581745746       DPPGOEKEHICIIKOI
--  -0.773597202236665     EABHDOLBBEIDLJLO
--  -0.190689716730473     EADHJHHKLHHKMEDG
--  -0.0470045237516562    EAFHOPAFOHHBPGCJ
--  -0.0115864939704268    EAHIEFFHBKJCNPMF
--  -0.00285604090440349   EAJIJKHCLHAGILBG
--  -0.000704006722693307  EALIOOFNBDCOOAMG
--  -0.000173535842863177  EANJEBBKHFNPDPAD
--  -4.27761380502506E-05  EAPJJCKPBGJEEFHA
--  -1.0544207791913E-05   EBBJODBPBNKKNIPE
--  -2.59912004745334E-06  EBDKDCGOKEJGGCDL
--  -6.4067639356036E-07   EBFKIAKBLGBGEJKE
--  3.180862629353E-07     LOJFFIKOCNMOIIKB
--  1.29042429395639E-06   LOLFKGFEIEBGJGMI
--  5.23504172442538E-06   LONFPFBFEPNNJAIF
--  2.12377138161667E-05   LOPGEEPEJEJMLAHP
--  8.61579547748313E-05   LPBGJFPGGEGGLLMK
--  0.000349528825712453   LPDGOIBOOABNBNJK
--  0.00141798166313501    LPFHDLHCDHKFMEBP
--  0.00575252124882327    LPHHIPPEKKCBMBFH
--  0.0233370441794017     LPJHOFKKIGCELCJB
--  0.094674597011311      LPLIDMJICJOMPBIA
--  0.384079459692908      LPNIJEMCADJMJBKO
--  1.55814797226306       LPPIOOCMJBHDCNED
--  6.32115319420792       MABJEINMGCAIIEAO
--  25.6438916046025       MADJKENGBEIHOPME
--  104.033102255957       MAFKACBOFIOMLAIO
0 голосов
/ 27 ноября 2018

Если вам нужен синтетический порядок "max", просто выполните:

select top (1) name, date_of_entry, falg1, flag2, salary, flag3, id
from test
order by name desc, date_of_entry desc, flag1 desc, flag2 desc, salary desc, flag3 desc, id desc;

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

Если вы хотитеотдельный ряд для каждого name, затем:

select top (1) with ties name, date_of_entry, falg1, flag2, salary, flag3, id
from test
order by row_number() over (partition by name desc order by date_of_entry desc, flag1 desc, flag2 desc, salary desc, flag3 desc, id desc);
0 голосов
/ 27 ноября 2018

Это даст вам то, что вы хотите, но, возможно, не так, как вам нравится в подзапросе

SELECT 
    name
    + '.' + CONVERT(varchar(8), date_of_entry, 112) 
    + '.' + CONVERT(varchar(1), flag1)
    + '.' + CONVERT(varchar(1), flag2)
    + '.' + CONVERT(varchar(2555), salary / (SELECT MIN(salary) AS min_sal FROM TEST))
    + '.' + CONVERT(varchar(1), flag3)
    + '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST 
ORDER BY SYNTHETIC_ORDER DESC
0 голосов
/ 27 ноября 2018

Почему вы не можете просто заказать его по отдельным столбцам?

SELECT 
    date_of_entry, flag1, flag2, salary, flag3 
    , name
        + '.' + CONVERT(varchar(8), date_of_entry, 112) 
        + '.' + CONVERT(varchar(1), flag1)
        + '.' + CONVERT(varchar(1), flag2)
        + '.' + CONVERT(varchar(2555), salary)
        + '.' + CONVERT(varchar(1), flag3)
        + '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM  TEST
ORDER BY date_of_entry DESC, flag1 DESC, flag2 DESC, salary DESC, flag3 DESC

Это даст вам МАКС.

SELECT SYNTHETIC_ORDER
FROM (
    SELECT 
        ROW_NUMBER() OVER(ORDER BY date_of_entry DESC, flag1 DESC, flag2 DESC, salary DESC, flag3 DESC) AS RowNum
        , name
            + '.' + CONVERT(varchar(8), date_of_entry, 112) 
            + '.' + CONVERT(varchar(1), flag1)
            + '.' + CONVERT(varchar(1), flag2)
            + '.' + CONVERT(varchar(2555), salary)
            + '.' + CONVERT(varchar(1), flag3)
            + '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
    FROM  TEST
) a
WHERE RowNum = 1
0 голосов
/ 27 ноября 2018

Вы можете изменить свой запрос на

SELECT 
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112) 
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM 
    TEST
ORDER BY 
    salary DESC
0 голосов
/ 27 ноября 2018

Можете ли вы попробовать:

SELECT name
+ '.' + CONVERT(varchar(8), date_of_entry, 112) 
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CHAR(DIGITS(salary))
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST
ORDER BY SYNTHETIC_ORDER DESC
...