纯技术术语
给定一个包含两列唯一键的表,以及这两列的输入值,基于两步匹配返回第一个匹配行的最有效方法是什么?:
此操作将在许多不同的地方,在许多行上完成。 "有效载荷"匹配项将是单个字符串列(nvarchar(400)
)。我想优化快速读取。使用较慢的插入和更新以及更多存储来支付此费用是可以接受的。因此,拥有有效负载include
d的多个索引是一个选项,只要有一个很好的方法来执行上述的两步匹配。在(key1, key2)
上绝对会有一个带有有效负载include
d的唯一索引,所以基本上所有读取都将仅仅依赖于此索引,除非有一些聪明的方法可以使用其他索引。
首选返回整个匹配行的方法,但如果只返回有效负载的标量函数的速度要快一个数量级,则值得考虑。
我尝试了三种不同的方法,其中两种方法我在下面发布了答案。第三种方法在解释计划成本中的成本大约高出20倍,并且我在本文末尾将其作为一个不做的例子。
我很想知道是否有更好的方法,如果有更好的话,我会高兴地投票给别人的建议作为答案。在我的开发人员数据库中,查询计划程序估算出与我的两种方法类似的成本,但我的开发人员数据库并没有接近将要生产的多语言文本量,所以很难准确知道这是否准确反映了大型数据集的比较读取性能。作为标记,该平台是SQL Server 2012,因此如果从该版本开始有新的适用功能,请使用它们。
业务背景
我有一个表LabelText
,表示用户提供的动态内容的翻译:
create table Label ( bigint identity(1,1) not null primary key );
create table LabelText (
LabelTextID bigint identity(1,1) not null primary key
, LabelID bigint not null
, LanguageCode char(2) not null
, LabelText nvarchar(400) not null
, constraint FK_LabelText_Label
foreign key ( NameLabelID ) references Label ( LabelID )
);
LabelID
和LanguageCode
上有一个唯一索引,因此每个ISO 2字符语言代码只能有一个文本项翻译。 LabelText
字段也是include
d,因此读取可以访问索引而无需从基础表中获取:
create unique index UQ_LabelText
on LabelText ( LabelID, LanguageCode )
include ( LabelText);
我正在寻找性能最快的方法,在给定LabelText
和LabelID
的情况下,通过两步匹配从LanguageCode
表中返回最佳匹配。< / p>
例如,我们假设我们有一个Component
表,如下所示:
create table Component (
ComponentID bigint identity(1,1) not null primary key
, NameLabelID bigint not null
, DescriptionLabelID bigint not null
, constraint FK_Component_NameLabel
foreign key ( NameLabelID ) references Label ( LabelID )
, constraint FK_Component_DescLabel
foreign key ( DescriptionLabelID ) references Label ( LabelID )
);
用户将各自拥有首选语言,但无法保证文本项目将使用其语言进行翻译。在此业务环境中,当用户的首选语言不可用时,显示任何可用翻译而不是无翻译更有意义。因此,例如德国用户可以将某个小部件称为“linkenpfostenklammer”。如果有英语翻译,英国用户会更喜欢看英文翻译,但除非有英文翻译,否则最好看到德语(或西班牙语或法语)版本,而不是什么都看不到。
什么不该做:交叉申请动态排序
无论是封装在表值函数中还是包含在内联中,以下对动态排序的交叉应用的使用比我的第一个答案或者联合中的标量值函数贵20倍(每个解释计划估计)我的第二个答案中的所有方法:
declare @LanguageCode char(2) = 'de';
select
c.ComponentID
, c.NameLabelID
, n.LanguageCode as NameLanguage
, n.LabelText as NameText
from Component c
outer apply (
select top 1
lt.LanguageCode
, lt.LabelText
from LabelText lt
where lt.LabelID = c.NameLabelID
order by
(case when lt.LanguageCode = @LanguageCode then 0 else 1 end)
) n
答案 0 :(得分:2)
我认为这将是最具性能的
select lt.*, c.*
from ( select LabelText, LabelID from LabelText
where LabelTextID = @LabelTextID and LabelID = @LabelID
union
select LabelText, min(LabelID) from LabelText
where LabelTextID = @LabelTextID
and not exists (select 1 from LabelText
where LabelTextID = @LabelTextID and LabelID = @LabelID)
group by LabelTextID, LabelText
) lt
join component c
on c.NameLabelID = lt.LabelID
答案 1 :(得分:0)
OP解决方案1:标量函数
标量函数可以很容易地封装查找以便在其他地方重用,尽管它不会返回实际返回的文本的语言代码。我还不确定非规范化视图中每行执行多次的成本。
create function GetLabelText(@LabelID bigint, @LanguageCode char(2))
returns nvarchar(400)
as
begin
declare @text nvarchar(400);
select @text = LabelText
from LabelText
where LabelID = @LabelID and LanguageCode = @LanguageCode
;
if @text is null begin
select @text = LabelText
from LabelText
where LabelID = @LabelID;
end
return @text;
end
用法如下:
declare @LanguageCode char(2) = 'de';
select
ComponentID
, NameLabelID
, DescriptionLabelID
, GetLabelText(NameLabelID, @LanguageCode) AS NameText
, GetLabelText(DescriptionLabelID, @LanguageCode) AS DescriptionText
from Component
答案 2 :(得分:0)
OP解决方案2:使用top 1的内联表值函数,全部联合
表值函数很好,因为它与标量函数一样封装了重用的查找,但也返回了实际选择的行的匹配LanguageCode
。在我的具有有限数据的开发数据库中,以下使用top 1
和union all
的解释计划成本与&#34; OP解决方案1&#34;中的标量函数方法相当:
create function GetLabelText(@LabelID bigint, @LanguageCode char(2))
returns table
as
return (
select top 1
A.LanguageCode
, A.LabelText
from (
select
LanguageCode
, LabelText
from LabelText
where LabelID = @LabelID
and LanguageCode = @LanguageCode
union all
select
LanguageCode
, LabelText
from LabelText
where LabelID = @LabelID
) A
);
用法:
declare @LanguageCode char(2) = 'de';
select
c.ComponentID
, c.NameLabelID
, n.LanguageCode AS NameLanguage
, n.LabelText AS NameText
, c.DescriptionLabelID
, c.LanguageCode AS DescriptionLanguage
, c.LabelText AS DescriptionText
from Component c
outer apply GetLabelText(c.NameLabelID, @LanguageCode) n
outer apply GetLabelText(c.DescriptionLabelID, @LanguageCode) d