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, JOIN
exceto 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 PK
e FK
estão todas configuradas e operacionais. Nenhuma das colunas descritas é null
capaz. 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 SupportCategories
e QUALQUER ID Languages
, ele retornará:
Se houver uma tradução da direita adequada para essa linguagem para essa string (Ie StringKeyId
-> StringKeys.Id
existe, e em LanguageStringTranslations
StringKeyId
, LanguageId
e StringTranslationId
existe combinação, então cargas StringTranslations.Text
para que StringTranslationId
.
Se o LanguageStringTranslations
StringKeyId
, LanguageId
e StringTranslationId
combinação que não existe, então ele carrega o StringKeys.Name
valor. 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 SupportCategories
e seus respectivos, StringTranslations.Text
se existir, OU seus, StringKeys.Name
se 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.Name
que é de StringKeys.DefaultLanguageId
traduçã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.Id
e obter todos eles, como tal (independentemente do idioma 38 ter English
sido usado ou 48 French
ou 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 8
a 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.Languages
tabela, 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 SupportCategories
e depois retornar tanto StringTranslations.Text
para isso StringKeys.Id
, Languages.Id
combinação, ou o StringKeys.Name
se 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 SELECT
instrução e selecionar os dois campos que eu quero ( SupportCategories.Id
e Result
).
Se não encontrar nada, farei o método padrão que normalmente uso para carregar tudo SupportCategories
no 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.