选择所有记录,如果连接存在则连接表A,如果不存在则连接表B

Der*_*sar 21 sql-server t-sql sql-server-2014

所以这是我的场景:

我正在为我的一个项目进行本地化,通常我会在 C# 代码中执行此操作,但是我想在 SQL 中执行更多此操作,因为我试图稍微增强我的 SQL。

环境:SQL Server 2014 Standard,C# (.NET 4.5.1)

注意:编程语言本身应该是无关紧要的,我只是为了完整性而包括它。

所以我在某种程度上完成了我想要的,但没有达到我想要的程度。JOIN除了基本的SQL之外,我已经有一段时间(至少一年)完成了任何 SQL ,这是一个相当复杂的JOIN.

这是数据库相关表的图表。(还有很多,但这部分不是必需的。)

数据库图表

图像中描述的所有关系在数据库中都是完整的 -PKFK约束都是设置和操作的。所描述的列都null不能。所有的表都有架构dbo

现在,我有一个查询几乎可以满足我的要求:也就是说,给定ANY Id ofSupportCategoriesANY Id of Languages,它将返回:

如果有合适的,正确的翻译是语言该字符串(即StringKeyId- >StringKeys.Id存在,并在LanguageStringTranslations StringKeyIdLanguageId以及StringTranslationId是否同时存在,那么它的负载StringTranslations.TextStringTranslationId

如果LanguageStringTranslations StringKeyIdLanguageIdStringTranslationId组合没有存在,那么它加载的StringKeys.Name值。该Languages.Id是给定的integer

我的查询,是否一团糟,如下:

SELECT CASE WHEN T.x IS NOT NULL THEN T.x ELSE (SELECT
    CASE WHEN dbo.StringTranslations.Text IS NULL THEN dbo.StringKeys.Name ELSE dbo.StringTranslations.Text END AS Result
FROM dbo.SupportCategories
    INNER JOIN dbo.StringKeys
        ON dbo.SupportCategories.StringKeyId = dbo.StringKeys.Id
    INNER JOIN dbo.LanguageStringTranslations
        ON dbo.StringKeys.Id = dbo.LanguageStringTranslations.StringKeyId
    INNER JOIN dbo.StringTranslations
        ON dbo.StringTranslations.Id = dbo.LanguageStringTranslations.StringTranslationId
WHERE dbo.LanguageStringTranslations.LanguageId = 38 AND dbo.SupportCategories.Id = 0) END AS Result FROM (SELECT (SELECT
    CASE WHEN dbo.StringTranslations.Text IS NULL THEN dbo.StringKeys.Name ELSE dbo.StringTranslations.Text END AS Result
FROM dbo.SupportCategories
    INNER JOIN dbo.StringKeys
        ON dbo.SupportCategories.StringKeyId = dbo.StringKeys.Id
    INNER JOIN dbo.LanguageStringTranslations
        ON dbo.StringKeys.Id = dbo.LanguageStringTranslations.StringKeyId
    INNER JOIN dbo.StringTranslations
        ON dbo.StringTranslations.Id = dbo.LanguageStringTranslations.StringTranslationId
WHERE dbo.LanguageStringTranslations.LanguageId = 5 AND dbo.SupportCategories.Id = 0) AS x) AS T
Run Code Online (Sandbox Code Playgroud)

问题是,它不能够提供我的所有SupportCategories和它们各自的StringTranslations.Text,如果它存在,或者他们StringKeys.Name如果它不存在。它非常适合提供其中任何一种,但根本不是。基本上,它是为了强制如果一种语言没有特定键的翻译,那么默认是使用StringKeys.Name哪个是StringKeys.DefaultLanguageId翻译的。(理想情况下,它甚至不会这样做,而是加载 for 的翻译StringKeys.DefaultLanguageId,如果为查询的其余部分指出正确的方向,我可以自己做。)

我在这方面花了很多时间,我知道如果我只是用 C# 编写它(就像我通常那样),现在就可以完成。我想在 SQL 中执行此操作,但无法获得我喜欢的输出。

唯一需要注意的是,我想限制应用的实际查询的数量。所有的列都被索引,就像我现在喜欢的那样,如果没有真正的压力测试,我无法进一步索引它们。

编辑:另一个注意事项,我试图使数据库尽可能规范化,所以如果可以避免的话,我不想重复。

示例数据

来源

dbo.SupportCategories(全部):

Id  StringKeyId
0   0
1   1
2   2
Run Code Online (Sandbox Code Playgroud)

dbo.Languages(185 条记录,示例中仅显示两条):

Id  Abbreviation    Family  Name    Native
38  en  Indo-European   English English
48  fr  Indo-European   French  français, langue française
Run Code Online (Sandbox Code Playgroud)

dbo.LanguagesStringTranslations(完整):

StringKeyId LanguageId  StringTranslationId
0   38  0
1   38  1
2   38  2
3   38  3
4   38  4
5   38  5
6   38  6
7   38  7
1   48  8 -- added as example
Run Code Online (Sandbox Code Playgroud)

dbo.StringKeys(完整):

Id  Name    DefaultLanguageId
0   Billing 38
1   API 38
2   Sales   38
3   Open    38
4   Waiting for Customer    38
5   Waiting for Support 38
6   Work in Progress    38
7   Completed   38
Run Code Online (Sandbox Code Playgroud)

dbo.StringTranslations(整体):

Id  Text
0   Billing
1   API
2   Sales
3   Open
4   Waiting for Customer
5   Waiting for Support
6   Work in Progress
7   Completed
8   Les APIs -- added as example
Run Code Online (Sandbox Code Playgroud)

电流输出

鉴于下面的确切查询,它输出:

Result
Billing
Run Code Online (Sandbox Code Playgroud)

期望输出

理想情况下,我希望能够省略特定的SupportCategories.Id,并获得所有这些(无论是否使用了语言 38 English,或 48 French,或目前任何其他语言):

Id  Result
0   Billing
1   API
2   Sales
Run Code Online (Sandbox Code Playgroud)

附加示例

鉴于我要为French(即添加1 48 8LanguageStringTranslations)添加本地化,输出将更改为(注意:这只是示例,显然我会向StringTranslations)添加本地化字符串)(使用法例更新):

