Então, aqui está o meu cenário:
Estou trabalhando na localização para um projeto meu e, normalmente, eu faria isso no código C #, no entanto, quero fazer isso no SQL um pouco mais, pois estou tentando aprimorar um pouco o meu SQL.
Ambiente: SQL Server 2014 Standard, C # (.NET 4.5.1)
Nota: a linguagem de programação em si deve ser irrelevante, só a incluo por completo.
Então, eu meio que consegui o que queria, mas não na medida em que queria. Já faz um tempo (pelo menos um ano) desde que eu fiz SQL, JOINexceto os básicos, e isso é bastante complexo JOIN.
Aqui está um diagrama das tabelas relevantes do banco de dados. (Há muito mais, mas não é necessário para esta parte.)

Todos os relacionamentos descritos na imagem estão completos no banco de dados - as restrições PKe FKestão todas configuradas e operacionais. Nenhuma das colunas descritas é nullcapaz. Todas as tabelas têm o esquema dbo.
Agora, eu tenho uma consulta que quase faz o que eu quero: ou seja, considerando QUALQUER ID SupportCategoriese QUALQUER ID Languages, ele retornará:
Se houver uma tradução da direita adequada para essa linguagem para essa string (Ie StringKeyId-> StringKeys.Idexiste, e em LanguageStringTranslations StringKeyId, LanguageIde StringTranslationIdexiste combinação, então cargas StringTranslations.Textpara que StringTranslationId.
Se o LanguageStringTranslations StringKeyId, LanguageIde StringTranslationIdcombinação que não existe, então ele carrega o StringKeys.Namevalor. O Languages.Idé um dado integer.
Minha consulta, seja uma bagunça, é a seguinte:
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
O problema é que ele não é capaz de me fornecer TODOS os SupportCategoriese seus respectivos, StringTranslations.Textse existir, OU seus, StringKeys.Namese não existir. É perfeito para fornecer qualquer um deles, mas nem um pouco. Basicamente, é impor que, se um idioma não possui uma tradução para uma chave específica, o padrão é usar o StringKeys.Nameque é de StringKeys.DefaultLanguageIdtradução. (Idealmente, nem faria isso, mas, ao invés disso, carregaria a tradução StringKeys.DefaultLanguageId, o que eu posso fazer se apontado na direção certa para o restante da consulta.)
Eu gastei muito tempo nisso, e sei que se eu escrevesse em C # (como eu costumo fazer), já estaria feito. Quero fazer isso no SQL e estou tendo problemas para obter a saída que eu gosto.
A única ressalva é que eu quero limitar o número de consultas reais aplicadas. Todas as colunas são indexadas e, como eu gosto, por enquanto, e sem testes de estresse reais, não posso indexá-las mais.
Edit: Outra observação, estou tentando manter o banco de dados o mais normalizado possível, para que não queira duplicar as coisas, se puder evitá-lo.
Dados de exemplo
Fonte
dbo.SupportCategories (totalidade):
Id StringKeyId
0 0
1 1
2 2
dbo.Languages (185 registros, mostrando apenas dois por exemplo):
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 (totalidade):
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 (totalidade):
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
Saída atual
Dada a consulta exata abaixo, ela gera:
Result
Billing
Saída desejada
Idealmente, eu gostaria de poder omitir o específico SupportCategories.Ide obter todos eles, como tal (independentemente do idioma 38 ter Englishsido usado ou 48 Frenchou QUALQUER outro idioma no momento):
Id Result
0 Billing
1 API
2 Sales
Exemplo Adicional
Dado que eu adicionaria uma localização para French(ou seja, adicionar 1 48 8a LanguageStringTranslations), a saída mudaria para (nota: este é apenas um exemplo, obviamente eu adicionaria uma string localizada a StringTranslations) (atualizada com o exemplo em francês):
Result
Les APIs
Saída desejada adicional
Dado o exemplo acima, a seguinte saída seria desejada (atualizada com exemplo em francês):
Id Result
0 Billing
1 Les APIs
2 Sales
(Sim, eu sei tecnicamente que isso está errado do ponto de vista da consistência, mas é o que seria desejado na situação.)
Editar:
Pequeno atualizado, mudei a estrutura da dbo.Languagestabela, larguei a Id (int)coluna e a substitui por Abbreviation(que agora é renomeada para Id, e todas as chaves estrangeiras e relacionamentos relativos atualizados). Do ponto de vista técnico, essa é uma configuração mais apropriada, na minha opinião, devido ao fato de a tabela estar limitada aos códigos ISO 639-1, que são exclusivos para começar.
Tl; dr
Assim: a questão, como eu poderia modificar esta consulta para retorno tudo a partir SupportCategoriese depois retornar tanto StringTranslations.Textpara isso StringKeys.Id, Languages.Idcombinação, ou o StringKeys.Namese o fizesse não existe?
Meu pensamento inicial é que, de alguma forma, eu poderia converter a consulta atual para outro tipo temporário como outra subconsulta, e agrupar essa consulta em outra SELECTinstrução e selecionar os dois campos que eu quero ( SupportCategories.Ide Result).
Se não encontrar nada, farei o método padrão que normalmente uso para carregar tudo SupportCategoriesno meu projeto C # e, em seguida, execute a consulta que tenho acima manualmente manualmente em cada um SupportCategories.Id.
Obrigado por toda e qualquer sugestão / comentário / crítica.
Além disso, peço desculpas por ser absurdamente longo, só não quero ambiguidade. Frequentemente, estou no StackOverflow e vejo perguntas que não têm substância, que não desejam cometer esse erro aqui.
