Tüm kayıtları seçin, birleştirme varsa A tablosuna katılın, yoksa B tablosuna katılın


20

İşte benim senaryom:

Benim bir proje için yerelleştirme üzerinde çalışıyorum ve genellikle benim SQL biraz buff çalışıyorum çünkü ben biraz daha SQL'de bunu yapmak istiyorum, ancak ben C # kodunda bunu hakkında gitmek istiyorum.

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

Not: Programlama dilinin kendisi alakasız olmalı, sadece tamlık için dahil ediyorum.

Bu yüzden istediğimi başardım, ama istediğim ölçüde değil. JOINTemel olanlar dışında SQL'ler yaptığımdan beri bir süre (en az bir yıl) oldu ve bu oldukça karmaşık JOIN.

İşte veritabanının ilgili tablolarının bir diyagramı. (Bu bölüm için çok daha fazlası var, ancak gerekli değil.)

Veritabanı Şeması

Resimde açıklanan tüm ilişkiler veritabanında tamamlanmıştır - PKve FKkısıtlamaların hepsi kurulum ve işletimdir. Açıklanan sütunların hiçbiri nullmümkün değil. Tüm tabloların şeması vardır dbo.

Şimdi, neredeyse ne istediğimi yapan bir sorgu var : yani, herhangi bir kimliği SupportCategoriesve herhangi bir kimliği verildi Languages, ya da dönecektir:

