Oto mój scenariusz:
Pracuję nad lokalizacją dla mojego projektu i zwykle robiłbym to w kodzie C #, jednak chcę to zrobić trochę więcej w SQL, ponieważ próbuję trochę wzmocnić mój SQL.
Środowisko: SQL Server 2014 Standard, C # (.NET 4.5.1)
Uwaga: sam język programowania powinien być nieistotny, podaję go tylko dla kompletności.
W pewnym sensie osiągnąłem to, czego chciałem, ale nie w takim stopniu, w jakim chciałem. Minęło trochę czasu (przynajmniej rok), odkąd stworzyłem dowolne SQL JOINpoza podstawowymi i jest to dość skomplikowane JOIN.
Oto schemat odpowiednich tabel bazy danych. (Jest o wiele więcej, ale nie jest to konieczne w tej części.)

Wszystkie relacje opisane na obrazie są kompletne w bazie danych - PKi FKwszystkie ograniczenia są konfigurowane i działają. Żadna z opisanych kolumn nie jest w nullstanie. Wszystkie tabele mają schemat dbo.
Teraz mam zapytanie, które prawie robi to, co chcę: to znaczy, biorąc pod uwagę DOWOLNY identyfikator SupportCategoriesi DOWOLNY identyfikator Languages, zwróci albo:
Jeśli istnieje tłumaczenie prawym właściwy dla danego języka do tego łańcucha (czyli StringKeyId-> StringKeys.Idistnieje, i LanguageStringTranslations StringKeyId, LanguageIdi StringTranslationIdpołączenie istnieje, to ładunki StringTranslations.Textza to StringTranslationId.
Jeżeli LanguageStringTranslations StringKeyId, LanguageIdi StringTranslationIdpołączenie było nie istnieje, to wczytuje StringKeys.Namewartość. To Languages.Idjest dane integer.
Moje zapytanie, czy to bałagan, wygląda następująco:
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
Problemem jest to, że nie jest w stanie zapewnić mi ALL z SupportCategoriesich odpowiednimi StringTranslations.Textjeśli istnieje, lub ich StringKeys.Namejeśli nie istnieje. Doskonale nadaje się do zapewnienia któregokolwiek z nich, ale wcale nie. Zasadniczo, jest to, aby wymusić, że jeśli język nie posiada tłumaczenie dla konkretnego klucza, po czym domyślnym jest użycie StringKeys.Nameco jest StringKeys.DefaultLanguageIdtłumaczeniem. (Idealnie nie zrobiłby tego, ale zamiast tego wczytuje tłumaczenie, dla StringKeys.DefaultLanguageIdktórego mogę zrobić sam, jeśli wskażę właściwy kierunek dla reszty zapytania).
Spędziłem na tym DUŻO czasu i wiem, że gdybym tylko napisał to w C # (jak zwykle to robię), byłoby to już zrobione. Chcę to zrobić w języku SQL i mam problem z uzyskaniem danych wyjściowych, które lubię.
Jedynym zastrzeżeniem jest to, że chcę ograniczyć liczbę zastosowanych zapytań. Wszystkie kolumny są indeksowane i tak jak na razie je lubię, i bez prawdziwych testów warunków skrajnych nie mogę ich dalej indeksować.
Edycja: Kolejna uwaga, staram się utrzymywać bazę danych tak znormalizowaną, jak to możliwe, więc nie chcę powielać rzeczy, jeśli mogę tego uniknąć.
Przykładowe dane
Źródło
dbo.SupportCategories (całość):
Id StringKeyId
0 0
1 1
2 2
dbo.Languages (185 rekordów, pokazano tylko dwa dla przykładów):
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 (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 (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
Wyjście prądowe
Biorąc pod uwagę dokładne zapytanie poniżej, generuje:
Result
Billing
Pożądane wyjście
Idealnie chciałbym móc pominąć konkretny SupportCategories.Idi uzyskać wszystkie z nich, tak jak jest (niezależnie od tego, czy Englishużyto języka 38 , czy 48 French, czy DOWOLNEGO innego języka w tej chwili):
Id Result
0 Billing
1 API
2 Sales
Dodatkowy przykład
Biorąc pod uwagę, że mam dodać lokalizację dla French(tj. Dodać 1 48 8do LanguageStringTranslations), dane wyjściowe zmieniłyby się na (uwaga: to tylko przykład, oczywiście dodałbym zlokalizowany ciąg StringTranslations) do (zaktualizowany o przykład z Francji):
Result
Les APIs
Dodatkowe pożądane wyjście
Biorąc pod uwagę powyższy przykład, pożądane byłyby następujące dane wyjściowe (zaktualizowane o przykład francuski):
Id Result
0 Billing
1 Les APIs
2 Sales
(Tak, wiem technicznie, że jest to niewłaściwe z punktu widzenia spójności, ale tego właśnie można by się spodziewać w tej sytuacji).
Edytować:
Małe zaktualizowane, zmieniłem strukturę dbo.Languagestabeli, upuściłem Id (int)z niej kolumnę i zastąpiłem ją Abbreviation(która jest teraz przemianowana na Idi wszystkie względne klucze obce i relacje zaktualizowane). Z technicznego punktu widzenia jest to, moim zdaniem, bardziej odpowiednia konfiguracja, ponieważ tabela jest ograniczona do kodów ISO 639-1, które są unikalne na początek.
Tl; dr
Tak: pytanie, jak mogę zmodyfikować tę kwerendę, aby powrócić wszystko od SupportCategoriesa następnie powrócić albo StringTranslations.Textza to StringKeys.Id, Languages.Idkombinacji, lubStringKeys.Name Jeśli tak nie istnieje?
Moją początkową myślą jest to, że mogę w jakiś sposób przerzucić bieżące zapytanie na inny typ tymczasowy jako inne podkwerendę i zawinąć to zapytanie w jeszcze jedną SELECTinstrukcję i wybrać dwa pola, które chcę ( SupportCategories.Idi Result).
Jeśli nic nie znajdę, zrobię standardową metodę, której zwykle używam, czyli załadowanie wszystkich SupportCategoriesdo mojego projektu w języku C #, a następnie uruchomię ręcznie dla każdego z powyższych zapytań SupportCategories.Id.
Dzięki za wszelkie sugestie / komentarze / krytykę.
Przepraszam też za to, że był absurdalnie długi, po prostu nie chcę żadnych dwuznaczności. Często używam StackOverflow i widzę pytania, które nie mają treści, nie chciałem tutaj popełnić tego błędu.
