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 JOIN
poza 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 - PK
i FK
wszystkie ograniczenia są konfigurowane i działają. Żadna z opisanych kolumn nie jest w null
stanie. Wszystkie tabele mają schemat dbo
.
Teraz mam zapytanie, które prawie robi to, co chcę: to znaczy, biorąc pod uwagę DOWOLNY identyfikator SupportCategories
i 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.Id
istnieje, i LanguageStringTranslations
StringKeyId
, LanguageId
i StringTranslationId
połączenie istnieje, to ładunki StringTranslations.Text
za to StringTranslationId
.
Jeżeli LanguageStringTranslations
StringKeyId
, LanguageId
i StringTranslationId
połączenie było nie istnieje, to wczytuje StringKeys.Name
wartość. To Languages.Id
jest 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 SupportCategories
ich odpowiednimi StringTranslations.Text
jeśli istnieje, lub ich StringKeys.Name
jeś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.Name
co jest StringKeys.DefaultLanguageId
tłumaczeniem. (Idealnie nie zrobiłby tego, ale zamiast tego wczytuje tłumaczenie, dla StringKeys.DefaultLanguageId
któ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.Id
i uzyskać wszystkie z nich, tak jak jest (niezależnie od tego, czy English
uż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 8
do 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.Languages
tabeli, upuściłem Id (int)
z niej kolumnę i zastąpiłem ją Abbreviation
(która jest teraz przemianowana na Id
i 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 SupportCategories
a następnie powrócić albo StringTranslations.Text
za to StringKeys.Id
, Languages.Id
kombinacji, 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ą SELECT
instrukcję i wybrać dwa pola, które chcę ( SupportCategories.Id
i Result
).
Jeśli nic nie znajdę, zrobię standardową metodę, której zwykle używam, czyli załadowanie wszystkich SupportCategories
do 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.