Así que aquí está mi escenario:
Estoy trabajando en la localización para un proyecto mío, y normalmente haría esto en el código C #, sin embargo, quiero hacerlo un poco más en SQL ya que estoy tratando de mejorar mi SQL un poco.
Entorno: SQL Server 2014 Standard, C # (.NET 4.5.1)
Nota: el lenguaje de programación en sí mismo debe ser irrelevante, solo lo incluyo para completarlo.
Así que logré lo que quería, pero no en la medida en que quería. Ha pasado un tiempo (al menos un año) desde que hice cualquier SQL JOIN, excepto los básicos, y esto es bastante complejo JOIN.
Aquí hay un diagrama de las tablas relevantes de la base de datos. (Hay muchos más, pero no son necesarios para esta porción).

Todas las relaciones descritas en la imagen están completas en la base de datos; las restricciones PKy FKestán configuradas y operativas. Ninguna de las columnas descritas son nullcapaces. Todas las tablas tienen el esquema dbo.
Ahora, tengo una consulta que casi hace lo que quiero: es decir, dado CUALQUIER ID SupportCategoriesy CUALQUIER ID de Languages, devolverá:
Si hay una traducción correcta, adecuada para ese idioma para esa cadena (Ie StringKeyId-> StringKeys.Idexiste, y en LanguageStringTranslations StringKeyId, LanguageIdy StringTranslationIdexiste combinación, entonces carga StringTranslations.Textpara que StringTranslationId.
Si el LanguageStringTranslations StringKeyId, LanguageIdy StringTranslationIdtenía la combinación no existe, entonces se carga el StringKeys.Namevalor. El Languages.Ides un hecho integer.
Mi consulta, ya sea un desastre, es la siguiente:
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
El problema es que no es capaz de proporcionar mí TODO del SupportCategoriesy sus respectivos StringTranslations.Textsi existe, o su StringKeys.Namesi no existiera. Es perfecto para proporcionar cualquiera de ellos, pero para nada. Básicamente, es para hacer cumplir que si un idioma no tiene una traducción para una clave específica, entonces lo predeterminado es usar StringKeys.Namecuál es la StringKeys.DefaultLanguageIdtraducción. (Idealmente, ni siquiera haría eso, sino que cargaría la traducción para StringKeys.DefaultLanguageId, lo que puedo hacer yo mismo si apunta en la dirección correcta para el resto de la consulta).
He dedicado MUCHO tiempo a esto, y sé que si solo lo escribiera en C # (como lo hago habitualmente) ya estaría hecho. Quiero hacer esto en SQL, y tengo problemas para obtener la salida que me gusta.
La única advertencia es que quiero limitar el número de consultas reales aplicadas. Todas las columnas están indexadas y, por el momento, me gustan, y sin pruebas de tensión reales no puedo indexarlas más.
Editar: Otra nota, estoy tratando de mantener la base de datos lo más normalizada posible, por lo que no quiero duplicar cosas si puedo evitarla.
Datos de ejemplo
Fuente
dbo.SupportCategories (Totalidad):
Id  StringKeyId
0   0
1   1
2   2
dbo.Languages (185 registros, mostrando solo dos como ejemplos):
Id  Abbreviation    Family  Name    Native
38  en  Indo-European   English English
48  fr  Indo-European   French  français, langue française
dbo.LanguagesStringTranslations (totalidad):
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 (Totalidad):
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 (totalidad):
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
Salida de corriente
Dada la consulta exacta a continuación, genera:
Result
Billing
Salida deseada
Idealmente, me gustaría poder omitir el específico SupportCategories.Idy obtener todos ellos, así (independientemente de si Englishse usó el idioma 38 , 48 Frencho CUALQUIER otro idioma en este momento):
Id  Result
0   Billing
1   API
2   Sales
Ejemplo adicional
Dado que debía agregar una localización para French(es decir, agregar 1 48 8a LanguageStringTranslations), la salida cambiaría a (nota: esto es solo un ejemplo, obviamente agregaría una cadena localizada a StringTranslations) (actualizado con un ejemplo francés):
Result
Les APIs
Salida deseada adicional
Dado el ejemplo anterior, se desearía el siguiente resultado (actualizado con el ejemplo francés):
Id  Result
0   Billing
1   Les APIs
2   Sales(Sí, sé que técnicamente eso está mal desde un punto de vista de coherencia, pero es lo que se desearía en la situación).
Editar:
Pequeño actualizado, cambié la estructura de la dbo.Languagestabla, solté la Id (int)columna y la reemplacé por Abbreviation(que ahora se renombró Idy se actualizaron todas las claves externas y relaciones relativas). Desde un punto de vista técnico, esta es una configuración más apropiada en mi opinión debido al hecho de que la tabla está limitada a los códigos ISO 639-1, que son únicos para empezar.
Tl; dr
Por lo tanto: la pregunta, ¿cómo podría modificar esta consulta para devolver todo a partir SupportCategoriesy luego regresar, ya sea StringTranslations.Textpara que StringKeys.Id, Languages.Idcombinados, o el StringKeys.Namesi lo hizo no existir?
Mi pensamiento inicial es que de alguna manera podría enviar la consulta actual a otro tipo temporal como otra subconsulta, y envolver esta consulta en otra SELECTdeclaración y seleccionar los dos campos que quiero ( SupportCategories.Idy Result).
Si no encuentro nada, simplemente haré el método estándar que normalmente uso, que es cargar todo SupportCategoriesen mi proyecto C #, y luego ejecutar con él la consulta que tengo arriba manualmente en cada uno SupportCategories.Id.
Gracias por todas y cada una de las sugerencias / comentarios / críticas.
Además, me disculpo por ser absurdamente largo, simplemente no quiero ninguna ambigüedad. A menudo estoy en StackOverflow y veo preguntas que carecen de sustancia, no quería cometer ese error aquí.
fuente

