เลือกระเบียนทั้งหมดเข้าร่วมกับตาราง A หากมีอยู่แล้วเข้าร่วมตาราง B ถ้าไม่ใช่


20

ดังนั้นนี่คือสถานการณ์ของฉัน:

ฉันทำงาน Localization สำหรับโครงการของฉันและโดยทั่วไปฉันจะทำเช่นนี้ในรหัส C # แต่ฉันต้องการทำเช่นนี้ใน SQL อีกเล็กน้อยตั้งแต่ฉันพยายามควัก SQL เล็กน้อย

สภาพแวดล้อม: มาตรฐาน SQL Server 2014, C # (.NET 4.5.1)

หมายเหตุ: ภาษาการเขียนโปรแกรมควรไม่เกี่ยวข้องฉันเพียง แต่รวมไว้เพื่อความสมบูรณ์เท่านั้น

ดังนั้นฉันจึงประสบความสำเร็จในสิ่งที่ฉันต้องการ แต่ไม่เท่าที่ฉันต้องการ จะได้รับในขณะที่ (อย่างน้อยปี) ตั้งแต่ผมได้กระทำใด ๆ SQL JOINs JOINยกเว้นคนพื้นฐานและนี้ค่อนข้างซับซ้อน

นี่คือ diagramme ของตารางที่เกี่ยวข้องของฐานข้อมูล (มีอีกมากมาย แต่ไม่จำเป็นสำหรับส่วนนี้)

ฐานข้อมูลไดอะแกรม

ความสัมพันธ์ทั้งหมดที่อธิบายในภาพเสร็จสมบูรณ์ในฐานข้อมูล - PKและFKข้อ จำกัด คือการตั้งค่าและการใช้งานทั้งหมด ไม่มีคอลัมน์ใดที่อธิบายnullได้ dboตารางทั้งหมดมีแบบแผน

ตอนนี้ฉันมีคำถามที่เกือบจะทำสิ่งที่ฉันต้องการ: นั่นคือให้ใด ๆจากรหัสSupportCategoriesและรหัสใด ๆของLanguagesมันก็จะกลับมาอย่างใดอย่างหนึ่ง:

หากมีการแปลขวาที่เหมาะสมสำหรับภาษานั้นสำหรับสตริงที่ (IE StringKeyId-> StringKeys.Idมีอยู่และในLanguageStringTranslations StringKeyId,LanguageIdและStringTranslationIdรวมกันอยู่แล้วมันโหลดที่StringTranslations.TextStringTranslationId

ถ้าLanguageStringTranslations StringKeyId, LanguageIdและStringTranslationIdรวมกันได้ไม่อยู่แล้วมันโหลดStringKeys.Nameค่า จะได้รับLanguages.Idinteger

ข้อความค้นหาของฉันไม่ว่าจะเป็นเรื่องยุ่งมีดังต่อไปนี้:

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

ปัญหาคือว่ามันไม่ได้มีความสามารถในการให้ฉันทั้งหมดของSupportCategoriesและของตนStringTranslations.Textถ้ามันมีอยู่แล้วหรือของพวกเขาStringKeys.Nameถ้ามันไม่ได้อยู่ มันสมบูรณ์แบบในการให้บริการอย่างใดอย่างหนึ่งของพวกเขา แต่ไม่ได้เลย โดยพื้นฐานแล้วมันคือการบังคับใช้ว่าถ้าภาษาไม่มีการแปลสำหรับคีย์เฉพาะดังนั้นค่าเริ่มต้นคือการใช้StringKeys.Nameซึ่งเป็นของการStringKeys.DefaultLanguageIdแปล (เป็นการดีที่จะไม่ทำอย่างนั้น แต่แทนที่จะโหลดการแปลStringKeys.DefaultLanguageIdซึ่งฉันสามารถทำเองได้หากชี้ไปในทิศทางที่ถูกต้องสำหรับส่วนที่เหลือของการค้นหา)

ฉันใช้เวลาไปมากกับเรื่องนี้และฉันรู้ว่าถ้าฉันจะเขียนมันใน C # (เหมือนที่ฉันทำตามปกติ) ตอนนี้จะเสร็จแล้ว ฉันต้องการทำสิ่งนี้ใน SQL และฉันมีปัญหาในการรับผลลัพธ์ที่ฉันชอบ

ข้อแม้เดียวคือฉันต้องการ จำกัด จำนวนการค้นหาจริงที่ใช้ คอลัมน์ทั้งหมดได้รับการจัดทำดัชนีและอย่างที่ฉันชอบในตอนนี้และหากไม่มีการทดสอบความเครียดจริง ๆ ฉันก็ไม่สามารถจัดทำดัชนีต่อไปได้

แก้ไข: บันทึกย่ออื่นฉันกำลังพยายามทำให้ฐานข้อมูลเป็นปกติที่สุดเท่าที่จะทำได้ดังนั้นฉันไม่ต้องการทำซ้ำสิ่งต่าง ๆ หากฉันสามารถหลีกเลี่ยงได้

ตัวอย่างข้อมูล

แหล่ง

dbo. สนับสนุนหมวดหมู่ (ทั้งหมด):

Id  StringKeyId
0   0
1   1
2   2

dbo.Languages ​​(185 บันทึก, แสดงตัวอย่างสองรายการเท่านั้น):

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

dbo.LanguageStringTranslations (ทั้งหมด):

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 (ทั้งหมด):

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 (ทั้งหมด):

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

กระแสไฟขาออก

รับแบบสอบถามแน่นอนด้านล่างมันส่งออก:

Result
Billing

ผลลัพธ์ที่ต้องการ

เป็นการดีที่ฉันต้องการจะสามารถละเว้นเฉพาะSupportCategories.Idและรับพวกเขาทั้งหมดดังนั้น (ไม่ว่าจะใช้ภาษา 38 Englishหรือ 48 Frenchหรือภาษาอื่นใดในขณะนี้):

Id  Result
0   Billing
1   API
2   Sales

ตัวอย่างเพิ่มเติม

ป.ร. ให้ไว้ผมจะเพิ่มการแปลสำหรับFrench(IE เพิ่ม1 48 8ไปLanguageStringTranslations) เอาท์พุทจะเปลี่ยนไป (หมายเหตุ: นี่เป็นเพียงตัวอย่างที่เห็นได้ชัดฉันจะเพิ่มสตริงภาษาท้องถิ่นStringTranslations) (ปรับปรุงด้วยเช่นฝรั่งเศส):

Result
Les APIs

ผลลัพธ์ที่ต้องการเพิ่มเติม

รับตัวอย่างข้างต้นผลลัพธ์ต่อไปนี้จะต้องการ (ปรับปรุงด้วยตัวอย่างภาษาฝรั่งเศส)

Id  Result
0   Billing
1   Les APIs
2   Sales

(ใช่ฉันรู้ว่าเทคนิคผิดจากจุดยืนที่สอดคล้องกัน แต่เป็นสิ่งที่ต้องการในสถานการณ์)

แก้ไข:

อัปเดตเล็กน้อยฉันเปลี่ยนโครงสร้างของdbo.LanguagesตารางและวางId (int)คอลัมน์จากมันและแทนที่ด้วยAbbreviation(ซึ่งตอนนี้เปลี่ยนชื่อเป็นIdและทั้งหมด Foreign-Keys และสัมพัทธ์และความสัมพันธ์ที่อัปเดตแล้ว) จากมุมมองทางเทคนิคนี่คือการตั้งค่าที่เหมาะสมมากขึ้นในความคิดของฉันเนื่องจากความจริงที่ว่าตารางนั้น จำกัด รหัส ISO 639-1 ซึ่งไม่เหมือนใครในการเริ่มต้น

tl; DR

ดังนั้น: คำถามที่ว่าฉันจะปรับเปลี่ยนแบบสอบถามนี้เพื่อกลับมาทุกอย่างจากSupportCategoriesนั้นกลับอย่างใดอย่างหนึ่งStringTranslations.Textที่StringKeys.Id, Languages.IdรวมหรือStringKeys.Nameถ้ามันไม่ไม่อยู่?

ความคิดเริ่มต้นของฉันคือว่าฉันสามารถส่งแบบสอบถามปัจจุบันไปยังอีกประเภทชั่วคราวเป็นแบบสอบถามย่อยอื่นและตัดแบบสอบถามนี้ในSELECTงบอื่นและเลือกสองเขตข้อมูลที่ฉันต้องการ ( SupportCategories.IdและResult )

ถ้าฉันไม่ได้พบสิ่งที่ฉันจะทำวิธีการมาตรฐานที่ผมมักจะใช้ซึ่งก็คือการโหลดทั้งหมดSupportCategoriesในโครงการของฉัน C # SupportCategories.Idแล้วกับมันเรียกใช้แบบสอบถามฉันมีดังกล่าวข้างต้นด้วยตนเองกับแต่ละ

ขอบคุณสำหรับคำแนะนำ / ความคิดเห็น / คำวิจารณ์ใด ๆ และทั้งหมด

นอกจากนี้ฉันต้องขอโทษด้วยที่มันนานมากฉันแค่ไม่ต้องการความคลุมเครือ ฉันมักจะอยู่ใน StackOverflow และดูคำถามที่ไม่มีเนื้อหาไม่ต้องการทำผิดพลาดที่นี่

คำตอบ:


16

นี่เป็นวิธีแรกที่ฉันคิด:

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;

โดยทั่วไปรับสตริงที่มีศักยภาพที่ตรงกับภาษาที่เลือกและรับสตริงเริ่มต้นทั้งหมดแล้วรวมเพื่อให้คุณเลือกเพียงหนึ่งต่อId - จัดลำดับความสำคัญกับภาษาที่เลือกจากนั้นใช้ค่าเริ่มต้นเป็นทางเลือก

คุณอาจทำสิ่งที่คล้ายกันด้วยUNION/ EXCEPTแต่ฉันคิดว่าสิ่งนี้จะนำไปสู่การสแกนหลายครั้งกับวัตถุเดียวกัน


12

ทางเลือกอื่นที่หลีกเลี่ยงINและการจัดกลุ่มในคำตอบของ 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;

ดังที่ระบุไว้FORCESEEKคำแนะนำนั้นจำเป็นสำหรับการวางแผนที่ดูมีประสิทธิภาพมากที่สุดเนื่องจากความสำคัญเชิงลบของLanguageStringTranslationsตารางที่มีข้อมูลตัวอย่างที่ให้ไว้เท่านั้น ด้วยแถวเพิ่มเติมเครื่องมือเพิ่มประสิทธิภาพจะเลือกดัชนีค้นหาตามธรรมชาติ

แผนการดำเนินการเองมีคุณสมบัติที่น่าสนใจ:

แผนปฏิบัติการ

คุณสมบัติ Pass Through ในการเข้าร่วม outer ด้านนอกหมายความว่าการค้นหาไปยัง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)
);

ตัวอย่างข้อมูล

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.