Result
Les APIs
Run Code Online (Sandbox Code Playgroud)

额外的期望输出

鉴于上面的示例,将需要以下输出(使用法式示例更新):

Id  Result
0   Billing
1   Les APIs
2   Sales
Run Code Online (Sandbox Code Playgroud)

(是的,我知道从技术上讲,从一致性的角度来看这是错误的,但这是在这种情况下所需要的。)

编辑:

小更新,我确实更改了dbo.Languages表的结构,并Id (int)从中删除了列,并将其替换为Abbreviation(现在重命名为Id,并且更新了所有相关的外键和关系)。从技术角度来看,我认为这是一个更合适的设置,因为该表仅限于 ISO 639-1 代码,这些代码一开始就是独一无二的。

Tl;博士

所以说:这个问题,我怎么能修改此查询返回的一切SupportCategories再或者返回StringTranslations.TextStringKeys.IdLanguages.Id组合,StringKeys.Name如果它没有存在吗?

我最初的想法是,我可以以某种方式将当前查询转换为另一个临时类型作为另一个子查询,并将此查询包装在另一个SELECT语句中并选择我想要的两个字段(SupportCategories.IdResult)。

如果我没有找到任何东西,我只会执行我通常使用的标准方法,即将所有内容加载SupportCategories到我的 C# 项目中,然后使用它手动运行我上面针对每个SupportCategories.Id.

感谢您的任何和所有建议/评论/批评。

另外,我为它的荒谬而道歉,我只是不想有任何歧义。我经常在 StackOverflow 上看到缺乏实质内容的问题,不想在这里犯那个错误。

Aar*_*and 17

这是我想出的第一种方法:

DECLARE @ChosenLanguage INT = 48;

SELECT sc.Id, Result = MAX(COALESCE(
   CASE WHEN lst.LanguageId = @ChosenLanguage      THEN st.Text END,
   CASE WHEN lst.LanguageId = sk.DefaultLanguageId THEN st.Text END)
)
FROM dbo.SupportCategories AS sc
INNER JOIN dbo.StringKeys AS sk
  ON sc.StringKeyId = sk.Id
LEFT OUTER JOIN dbo.LanguageStringTranslations AS lst
  ON sk.Id = lst.StringKeyId
  AND lst.LanguageId IN (sk.DefaultLanguageId, @ChosenLanguage)
LEFT OUTER JOIN dbo.StringTranslations AS st
  ON st.Id = lst.StringTranslationId
  --WHERE sc.Id = 1
  GROUP BY sc.Id
  ORDER BY sc.Id;
Run Code Online (Sandbox Code Playgroud)

基本上,获取与所选语言匹配的潜在字符串获取所有默认字符串,然后聚合,以便您只选择一个Id- 优先选择所选语言,然后将默认值作为后备。

您可能可以使用UNION/做类似的事情,EXCEPT但我怀疑这几乎总是会导致对同一对象进行多次扫描。


Pau*_*ite 13

避免INAaron 答案中的和 分组的替代解决方案:

DECLARE 
    @SelectedLanguageId integer = 48;

SELECT 
    SC.Id,
    SC.StringKeyId,
    Result =
        CASE
            -- No localization available
            WHEN LST.StringTranslationId IS NULL
            THEN SK.Name
            ELSE
            (
                -- Localized string
                SELECT ST.[Text]
                FROM dbo.StringTranslations AS ST
                WHERE ST.Id = LST.StringTranslationId
            )
        END
FROM dbo.SupportCategories AS SC
JOIN dbo.StringKeys AS SK
    ON SK.Id = SC.StringKeyId
