Tsql 连接到由两部分组成的密钥的最有效的方法,以及仅匹配第一部分的回退?
纯技术术语 给定一个具有两列唯一键的表,以及这两列的输入值,基于两步匹配返回第一个匹配行的最有效方法是什么Tsql 连接到由两部分组成的密钥的最有效的方法,以及仅匹配第一部分的回退?,tsql,join,sql-server-2012,internationalization,Tsql,Join,Sql Server 2012,Internationalization,纯技术术语 给定一个具有两列唯一键的表,以及这两列的输入值,基于两步匹配返回第一个匹配行的最有效方法是什么 如果两个关键部件上存在完全匹配,则返回 否则,仅基于第一个零件返回第一个匹配行(如果有) 此操作将在许多不同的位置、许多行上执行。匹配的“有效负载”将是单个字符串列(nvarchar(400))。我想优化快速阅读。通过较慢的插入和更新以及更多的存储来支付费用是可以接受的。因此,有多个索引和有效负载d是一个选项,只要有一个好的方法来执行上面描述的两步匹配。对于有效负载included,(ke
nvarchar(400)
)。我想优化快速阅读。通过较慢的插入和更新以及更多的存储来支付费用是可以接受的。因此,有多个索引和有效负载d是一个选项,只要有一个好的方法来执行上面描述的两步匹配。对于有效负载include
d,(key1,key2)
上绝对会有一个唯一的索引,因此基本上所有的读取都将单独从该索引中进行,除非有一些聪明的方法使用额外的索引
最好使用返回整个匹配行的方法,但如果只返回有效负载的标量函数快一个数量级,则值得考虑
我尝试了三种不同的方法,其中两种我在下面贴出了答案。第三种方法在解释计划成本中大约要贵20倍,我在这篇文章的末尾把它作为一个不应该做的例子
不过,我很好奇是否有更好的办法,如果有更好的办法,我会很乐意投票给其他人。在我的dev数据库中,queryplanner估计的成本与我的两种方法类似,但我的dev数据库中的多语言文本量与即将投入生产的文本量相差甚远,因此很难知道这是否准确反映了在大型数据集上的比较读取性能。如标签所示,该平台是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,因此reads可以访问索引,而无需从基础表中取回:
create unique index UQ_LabelText
on LabelText ( LabelID, LanguageCode )
include ( LabelText);
我正在寻找一种最快的方法,通过两步匹配从LabelText
表返回最佳匹配,给定LabelID
和LanguageCode
例如,假设我们有一个组件
表,如下所示:
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 )
);
declare @LanguageCode char(2) = 'de';
select
ComponentID
, NameLabelID
, DescriptionLabelID
, GetLabelText(NameLabelID, @LanguageCode) AS NameText
, GetLabelText(DescriptionLabelID, @LanguageCode) AS DescriptionText
from Component
每个用户都有一种首选语言,但不能保证文本项会有其语言的翻译。在这种业务环境下,当用户的首选语言不可用时,显示任何可用的翻译比不显示任何翻译更有意义。因此,例如,一个德国用户可能会将某个小部件称为“linkenpfostenklammer”。如果有英文译本,英国用户更愿意看,但在有英文译本之前,最好看德语(或西班牙语,或法语)版本,而不是什么也看不到
不可执行的操作:使用动态排序交叉应用
无论是封装在表值函数中还是包含在内联中,以下交叉应用与动态排序的使用比我的第一个答案中的标量值函数或我的第二个答案中的union all方法大约贵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
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
用法如下所示:
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 )
);
declare @LanguageCode char(2) = 'de';
select
ComponentID
, NameLabelID
, DescriptionLabelID
, GetLabelText(NameLabelID, @LanguageCode) AS NameText
, GetLabelText(DescriptionLabelID, @LanguageCode) AS DescriptionText
from Component
OP解决方案2:使用top 1、union all的内联表值函数 表值函数很好,因为它像使用标量函数一样封装查找以供重用,而且还返回实际选定行的匹配
LanguageCode
。在数据有限的my dev数据库中,以下使用top 1
和union all
的解释计划成本与“OP解决方案1”中的标量函数方法相当:
用法:
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
我认为这将是最有效的
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
你测试过这个吗?它的表现如何?