因为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
- 我认为这是一个安全的假设,因为如果有任何更大的接受的答案将失败),则持续时间差异大致相同值)。