Base de datos detrás de una interfaz de usuario multilingüe

8

Esta pregunta es sobre un tema algo más complicado que el que ya se ha abordado en estas viejas preguntas, todas las cuales son duplicados entre sí:

Sugerencia para la estructura de la base de datos para varios idiomas (junio de 2011)

¿Cuál es la mejor estructura de base de datos para mantener datos multilingües? (Febrero de 2010)

¿Cuáles son las mejores prácticas para el diseño de bases de datos en varios idiomas? (Mayo de 2009)

Esquema para una base de datos multilenguaje (noviembre de 2008)


El esquema de base de datos más popular para respaldar interfaces de usuario multilingües parece ser tener todos los textos traducidos de todos los idiomas en una tabla con 3 columnas: la identificación del texto, el código del idioma y el texto en sí. La identificación de texto y el código de idioma juntos forman la clave principal.

Todo eso está muy bien, pero ahora considera una complicación: supongamos que los textos deben poder buscarse. Supongamos, por ejemplo, que se trata de una tienda electrónica en varios idiomas. Esto significa que para cada categoría de producto ingresada en la base de datos, el propietario de la tienda ingresará el nombre de la categoría de producto en todos y cada uno de los N idiomas admitidos, y luego el comprador podrá buscar la categoría de producto por nombre, en su propio idioma .

Hay un problema: colación .

Diferentes idiomas tienen diferentes secuencias de clasificación, y la secuencia de clasificación que funciona para un idioma no funciona para otro. Entonces, si todos los textos de todos los idiomas están en una sola columna, ¿qué secuencia de clasificación van a tener? ¿Cómo vamos a consultar la base de datos para encontrar la identificación de texto de un texto específico? Si bien en una búsqueda de productos web, la precisión y el rendimiento pueden no ser terriblemente importantes, a los fines de esta discusión, supongamos que realmente importan.

La mayoría de los administradores de bases de datos están familiarizados con el concepto de cotejo en el sentido de "cotejo de la base de datos". Afortunadamente, esa es solo la clasificación predeterminada, que se usa si no hay otra información de clasificación, pero también existen otros lugares, donde se puede especificar la clasificación:

  • El comando SQL CREATE INDEX admite una especificación de intercalación. (Aunque los rumores dicen que Microsoft SQL Server no lo admite; ¿alguien lo sabe?)

  • La instrucción SQL SELECT también admite la intercalación, pero en este caso la especificación de intercalación funciona como una función, provocando un escaneo de índice en lugar de una búsqueda de índice, algo que podría ser inadmisible si queremos rendimiento. (Por otra parte, si eso es lo mejor que podemos tener, podría ser mejor que nada).

  • También escuché que en Microsoft SQL Server puede tener columnas calculadas no persistentes en las que puede especificar la intercalación y crear un índice filtrado, aunque nunca he oído hablar de esto antes, y si es solo un servidor Microsoft-SQL-Server característica, entonces prefiero abstenerme de usarlo, no importa cuán genial y bien pensado sea.

Entonces, a la luz de todo eso, ¿cómo estructuramos nuestra base de datos y cómo realizamos nuestras consultas, si el objetivo es una base de datos multilingüe actualizable y con capacidad de búsqueda?


Esta pregunta se inspiró en una discusión que tuvo lugar aquí: ¿cómo almacenará nvarchar (max) los datos en la base de datos? ¿Será rápido si algunos datos tienen menos de 4000 caracteres?

Mike Nakis
fuente
2
Si una característica de solo producto de Microsoft es realmente genial y bien pensada, debería tener buenas posibilidades de obtener soporte en productos similares por parte de otros proveedores a tiempo. Solo un pensamiento.

Respuestas:

8

Es posible almacenar cadenas con diferentes colaciones en la misma columna usando SQL_VARIANT :

CREATE TABLE dbo.Localized
(
    text_id     INTEGER NOT NULL,
    lang_id     INTEGER NOT NULL,
    text_body   SQL_VARIANT NOT NULL,

    CONSTRAINT [PK dbo.Localized text_id, lang_id]
        PRIMARY KEY CLUSTERED (text_id, lang_id),
)
GO
INSERT dbo.Localized
    (text_id, lang_id, text_body)
VALUES
    (1001, 2057, N'Database problems' COLLATE Latin1_General_CI_AS);
GO
INSERT dbo.Localized
    (text_id, lang_id, text_body)
VALUES
    (1001, 1025, N'قاعدة بيانات المشاكل' COLLATE Arabic_CI_AS)

