सभी रिकॉर्ड का चयन करें, यदि तालिका मौजूद है तो तालिका ए के साथ जुड़ें, यदि नहीं तो तालिका बी


20

तो यहाँ मेरा परिदृश्य है:

मैं अपनी एक परियोजना के लिए स्थानीयकरण पर काम कर रहा हूं, और आमतौर पर मैं इसे C # कोड में करने के बारे में बताता हूं, हालांकि मैं इसे SQL में थोड़ा और अधिक करना चाहता हूं क्योंकि मैं अपने SQL को थोड़ा ऊपर करने की कोशिश कर रहा हूं।

पर्यावरण: SQL सर्वर 2014 मानक, C # (.NET 4.5.1)

नोट: प्रोग्रामिंग भाषा स्वयं अप्रासंगिक होनी चाहिए, मैं केवल इसे पूर्णता के लिए शामिल कर रहा हूं।

इसलिए मैंने जो चाहा, उसे पूरा किया, लेकिन उस हद तक नहीं जितना मैं चाहता था। जब से मैंने कोई एसक्यूएल JOINएस किया है, तब तक यह थोड़ी देर (कम से कम एक वर्ष) की है, लेकिन यह काफी जटिल है JOIN

यहां डेटाबेस के संबंधित तालिकाओं का एक आरेख है। (बहुत अधिक हैं, लेकिन इस हिस्से के लिए आवश्यक नहीं हैं।)

डेटाबेस आरेख

छवि में वर्णित सभी रिश्ते डेटाबेस में पूर्ण हैं - PKऔर FKबाधाएं सभी सेटअप और ऑपरेटिंग हैं। वर्णित कोई भी स्तंभ nullसक्षम नहीं हैं। सभी तालिकाओं में स्कीमा है dbo

अब, मेरे पास एक क्वेरी है जो लगभग वही करता है जो मैं चाहता हूं: अर्थात, किसी भी प्रकार की कोई आईडी SupportCategoriesऔर कोई भी आईडी दी जाती है Languages, यह या तो वापस आ जाएगी:

अगर वहाँ है कि स्ट्रिंग के लिए उस भाषा (IE के लिए एक सही उचित अनुवाद है StringKeyId-> StringKeys.Idमौजूद है, और में LanguageStringTranslations StringKeyId, LanguageIdहै, और StringTranslationIdसंयोजन मौजूद है, तो यह भार StringTranslations.Textहै कि के लिए StringTranslationId

तो LanguageStringTranslations StringKeyId, LanguageIdहै, और StringTranslationIdसंयोजन किया नहीं मौजूद है, तो यह लोड करता है 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

समस्या यह है कि यह मुझे प्रदान करने में सक्षम नहीं है सभी की SupportCategoriesहै और उनके संबंधित StringTranslations.Textयदि वह मौजूद है, या उनके StringKeys.Nameअगर यह मौजूद नहीं था। यह उनमें से किसी एक को प्रदान करने में एकदम सही है, लेकिन बिल्कुल भी नहीं। असल में, यह लागू करना है कि यदि किसी भाषा में किसी विशिष्ट कुंजी के लिए अनुवाद नहीं है, तो डिफ़ॉल्ट का उपयोग करना है StringKeys.Nameजो StringKeys.DefaultLanguageIdअनुवाद का है। (आदर्श रूप से, यह भी ऐसा नहीं करेगा, बल्कि इसके लिए अनुवाद को लोड करता है StringKeys.DefaultLanguageId, जिसे मैं स्वयं कर सकता हूं यदि बाकी क्वेरी के लिए सही दिशा में बताया गया है।)

मैंने इस पर बहुत समय बिताया है, और मुझे पता है कि अगर मैं इसे सी # में लिखता (जैसे मैं आमतौर पर करता हूं) तो यह अब तक हो जाएगा। मैं एसक्यूएल में ऐसा करना चाहता हूं, और मुझे अपनी पसंद का आउटपुट प्राप्त करने में परेशानी हो रही है।

एकमात्र चेतावनी, क्या मैं लागू वास्तविक प्रश्नों की संख्या को सीमित करना चाहता हूं। सभी कॉलम अनुक्रमित हैं और जैसे कि मैं उन्हें अभी के लिए पसंद करता हूं, और वास्तविक तनाव-परीक्षण के बिना मैं उन्हें और अनुक्रमित नहीं कर सकता।

संपादित करें: एक और ध्यान दें, मैं डेटाबेस को यथासंभव सामान्य रखने की कोशिश कर रहा हूं, इसलिए मैं चीजों को डुप्लिकेट नहीं करना चाहता अगर मैं इससे बच सकता हूं।

उदाहरण डेटा

स्रोत

dbo.SupportCategories (संपूर्ण):

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.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

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, और सभी रिश्तेदार फॉरेन-की और और रिश्ते अपडेट किए गए हैं)। एक तकनीकी दृष्टिकोण से, यह इस तथ्य के कारण मेरी राय में अधिक उपयुक्त सेटअप है कि तालिका आईएसओ 639-1 कोडों तक सीमित है, जो कि शुरुआत के लिए अद्वितीय हैं।

Tl, डॉ

तो: सवाल है, मैं कैसे वापसी के लिए इस क्वेरी को संशोधित कर सकता है सब कुछ से SupportCategoriesऔर फिर या तो वापस कर StringTranslations.Textउस के लिए StringKeys.Id, Languages.Idसंयोजन, याStringKeys.Name यह था कि अगर नहीं मौजूद है?

मेरा प्रारंभिक विचार यह है कि मैं किसी अन्य अस्थायी प्रकार के रूप में वर्तमान क्वेरी को किसी अन्य उपकुंजी के रूप में डाल सकता हूं, और इस क्वेरी को अभी तक किसी अन्य SELECTविवरण में लपेट सकता हूं और उन दो क्षेत्रों का चयन कर सकता हूं जो मैं चाहता हूं ( SupportCategories.Idऔर Result)।

अगर मुझे कुछ नहीं मिलता है, तो मैं बस मानक विधि का उपयोग करता हूं जो मैं आमतौर पर उपयोग करता हूं, जो कि SupportCategoriesमेरे सी # प्रोजेक्ट में सभी को लोड करना है, और फिर इसके साथ क्वेरी को चलाने के लिए जो मैंने ऊपर प्रत्येक के खिलाफ मैन्युअल रूप से किया है 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हारून के जवाब में और समूहन से बचा जाता है :

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प्रदान किए गए नमूना डेटा के साथ तालिका की कम हृदयता के कारण सबसे कुशल दिखने वाली योजना प्राप्त करने के लिए आवश्यक है । अधिक पंक्तियों के साथ, ऑप्टिमाइज़र स्वाभाविक रूप से एक सूचकांक की तलाश करेगा।

निष्पादन योजना में एक दिलचस्प विशेषता है:

निष्पादन योजना

अंतिम बाहरी जॉइन पर पास थ्रू प्रॉपर्टी का मतलब है कि StringTranslationsटेबल के लिए एक लुकअप केवल तभी किया जाता है जब एक पंक्ति पहले LanguageStringTranslationsटेबल में मिली थी । अन्यथा, इस जुड़ाव का आंतरिक पक्ष वर्तमान पंक्ति के लिए पूरी तरह से छोड़ दिया जाता है।

तालिका डीडीएल

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.