Итак, вот мой сценарий:
Я работаю над локализацией для моего проекта, и обычно я делаю это в коде C #, однако я хочу сделать это в SQL немного больше, так как я пытаюсь немного улучшить свой SQL.
Среда: SQL Server 2014 Standard, C # (.NET 4.5.1)
Примечание: сам язык программирования не имеет значения, я включаю его только для полноты.
Так что я выполнил то, что хотел, но не так, как хотел. Прошло много времени (по крайней мере, год) с тех пор, как я выполнил любые SQL, JOIN
кроме базовых, и это довольно сложно JOIN
.
Вот схема соответствующих таблиц базы данных. (Есть еще много, но не обязательно для этой части.)
Все отношения, описанные в образе, завершены в базе данных - PK
и FK
ограничения и все настроены и работают. Ни один из описанных столбцов не в null
состоянии. Все таблицы имеют схему dbo
.
Теперь у меня есть запрос, который почти выполняет то, что я хочу: то есть, учитывая ЛЮБОЙ Id SupportCategories
и ЛЮБОЙ Id 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
, который я мог бы сделать сам, если бы указывал в правильном направлении для остальной части запроса.)
Я потратил много времени на это, и я знаю, если бы я просто написал это на C # (как я обычно делаю), это было бы сделано к настоящему времени. Я хочу сделать это в SQL, и у меня возникают проблемы с получением результата, который мне нравится.
Единственное предостережение: я хочу ограничить количество примененных запросов. Все столбцы проиндексированы, и такие, какие мне нравятся сейчас, и без реального стресс-тестирования я не смогу их проиндексировать дальше.
Изменить: еще одно примечание, я пытаюсь сохранить базу данных максимально нормализованным, поэтому я не хочу дублировать вещи, если я могу избежать этого.
Пример данных
Источник
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 (Completety):
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 (Completety):
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 (Completety):
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
(т.е. добавить 1 48 8
к LanguageStringTranslations
), вывод изменится на (примечание: это только пример, очевидно, я бы добавил локализованную строку в StringTranslations
) (обновлено на французском примере):
Result
Les APIs
Дополнительный желаемый выход
Учитывая приведенный выше пример, был бы желателен следующий вывод (обновленный на французском примере):
Id Result
0 Billing
1 Les APIs
2 Sales
(Да, технически я знаю, что это неправильно с точки зрения согласованности, но это то, что было бы желательно в данной ситуации.)
Редактировать:
Небольшое обновление. Я изменил структуру dbo.Languages
таблицы, удалил Id (int)
из нее столбец и заменил его Abbreviation
(который теперь переименован Id
, а все относительные внешние ключи и и отношения обновлены). С технической точки зрения, на мой взгляд, это более подходящая установка, поскольку таблица ограничена кодами ISO 639-1, которые с самого начала являются уникальными.
Tl; др
Итак: вопрос о том , как я мог изменить этот запрос, возвращающий все от SupportCategories
и затем возвращать либо StringTranslations.Text
для того StringKeys.Id
, Languages.Id
комбинации, илиStringKeys.Name
если она НЕ существует?
Моя первоначальная мысль, что я мог бы как-то привести текущий запрос к другому временному типу в качестве другого подзапроса, обернуть этот запрос еще одним SELECT
оператором и выбрать два поля, которые я хочу ( SupportCategories.Id
и Result
).
Если я ничего не найду, я просто использую стандартный метод, который я обычно использую, который загружает все SupportCategories
в мой проект C #, а затем запускает запрос, который у меня есть выше, вручную для каждого SupportCategories.Id
.
Спасибо за любые предложения / комментарии / критику.
Кроме того, я прошу прощения за то, что это нелепо долго, я просто не хочу никакой двусмысленности. Я часто бываю на StackOverflow и вижу вопросы, в которых нет смысла, я не хотел бы допустить эту ошибку здесь.