Este diseño tiene varios inconvenientes (incluyendo estar limitado a 8000 bytes), no menos importante en el área de búsqueda: SQL_VARIANTno se puede indexar en texto completo, y algunas características de comparación de cadenas (por ejemplo LIKE) tampoco se pueden usar directamente. Por otro lado, es posible crear un índice regular SQL_VARIANTy realizar las comparaciones más básicas (p. Ej., <, =,>) De manera consciente de la clasificación:

CREATE UNIQUE INDEX uq1 ON dbo.Localized (text_body)
GO
-- One row
SELECT
    l.*
FROM dbo.Localized AS l 
WHERE
    l.text_body = CONVERT(SQL_VARIANT, N'Database problems' COLLATE Latin1_General_CI_AS)

-- No rows (and no collation error!)
SELECT
    l.*
FROM dbo.Localized AS l
WHERE
    l.text_body = CONVERT(SQL_VARIANT, N'Database problems' COLLATE Arabic_CI_AS)

-- One row, index seek, manual version of "LIKE 'D%'"
SELECT
    l.*
FROM dbo.Localized AS l 
WHERE
    l.text_body >= CONVERT(SQL_VARIANT, N'D' COLLATE Latin1_General_CI_AS)
    AND l.text_body < CONVERT(SQL_VARIANT, N'E' COLLATE Latin1_General_CI_AS)

También podemos escribir el tipo habitual de procedimientos:

CREATE PROCEDURE dbo.GetLocalizedString
    @text_id    INTEGER,
    @lang_id    INTEGER,
    @text_body  SQL_VARIANT OUTPUT
AS
BEGIN
    SELECT
        @text_body = l.text_body
    FROM dbo.Localized AS l
    WHERE
        l.text_id = @text_id
        AND l.lang_id = @lang_id
END
GO
DECLARE @text SQL_VARIANT

EXECUTE dbo.GetLocalizedString
    @text_id = 1001,
    @lang_id = 1025,
    @text_body = @text OUTPUT

SELECT @text

Por supuesto, la indexación de texto completo también es problemática en el diseño de "tabla única para todas las traducciones", ya que la indexación de texto completo (todo menos) requiere una configuración de identificación de idioma por columna . El diseño de múltiples tablas descrito por Joop Eggen podría estar indexado en texto completo (aunque naturalmente requeriría un índice por tabla).

La otra opción principal es tener una columna por configuración regional en la tabla base:

CREATE TABLE dbo.Example
(
    text_id     INTEGER NOT NULL,
    text_2057   NVARCHAR(MAX) COLLATE Latin1_General_CI_AS NULL,
    text_1025   NVARCHAR(MAX) COLLATE Arabic_CI_AS NULL,

    CONSTRAINT [PK dbo.Example text_id]
        PRIMARY KEY CLUSTERED (text_id)
)

Esta disposición tiene una cierta simplicidad y funciona bien con la indexación de texto completo, aunque requiere que se agregue una nueva columna con cada nuevo idioma, y ​​muchos desarrolladores consideran que este tipo de estructura es poco elegante e insatisfactorio para trabajar.

Cada una de las alternativas tiene ventajas y desventajas, y requerirá una dirección indirecta en un nivel u otro, por lo que puede depender de dónde los desarrolladores interesados ​​se sientan más felices al ubicar esa dirección indirecta. Me imagino que la mayoría de las personas preferirán el diseño de tablas múltiples para la mayoría de los propósitos.

Paul White 9
fuente
Probablemente use una tabla separada en lugar de columnas separadas para un mejor diseño físico: fue mi respuesta diciendo que eso inspiró esta pregunta dba.stackexchange.com/a/9954/630
gbn
5

Evidentemente, desea una tabla por idioma: xxx_en , xxx_fr , xxx_eo . Eso sería más óptimo y permitiría intercalaciones dependientes del idioma. Incluso sería imaginable que tenga una base de datos por idioma [en] [xxx] , [fr] [xxx] , [eo] [xxx] .

Los detalles técnicos son entonces de importancia secundaria (uno puede o no puede optimizar más).

Las teclas de texto reales van en una tabla xxx .

Joop Eggen
fuente
2
El problema con esto es que es muy poco relacional.
Mike Nakis
Sí, mi experiencia es que la búsqueda de texto, ya sea que db sea compatible o no, es difícil de integrar relacionalmente. Gracias por dar un punto de todos modos.