在SQL中对公寓号进行排序 - 数字词典排序

时间:2016-08-22 00:02:27

标签: sql sql-server

我需要在SQL中对公寓号码表进行排序。对于那些不熟悉的人来说,这些不一定是数字,但是数字排序需要像它们一样应用。

例如,一组可能的公寓号码将按如下方式订购:

1
10
101
101-A
1000
200
200A
2000
C
D
E-100
F

执行ORDER BY CONVERT( int, ApartmentNumber )不会起作用,因为并非所有的公寓号都是字符串编码的十进制整数。同样地,ORDER BY ApartmentNumber无法正常工作,因为它会在101之后放置1000

StackOverflow上的其他QA通常关注已知的固定格式的值Sorting string column containing numbers in SQL?或进行错误处理的排序:Sorting field by numerical value and lexicographical value

在我自己的项目中,以前的开发人员使用了这个技巧:

ORDER BY
    RIGHT('00000000000000000000' + RTRIM( ApartmentNumber ), 20 ) ASC

......感觉更糟。事情是:这个技巧在算法上是合理的 - 它只是感觉像是一个黑客必须让数据库引擎执行字符串分配(多次,如果他们不优化串联和RTRIM到一个字符串操作)。

提供SO的另一种方法是:

ORDER BY
    LEN( ApartmentNumber ),
    ApartmentNumber

...但是这会产生输入集的错误排序:

1
C
D
F
10
101
200
1000
2000
200A
101-A
E-100

我在本地使用SQL Server 2012(2008年(级别100)兼容模式),此应用程序将部署到Azure SQL服务器(Azure SQL V12)。

更新:

我一直在考虑一些选择,我认为最好的"是使用固定长度的char(10)字段而不是varchar(10),并确保字段的内容始终保持左对齐,这样就可以通过简单的{{1}确保排序顺序}}

更新2:我意识到上述想法(ORDER BY ApartmentNumber)没有解决char(10)需要在200A之前排序的问题。我认为最好的解决方案是将值规范化为统一的整数表示并将该值存储在数据库中。如果没有用SQL编写,那么进行转换的算法最好(即最简洁)。

1 个答案:

答案 0 :(得分:0)

我不认为我使用简单屏蔽模式的原始想法会运行良好,我最终将问题减少到这四个表达式。希望你能在这一切中找到一些价值。

order by
    case
         when patindex('%[0-9][A-Z-]%', apt) > 0
           then cast(substring(apt, 1, patindex('%[0-9][A-Z-]%', apt)) as int)
         when patindex('[0-9]%', apt) = 1
           then cast(apt as int)
         else 99999
    end /* numeric_prefix */,
    case
         when patindex('%[A-Z][0-9-]%', apt) > 0
           then left(apt, patindex('%[A-Z][0-9-]%', apt))
         when patindex('[A-Z]%', apt) = 1
           then apt
         else ''
    end /* char_prefix */,
    case 
         when patindex('%[A-Z-][0-9]%', apt) > 0
           then cast(substring(apt, patindex('%[A-Z-][0-9]%', apt) + 1, 10) as int)
         else 0
    end /* numeric_suffix */,
    replace(apt, '-', '') /* stripped_hyphen */,
    case when charindex('-', apt) = 0 then 0 else 1 end /* sort_hyphen_last */

http://rextester.com/MFYVN38156

这是一个可以存储并用于排序的表达式:

case
     when patindex('%[0-9][A-Z-]%', apt) > 0
     then right('*****' + left(apt, patindex('%[0-9][A-Z-]%', apt)), 5) +
          right('>>>>>' +
              replace(substring(apt, patindex('%[0-9][A-Z-]%', apt) + 1, 10), '-', ''), 5)
     when patindex('%[A-Z][0-9-]%', apt) > 0
     then right('>>>>>' + left(apt, patindex('%[A-Z][0-9-]%', apt)), 5) +
          right('*****' +
              replace(substring(apt, patindex('%[A-Z][0-9-]%', apt) + 1, 10), '-', ''), 5)
     when patindex('[0-9]%', apt) = 1 then right('*****' + apt, 5) + '>>>>>'
     when patindex('[A-Z]%', apt) = 1 then right('>>>>>' + apt, 5) + '*****'
     else '>>>>>*****'
end +
case when charindex('-', apt) = 0 then '' else '-' end /* collate Latin1_General_BIN */
选择

>作为在数字和字母字符之间进行排序的字符。 *落在数字之前。输出如下:

   apt     sort_string  
 -------- ------------- 
  1        ****1>>>>>   
  10       ***10>>>>>   
  101      **101>>>>>   
  101-A    **101>>>>A-  
  200      **200>>>>>   
  200A     **200>>>>A   
  200-A    **200>>>>A-  
  1000     *1000>>>>>   
  2000     *2000>>>>>   
  C        >>>>C*****   
  D        >>>>D*****   
  E        >>>>E*****   
  E10      >>>>E***10   
  E100     >>>>E**100   
  E-100    >>>>E**100-  
  E101     >>>>E**101   
  E-101    >>>>E**101-  
  E200     >>>>E**200   
  E-200    >>>>E**200-  
  E1000    >>>>E*1000   
  E-1001   >>>>E*1001-  
  F        >>>>F*****