Chọn tất cả các bản ghi, tham gia với bảng A nếu tham gia tồn tại, bảng B nếu không


20

Vì vậy, đây là kịch bản của tôi:

Tôi đang làm việc về Bản địa hóa cho một dự án của tôi và thông thường tôi sẽ thực hiện việc này trong mã C #, tuy nhiên tôi muốn làm điều này trong SQL thêm một chút vì tôi đang cố gắng nâng cấp SQL của mình một chút.

Môi trường: Tiêu chuẩn SQL Server 2014, C # (.NET 4.5.1)

Lưu ý: bản thân ngôn ngữ lập trình không liên quan, tôi chỉ bao gồm nó để hoàn thiện.

Vì vậy, tôi sắp xếp hoàn thành những gì tôi muốn, nhưng không đến mức tôi muốn. Đã được một thời gian (ít nhất là một năm) kể từ khi tôi thực hiện bất kỳ SQL nào JOINngoại trừ những SQL cơ bản và điều này khá phức tạp JOIN.

Dưới đây là sơ đồ của các bảng có liên quan của cơ sở dữ liệu. (Có nhiều hơn nữa, nhưng không cần thiết cho phần này.)

Sơ đồ cơ sở dữ liệu

Tất cả các mối quan hệ được mô tả trong hình ảnh đều hoàn chỉnh trong cơ sở dữ liệu - các ràng buộc PKFKtất cả đều được thiết lập và vận hành. Không có cột nào được mô tả là nullcó thể. Tất cả các bảng có lược đồ dbo.

Bây giờ, tôi có một truy vấn gần như thực hiện những gì tôi muốn: đó là, đưa ra BẤT K Id Id nào SupportCategoriesBẤT K Id Id nào Languages, nó sẽ trả về:

Nếu có một bản dịch phải thích hợp cho rằng ngôn ngữ cho chuỗi (Ie StringKeyId-> StringKeys.Idtồn tại, và trong LanguageStringTranslations StringKeyId, LanguageIdStringTranslationIdkết hợp tồn tại, sau đó nó tải StringTranslations.Textcho rằng StringTranslationId.

Nếu LanguageStringTranslations StringKeyId, LanguageIdStringTranslationIdsự kết hợp đã không tồn tại, sau đó nó tải các StringKeys.Namegiá trị. Đây Languages.Idlà một cho trước integer.

Truy vấn của tôi, có thể là một mớ hỗn độn, như sau:

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

Vấn đề là nó không có khả năng cung cấp cho tôi TẤT CẢ của SupportCategoriesvà của mình StringTranslations.Textnếu nó tồn tại, HOẶC họ StringKeys.Namenếu nó không tồn tại. Nó là hoàn hảo trong việc cung cấp bất kỳ một trong số họ, nhưng không phải là tất cả. Về cơ bản, cần phải thực thi rằng nếu một ngôn ngữ không có bản dịch cho một khóa cụ thể, thì mặc định là sử dụng bản dịch StringKeys.Nameđó là StringKeys.DefaultLanguageIdbản dịch. (Lý tưởng nhất là nó thậm chí sẽ không làm điều đó, mà thay vào đó hãy tải bản dịch cho StringKeys.DefaultLanguageIdcái mà tôi có thể tự làm nếu được chỉ đúng hướng cho phần còn lại của truy vấn.)

Tôi đã dành rất nhiều thời gian cho việc này và tôi biết nếu tôi chỉ viết nó bằng C # (như tôi thường làm) thì bây giờ nó sẽ được thực hiện. Tôi muốn làm điều này trong SQL và tôi gặp khó khăn khi nhận đầu ra mà tôi thích.

Nhắc nhở duy nhất, là tôi muốn giới hạn số lượng truy vấn thực tế được áp dụng. Tất cả các cột được lập chỉ mục và như tôi thích chúng bây giờ và không có kiểm tra căng thẳng thực sự, tôi không thể lập chỉ mục cho chúng thêm.

Chỉnh sửa: Một lưu ý khác, tôi đang cố gắng giữ cơ sở dữ liệu bình thường nhất có thể, vì vậy tôi không muốn sao chép mọi thứ nếu tôi có thể tránh được.

Dữ liệu mẫu

Nguồn

dbo.SupportC loại (Entirety):

Id  StringKeyId
0   0
1   1
2   2

dbo. Ngôn ngữ (185 bản ghi, chỉ hiển thị hai ví dụ):

Id  Abbreviation    Family  Name    Native
38  en  Indo-European   English English
48  fr  Indo-European   French  français, langue française

dbo.LacularStringTranslations (Entirety):

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

dbo.StringKeys (Entirety):

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

dbo.StringTranslations (Toàn bộ):

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

Sản lượng hiện tại

Đưa ra truy vấn chính xác dưới đây, nó xuất ra:

Result
Billing

Kết quả mong muốn

Lý tưởng nhất, tôi muốn có thể bỏ qua phần cụ thể SupportCategories.Idvà nhận tất cả chúng, như vậy (bất kể ngôn ngữ 38 Englishđã được sử dụng hay 48 Frenchhoặc BẤT K Language ngôn ngữ nào khác vào lúc này):

Id  Result
0   Billing
1   API
2   Sales

Ví dụ bổ sung

Nếu tôi đã thêm một bản địa hóa cho French(Ie add 1 48 8to LanguageStringTranslations), đầu ra sẽ thay đổi thành (lưu ý: đây chỉ là ví dụ, rõ ràng tôi sẽ thêm một chuỗi nội địa hóa vào StringTranslations) (cập nhật với ví dụ tiếng Pháp):

Result
Les APIs

Đầu ra mong muốn bổ sung

Cho ví dụ trên, đầu ra sau đây sẽ được mong muốn (cập nhật với ví dụ bằng tiếng Pháp):

Id  Result
0   Billing
1   Les APIs
2   Sales

(Vâng, tôi biết về mặt kỹ thuật đó là sai từ quan điểm nhất quán, nhưng đó là điều sẽ được mong muốn trong tình huống này.)

Chỉnh sửa:

Được cập nhật nhỏ, tôi đã thay đổi cấu trúc của dbo.Languagesbảng và thả Id (int)cột khỏi nó và thay thế nó bằng Abbreviation(hiện được đổi tên thành Id, và tất cả các Khóa ngoài và các mối quan hệ được cập nhật). Theo quan điểm kỹ thuật, đây là một thiết lập phù hợp hơn theo quan điểm của tôi do thực tế là bảng được giới hạn ở các mã ISO 639-1, là duy nhất để bắt đầu.

Tl; dr

Vì vậy: các câu hỏi, làm thế nào có thể tôi sửa đổi truy vấn này để trả lại tất cả mọi thứ từ SupportCategoriesvà sau đó trở về hoặc là StringTranslations.Textcho rằng StringKeys.Id, Languages.Idsự kết hợp, hoặc những StringKeys.Namenếu nó đã không tồn tại?

Suy nghĩ ban đầu của tôi, là bằng cách nào đó tôi có thể chuyển truy vấn hiện tại sang một loại tạm thời khác như một truy vấn con khác và bọc truy vấn này trong một SELECTcâu lệnh khác và chọn hai trường tôi muốn ( SupportCategories.IdResult).

Nếu tôi không tìm thấy bất cứ điều gì, tôi sẽ chỉ thực hiện phương pháp tiêu chuẩn mà tôi thường sử dụng đó là tải tất cả SupportCategoriesvào dự án C # của mình, và sau đó với nó chạy truy vấn tôi có ở trên theo cách thủ công SupportCategories.Id.

Cảm ơn cho bất kỳ và tất cả các đề xuất / ý kiến ​​/ phê bình.

Ngoài ra, tôi xin lỗi vì nó quá dài một cách vô lý, tôi chỉ không muốn bất kỳ sự mơ hồ nào. Tôi thường xuyên vào StackOverflow và thấy những câu hỏi thiếu chất, không muốn mắc lỗi ở đây.

Câu trả lời:


16

Đây là cách tiếp cận đầu tiên tôi nghĩ ra:

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;

Về cơ bản, hãy lấy các chuỗi tiềm năng phù hợp với ngôn ngữ đã chọn nhận tất cả các chuỗi mặc định, sau đó tổng hợp để bạn chỉ chọn một chuỗi Idưu tiên cho ngôn ngữ đã chọn, sau đó lấy mặc định làm dự phòng.

Bạn có thể có thể làm những điều tương tự với UNION/ EXCEPTnhưng tôi nghi ngờ điều này hầu như sẽ luôn dẫn đến nhiều lần quét đối với cùng một đối tượng.


12

Một giải pháp thay thế giúp tránh INvà nhóm trong câu trả lời của Aaron:

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;

Như đã lưu ý, FORCESEEKgợi ý chỉ được yêu cầu để có được kế hoạch tìm kiếm hiệu quả nhất do tính chính xác của LanguageStringTranslationsbảng với dữ liệu mẫu được cung cấp. Với nhiều hàng hơn, trình tối ưu hóa sẽ chọn một chỉ mục tìm kiếm một cách tự nhiên.

Bản thân kế hoạch thực hiện có một tính năng thú vị:

Kế hoạch thực hiện

Thuộc tính Pass Through ở phép nối ngoài cuối cùng có nghĩa là việc tra cứu StringTranslationsbảng chỉ được thực hiện nếu một hàng được tìm thấy trước đó trong LanguageStringTranslationsbảng. Mặt khác, mặt trong của phép nối này được bỏ qua hoàn toàn cho hàng hiện tại.

Bảng 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)
);

Dữ liệu mẫu

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.