最有效的方式来加入一个由两部分组成的键,只有匹配第一部分的回退?

时间:2015-10-12 14:07:21

标签: tsql join sql-server-2012 internationalization

纯技术术语

给定一个包含两列唯一键的表,以及这两列的输入值,基于两步匹配返回第一个匹配行的最有效方法是什么?:

  1. 如果两个关键部分都存在完全匹配,请返回
  2. 否则,仅根据第一部分返回第一个(如果有)匹配行
  3. 此操作将在许多不同的地方,在许多行上完成。 "有效载荷"匹配项将是单个字符串列(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 )
    );
    

    LabelIDLanguageCode上有一个唯一索引,因此每个ISO 2字符语言代码只能有一个文本项翻译。 LabelText字段也是include d,因此读取可以访问索引而无需从基础表中获取:

    create unique index UQ_LabelText 
        on LabelText ( LabelID, LanguageCode )
        include ( LabelText);
    

    我正在寻找性能最快的方法,在给定LabelTextLabelID的情况下,通过两步匹配从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
    

3 个答案:

答案 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 1union 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