订购包含数字的字符串

时间:2013-01-22 02:32:56

标签: sql sql-server

我有一个名为MR的列,它是一个varchar。当我使用ORDER BY运行查询时,似乎没有正确排序。

select MR, LName, FName from users 
  where MR between 'MR20001' and 'MR20002' 
  order by MR

结果:

MR20001   | LINA  | MARY
MR200011  | TEST  | CASE
MR20002   | KO    | MIKE

为什么MR200011之前显示MR20002

3 个答案:

答案 0 :(得分:5)

因为MR是一个字符串,并且 - 例如 - 24排序低于3,因为它不关心数值。这就像在Smith之前对Azlea进行排序,因为z> m

如果您只想将数字视为数字,则可能不存储MR前缀。根据列名称,这似乎完全是多余的。为什么不将数字部分单独存储为INT并创建一个在运行时附加'MR'的视图?您可以轻松地执行此操作而不会真正影响应用程序(如果您无法通过存储过程控制插入/更新操作,请添加而不是触发器):

CREATE VIEW dbo.users_appended
AS
    SELECT MR = 'MR' + CONVERT(VARCHAR(25), MR), 
        MRSort = MR --, ... other columns ...
      FROM dbo.users;
GO

SELECT MR, other columns
  FROM dbo.users_appended
  ORDER BY MRSort;

如果您无法更改架构,可以说:

ORDER BY CONVERT(BIGINT, SUBSTRING(MR, 3, 25));

但我真的认为你根本不应该存储MR。如果你不能改变它,那么可以考虑一个视图或计算列来拉出字符串的数字部分。如果您只打算在一个方向上进行排序,您甚至可以索引计算列。

ALTER TABLE dbo.users ADD MRNumber
  AS (CONVERT(BIGINT, SUBSTRING(MR, 3, 25))) PERSISTED;

CREATE INDEX ix_mrnumber ON dbo.users(MRNumber);

您必须测试维护计算列和索引所需的工作是否与查询的差异合理。

视图类似,但您无法从索引中获得任何效率:

CREATE VIEW dbo.users_extended
AS
  SELECT MR, ..., MRNumber = CONVERT(BIGINT, SUBSTRING(MR, 3, 25));
GO

SELECT MR, ... 
  FROM dbo.users_extended
  ORDER BY MRNumber;

至于使用LEN,请注意。虽然代码更简单,但它不一定更有效。在我的系统上,我创建了两个具有广泛值的表:

SELECT 'MR'+RTRIM(ABS(object_id)) AS MR 
  INTO dbo.flab 
  FROM sys.all_objects -- 2096 rows

SELECT 'MR'+RTRIM(ABS(s1.object_id)) AS MR 
  INTO dbo.mort 
  FROM sys.all_objects AS s1
  CROSS JOIN sys.all_objects AS s2; -- 4397409 rows

现在,测试这样的简单查询:

SELECT * FROM dbo.flab ORDER BY LEN(MR), MR;
SELECT * FROM dbo.flab ORDER BY CONVERT(BIGINT, SUBSTRING(MR, 3, 25));
SELECT * FROM dbo.mort ORDER BY LEN(MR), MR;
SELECT * FROM dbo.mort ORDER BY CONVERT(BIGINT, SUBSTRING(MR, 3, 25));

堆上的结果(密切关注持续时间和CPU,尽管SQL Server在估计成本方面无意义地吐出):

enter image description here

使用MR上的聚集索引:

enter image description here

我还将所有计算都更改为BIGINT,以避免子字符串超过12个字符的任何潜在危险(并且仍然避免昂贵 - 是的,昂贵的 - LEN())。请注意,如果使用INT代替BIGINT,估算费用为50/50且持续时间差异大致相同(假设使用INT是安全的 - 我认为是一个安全的假设,因为如果有更大的值,接受的答案就会失败。)

答案 1 :(得分:1)

尝试投射,

select MR, LName, FName 
from users 
where MR between 'MR20001' and 'MR20002' 
order by CAST(REPLACE(MR, 'MR', '') AS INT)

答案 2 :(得分:0)

为了避免所有昂贵的铸造和替换,您可以使用它作为替代:

select MR, LName, FName 
from Table1 
order by LEN(MR),MR

http://www.sqlfiddle.com/#!3/ae729/6