在我的数据库中,假设我们有一个如下定义的表:
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?我们应该使用全文索引吗?
答案 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)
修复您的数据模型。您在名称列中有一个以逗号分隔的列表,对我而言,这意味着您最好能够查询是否有相关的表。您的名字似乎是一个不是名字的成分列表。
如果这确实是真名,那么注册用户就有了一个好的计划。