LEFT JOIN dbo.LanguageStringTranslations AS LST
    WITH (FORCESEEK) -- Only for low row count in sample data
    ON LST.StringKeyId = SK.Id
    AND LST.LanguageId = @SelectedLanguageId;
Run Code Online (Sandbox Code Playgroud)

如前所述,FORCESEEK由于LanguageStringTranslations提供的示例数据表的基数较低,因此仅需要提示才能获得最有效的计划。随着行数增多,优化器自然会选择索引查找。

执行计划本身有一个有趣的特点:

执行计划

最后一个外连接的 Pass Through 属性意味着StringTranslations只有在先前在LanguageStringTranslations表中找到行时才会执行对表的查找。否则,对于当前行,将完全跳过此连接的内侧。

表 DDL

CREATE TABLE dbo.Languages
(
    Id integer NOT NULL,
    Abbreviation char(2) NOT NULL,
    Family nvarchar(96) NOT NULL,
    Name nvarchar(96) NOT NULL,
    [Native] nvarchar(96) NOT NULL,

    CONSTRAINT PK_dbo_Languages
        PRIMARY KEY CLUSTERED (Id)
);

CREATE TABLE dbo.StringTranslations
(
    Id bigint NOT NULL,
    [Text] nvarchar(128) NOT NULL,

    CONSTRAINT PK_dbo_StringTranslations
    PRIMARY KEY CLUSTERED (Id)
);

CREATE TABLE dbo.StringKeys
(
    Id bigint NOT NULL,
    Name varchar(64) NOT NULL,
    DefaultLanguageId integer NOT NULL,

    CONSTRAINT PK_dbo_StringKeys
    PRIMARY KEY CLUSTERED (Id),

    CONSTRAINT FK_dbo_StringKeys_DefaultLanguageId
    FOREIGN KEY (DefaultLanguageId)
    REFERENCES dbo.Languages (Id)
);

CREATE TABLE dbo.SupportCategories
(
    Id integer NOT NULL,
    StringKeyId bigint NOT NULL,

    CONSTRAINT PK_dbo_SupportCategories
        PRIMARY KEY CLUSTERED (Id),

    CONSTRAINT FK_dbo_SupportCategories
    FOREIGN KEY (StringKeyId)
    REFERENCES dbo.StringKeys (Id)
);

CREATE TABLE dbo.LanguageStringTranslations
(
    StringKeyId bigint NOT NULL,
    LanguageId integer NOT NULL,
    StringTranslationId bigint NOT NULL,

    CONSTRAINT PK_dbo_LanguageStringTranslations
    PRIMARY KEY CLUSTERED 
        (StringKeyId, LanguageId, StringTranslationId),

    CONSTRAINT FK_dbo_LanguageStringTranslations_StringKeyId
    FOREIGN KEY (StringKeyId)
    REFERENCES dbo.StringKeys (Id),

    CONSTRAINT FK_dbo_LanguageStringTranslations_LanguageId
    FOREIGN KEY (LanguageId)
    REFERENCES dbo.Languages (Id),

    CONSTRAINT FK_dbo_LanguageStringTranslations_StringTranslationId
    FOREIGN KEY (StringTranslationId)
    REFERENCES dbo.StringTranslations (Id)
);
Run Code Online (Sandbox Code Playgroud)

样本数据

INSERT dbo.Languages
    (Id, Abbreviation, Family, Name, [Native])
VALUES
    (38, 'en', N'Indo-European', N'English', N'English'),
    (48, 'fr', N'Indo-European', N'French', N'français, langue française');

INSERT dbo.StringTranslations
    (Id, [Text])
VALUES
    (0, N'Billing'),
    (1, N'API'),
    (2, N'Sales'),
    (3, N'Open'),
    (4, N'Waiting for Customer'),
    (5, N'Waiting for Support'),
    (6, N'Work in Progress'),
    (7, N'Completed'),
    (8, N'Les APIs'); -- added as example

INSERT dbo.StringKeys
    (Id, Name, DefaultLanguageId)
VALUES
    (0, 'Billing', 38),
    (1, 'API', 38),
    (2, 'Sales', 38),
    (3, 'Open', 38),
    (4, 'Waiting for Customer', 38),
    (5, 'Waiting for Support', 38),
    (6, 'Work in Progress', 38),
    (7, 'Completed', 38);

INSERT dbo.SupportCategories
    (Id, StringKeyId)
VALUES
    (0, 0),
    (1, 1),
    (2, 2);

INSERT dbo.LanguageStringTranslations
    (StringKeyId, LanguageId, StringTranslationId)
VALUES
    (0, 38, 0),
    (1, 38, 1),
    (2, 38, 2),
    (3, 38, 3),
    (4, 38, 4),
    (5, 38, 5),
    (6, 38, 6),
    (7, 38, 7),
    (1, 48, 8); -- added as example
Run Code Online (Sandbox Code Playgroud)