Bu dizi için bu dil (Ie için sağ doğru çeviri varsa StringKeyId-> StringKeys.Idvar ve içinde LanguageStringTranslations StringKeyId, LanguageIdve StringTranslationIdkombinasyon daha sonra yükler, mevcut StringTranslations.Textbunun için StringTranslationId.

Eğer LanguageStringTranslations StringKeyId, LanguageIdve StringTranslationIdkombinasyon vermedi DEĞİL var, o zaman yükler StringKeys.Namedeğeri. Languages.IdBir verilir integer.

Sorgu, karışıklık olsun, aşağıdaki gibidir:

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

Sorun şu ki beni sağlama yeteneğine olmadığıdır TÜM ait SupportCategoriesve kendi StringTranslations.Textvarsa, YA onların StringKeys.Nameo olmasaydı. Bunlardan herhangi birini sağlamak mükemmel, ama hiç değil. Temel olarak, bir dilin belirli bir anahtar için çevirisi yoksa, varsayılanın StringKeys.Namehangisinin StringKeys.DefaultLanguageIdçeviri olduğunu kullanmaktır . (İdeal olarak, bunu bile yapmaz, bunun yerine çeviriyi yükler, bunun yerine StringKeys.DefaultLanguageIdsorgunun geri kalanı için doğru yönde işaret edersem kendim yapabilirim.)

Ben bu konuda çok zaman geçirdim, ve ben sadece C # (genellikle yaptığım gibi) yazacak olsaydı şimdiye kadar yapılacaktı biliyorum. Bunu SQL'de yapmak istiyorum ve sevdiğim çıktıyı almakta sorun yaşıyorum.

Tek uyarı, uygulanan gerçek sorgu sayısını sınırlamak istiyorum. Tüm sütunlar dizine eklenmiştir ve şimdilik onları beğendim ve gerçek stres testi olmadan bunları daha fazla dizine ekleyemiyorum.

Düzenleme: Başka bir not, ben mümkün olduğunca normalleştirilmiş veritabanı tutmak için çalışıyorum, bu yüzden ben önleyebilirsiniz şeyler çoğaltmak istemiyorum.

Örnek Veriler

Kaynak

dbo.SupportCategories (Tamamen):

Id  StringKeyId
0   0
1   1
2   2

dbo.Languages ​​(185 kayıt, örnekler için sadece iki tane gösterilmektedir):

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

dbo.LanguagesStringTranslations (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 (Tamamen):

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 (Entirety):

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

Akım Çıkışı

Aşağıdaki kesin sorgu göz önüne alındığında, çıktı:

Result
Billing

Istenilen çıktı

İdeal olarak, belirli bir şeyi atlamak SupportCategories.Idve hepsini elde etmek istiyorum, böylece ( şu anda 38 dilinin Englishkullanılıp kullanılmadığına veya 48 Frenchveya şu anda HERHANGİ başka bir dil kullanılmasına bakılmaksızın ):

Id  Result
0   Billing
1   API
2   Sales

Ek Örnek

French(Ie add ) için bir yerelleştirme eklemek 1 48 8için LanguageStringTranslationsverildiğinde, çıkış olarak değişecektir (not: bu sadece örnektir, açıkçası yerelleştirilmiş bir dize ekleyeceğim StringTranslations) (Fransızca örnekle güncellendi):

Result
Les APIs

Ek İstenen Çıktı

Yukarıdaki örnek göz önüne alındığında, aşağıdaki çıktı istenecektir (Fransızca örnekle güncellenmiştir):

Id  Result
0   Billing
1   Les APIs
2   Sales

(Evet, teknik olarak bunun tutarlılık açısından yanlış olduğunu biliyorum, ancak bu durumda istenen şey budur.)

Düzenle:

Küçük güncellendi, dbo.Languagestablonun yapısını değiştirdim ve Id (int)sütunu ondan bıraktım ve onunla değiştirdim Abbreviation(şimdi yeniden adlandırıldı Idve tüm göreceli Yabancı Anahtarlar ve ve ilişkiler güncellendi). Teknik açıdan bakıldığında, tablonun başlangıçta benzersiz olan ISO 639-1 kodları ile sınırlı olması nedeniyle bu daha uygun bir kurulum.

Tl, Dr.

Yani: Soru, nasıl dönüş için bu sorguyu değiştirebilir şeyi gelen SupportCategoriesve ardından ya dönmek StringTranslations.Textbunun için StringKeys.Id, Languages.Idkombinasyonu, yaStringKeys.Name da eğer DEĞİL var?

İlk düşüncem, bir şekilde geçerli sorguyu başka bir alt sorgu olarak başka bir geçici sorguya atabiliyor ve bu sorguyu başka bir SELECTifadeye sarıp istediğim ( SupportCategories.Idve Result) iki alanı seçebileceğim .

Bir şey bulamazsam, sadece tipik olarak tüm SupportCategoriesC # projeme yüklemek için kullandığım standart yöntemi yapacağım ve sonra onunla her birine karşı el ile yukarıdaki sorguyu çalıştırın SupportCategories.Id.

Her türlü öneri / yorum / eleştiri için teşekkürler.

Ayrıca saçma sapan bir şey olduğu için özür dilerim, herhangi bir belirsizlik istemiyorum. Sık sık StackOverflow üzerinde ve madde eksikliği, burada bu hata yapmak istemiyordu soruları görmek.

Yanıtlar:


16

İşte geldiğim ilk yaklaşım:

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;

Temel olarak, seçilen dille eşleşen potansiyel dizeleri alın ve tüm varsayılan dizeleri alın, ardından toplayın Id, böylece seçilen dile göre yalnızca bir öncelik verin, ardından varsayılanı bir yedek olarak alın.

Muhtemelen UNION/ ile benzer şeyler yapabilirsiniz, EXCEPTancak bunun neredeyse her zaman aynı nesnelere karşı birden fazla taramaya yol açacağından şüpheleniyorum.


12

INAaron'un cevabında ve gruplandırmadan kaçınan alternatif bir çözüm :

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;

Belirtildiği gibi, FORCESEEKipucu sadece LanguageStringTranslationssağlanan örnek verilerle tablonun düşük kardinalliğinden dolayı en verimli görünümlü planı elde etmek için gereklidir . Daha fazla satırla, optimizer doğal olarak bir dizin araması seçer.

Uygulama planının kendisi ilginç bir özelliğe sahiptir:

Yürütme planı

Son dış birleşimdeki Geçiş özelliği, StringTranslationstabloya bir aramanın yalnızca tabloda daha önce bir satır bulunması durumunda gerçekleştirildiği anlamına gelir LanguageStringTranslations. Aksi takdirde, bu birleştirmenin iç tarafı geçerli satır için tamamen atlanır.

Tablo 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)
);

Örnek veri

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.