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
    

    你测试过这个吗?它的表现如何?