SQL Server 2005,宽索引,计算列和sargable查询

时间:2010-06-02 19:31:34

标签: sql sql-server sql-server-2005 tsql

在我的数据库中,假设我们有一个如下定义的表:

CREATE TABLE [Chemical](
    [ChemicalId] int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    [Name] nvarchar(max) NOT NULL,
    [Description] nvarchar(max) NULL
)

Name的值可能非常大,因此我们必须使用nvarchar(max)。不幸的是,我们想在这个列上创建一个索引,但索引中不支持nvarchar(max)。

因此我们根据它创建以下计算列和相关索引:

ALTER TABLE [Chemical]
ADD [Name_Indexable] AS LEFT([Name], 20)

CREATE INDEX [IX_Name] 
ON [Chemical]([Name_Indexable]) 
INCLUDE([Name])

索引不是唯一的,但我们可以通过触发器强制执行唯一性。

如果我们执行以下查询,执行计划会产生索引 scan ,这不是我们想要的:

SELECT [ChemicalId], [Name], [Description] 
FROM [Chemical] 
WHERE [Name]='[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester'

但是,如果我们修改查询以使其“可以”,那么执行计划会产生索引 seek ,这就是我们想要的:

SELECT [ChemicalId], [Name], [Description] 
FROM [Chemical] 
WHERE [Indexable_Name]='[1,1''-Bicyclohexyl]-' AND [Name]='[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester'

如果我们通过中间层控制对数据库执行的所有查询的格式,这是一个很好的解决方案吗?有没有更好的办法?这是一个主要的kludge?我们应该使用全文索引吗?

6 个答案:

答案 0 :(得分:2)

您的索引位于name_indexable,而不是name。由于name_indexable是从涉及name的函数生成的,而不是直接在列name上生成的,所以当where子句包含对name子句的引用时,优化器不会自动使用索引name_indexable。您必须搜索name_indexable才能使用该索引。由于你有一个中间层,你最好的选择可能是提供一个函数,如果给定的名称是< = 200个字符,则在{{1}}上搜索,否则搜索两者。

答案 1 :(得分:2)

使Name_Index列成为持久计算列和主键,并通过附加ChemicalId而不是依赖触发器来强制实现唯一性。

CREATE TABLE dbo.[Chemical]
    ([ChemicalId] int NOT NULL IDENTITY(1,1), 
    [Name] Nvarchar(max) NOT NULL, 
    [Description] Nvarchar(max) NOT NULL,
    [Name_Index] AS (CONVERT(VARCHAR(20), LEFT([Name], 20)) + CONVERT(VARCHAR(20), [ChemicalId])) PERSISTED PRIMARY KEY);

答案 2 :(得分:1)

你试过吗

WHERE [Name_Indexable]='1,2,3-Propanetriol'

毕竟这是在

上创建索引的地方

答案 3 :(得分:1)

恕我直言,是的,我认为这是一个糟糕的做法。如果您知道前20个字符将是唯一的,那么它应该是具有唯一约束的第一类列。如果您想更好地搜索“名称”列,那么使用全文搜索是正确的方法。如果要确保varchar(max)列是唯一的,那么创建一个计算列,该列生成一个散列值,并放置一个唯一约束。

Alter Table Add NameHash Hashbytes('SHA1', [Name])

<强> ADDITION

根据我们的讨论,如果您的搜索总是与完全匹配,那么您可以散列搜索参数并将其与上面的NameHash进行比较。 然而,问题是匹配必须是完全匹配(即区分大小写)。

我仍然满足于FTS将是您最好的选择。尽管将文本分解为单词会产生开销,但FTS是最适合对大量文本进行搜索的工具。搜索条件越长,搜索条件就越准确,搜索速度就越快。

答案 4 :(得分:1)

我从问题(最后一个查询)中找到你的解决方案非常好,但我个人更喜欢说SQL更确切地说我想做什么。因此,如果您使用Microsoft SQL Server或其他一些支持CTE(公用表表达式)的SQL Server,您可以重写您的查询,如下所示:

DECLARE @data nvarchar(max);

SET @data = '[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester';

WITH ReduceData ([ChemicalId], [Name], [Description]) AS (
    SELECT [ChemicalId], [Name], [Description] 
    FROM [dbo].[Chemical]
    WHERE [Name_Indexable]=LEFT(@data,20)
)
SELECT [ChemicalId], [Name], [Description] 
FROM ReduceData
WHERE [Name]=@data

(在实际实现中,您可能不需要定义@data。而是可以使用参数化查询。)。我建议只是说SQL更明确你想要什么。所有CTE查询都可以进行非常好的优化。

可能是您的原始查询将被编译为与我的CTE版本完全相同的执行计划。您可以查看这两个计划并进行比较。在您的项目中,您可能从问题中获得了更复杂的查询。如果你将使用更多的CTE,你的SQL代码将易于阅读,它可以非常好地优化,你可以确保SQL Server完全符合你的要求。

更新:顺便说一句

ALTER TABLE [Chemical]
    ADD [Name_Indexable] AS LEFT([Name], 20)

应改为

ALTER TABLE [Chemical]
    ADD [Name_Indexable] AS CAST(LEFT([Name], 20) AS varchar(20)) PERSISTED

在Microsoft SQL Server 2008上创建类型为varchar(20)的[Name_Indexable]列并将其标记为PERSISTED以将计算值存储在表中,并在计算列所依赖的任何其他列时更新它们已更新

答案 5 :(得分:0)

修复您的数据模型。您在名称列中有一个以逗号分隔的列表,对我而言,这意味着您最好能够查询是否有相关的表。您的名字似乎是一个不是名字的成分列表。

如果这确实是真名,那么注册用户就有了一个好的计划。