0

我有一个名为MRvarchar 的列。当我使用 an 运行查询时,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

4

3 回答 3

5

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

如果您只想将数字视为数字,那么也许不要存储MR前缀。根据列名,这似乎是完全多余的。为什么不将数字部分单独存储为 anINT并创建一个'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 在估计成本方面吐出废话):

在此处输入图像描述

并在 上使用聚集索引MR

在此处输入图像描述

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

于 2013-01-22T02:37:25.910 回答
1

试试投吧

select MR, LName, FName 
from users 
where MR between 'MR20001' and 'MR20002' 
order by CAST(REPLACE(MR, 'MR', '') AS INT)
于 2013-01-22T02:34:19.803 回答
0

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

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

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

于 2013-01-22T02:49:59.883 回答