Banco de dados por trás de uma interface de usuário multilíngue


8

Esta pergunta é sobre um problema um pouco mais complicado do que o que já foi abordado nessas perguntas antigas, todas duplicadas uma da outra:

Sugestão para estrutura de banco de dados para vários idiomas (junho de 2011)

Qual é a melhor estrutura de banco de dados para manter dados multilíngues? (Fevereiro de 2010)

Quais são as práticas recomendadas para o design de banco de dados em vários idiomas? (Maio de 2009)

Esquema para um banco de dados multilíngue (novembro de 2008)


O esquema de banco de dados mais popular para fazer backup de interfaces de usuário multilíngues parece ter todos os textos traduzidos de todos os idiomas em uma tabela com 3 colunas: a identificação do texto, o código do idioma e o próprio texto. O ID do texto e o código do idioma juntos formam a chave primária.

Tudo bem, mas agora considere uma complicação: suponha que os textos precisem ser pesquisáveis. Suponha, por exemplo, que seja uma loja virtual em vários idiomas. Isso significa que, para cada categoria de produto inserida no banco de dados, o proprietário da loja inserirá o nome da categoria de produto em todos os N idiomas suportados e, em seguida, o comprador poderá procurar a categoria de produto por nome, na sua própria língua .

Há um problema: agrupamento .

Idiomas diferentes têm sequências de intercalação diferentes, e a sequência de intercalação que funciona para um idioma não funciona para outro. Portanto, se todos os textos de todos os idiomas estiverem em uma única coluna, que sequência de agrupamento eles terão? Como vamos consultar o banco de dados para encontrar o ID do texto de um texto específico? Enquanto em uma pesquisa na Web, a precisão e o desempenho podem não ser muito importantes, para os propósitos desta discussão, suponhamos que eles realmente sejam importantes.

A maioria dos administradores de banco de dados está familiarizada com o conceito de agrupamento no sentido de "agrupamento do banco de dados". Felizmente, esse é apenas o agrupamento padrão, que é usado se nenhuma outra informação de agrupamento estiver presente, mas também existem outros lugares onde o agrupamento pode ser especificado:

  • O comando SQL CREATE INDEX suporta uma especificação de agrupamento. (Embora existam rumores de que o Microsoft SQL Server não o suporta; alguém sabe disso?)

  • A instrução SQL SELECT também suporta agrupamento, mas neste caso a especificação de agrupamento funciona como uma função, causando uma varredura de índice em vez de uma pesquisa de índice, algo que pode ser inadmissível se queremos desempenho. (Então, novamente, se é o melhor que podemos ter, pode ser melhor que nada.)

  • Também ouvi dizer que no Microsoft SQL Server você pode ter colunas computadas não persistentes nas quais é possível especificar agrupamento e criar um índice filtrado, embora eu nunca tenha ouvido falar disso antes, e se for apenas Microsoft-SQL-Server recurso, então eu prefiro não usá-lo, não importa o quão legal e bem pensado seja.

Portanto, à luz de tudo isso, como estruturamos nosso banco de dados e como executamos nossas consultas, se o objetivo é um banco de dados multilíngue atualizável e pesquisável?


Esta questão foi inspirada por uma discussão que ocorreu aqui: como o nvarchar (max) armazenará dados no banco de dados será rápido se alguns dados tiverem menos de 4000 caracteres?


2
Se um recurso exclusivo para produtos da Microsoft for realmente interessante e bem pensado, deverá ter chances justas de obter suporte em produtos similares por outros fornecedores a tempo. Apenas um pensamento.

Respostas:


8

É possível armazenar seqüências de caracteres com agrupamentos diferentes na mesma coluna 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)

Esse design tem várias desvantagens (incluindo estar limitado a 8000 bytes), principalmente na área de pesquisa: SQL_VARIANTnão pode ser indexado em texto completo e alguns recursos de comparação de cadeias (por exemplo LIKE) também não podem ser usados ​​diretamente. Por outro lado, é possível criar um índice regular SQL_VARIANTe realizar as comparações mais básicas (por exemplo, <, =,>) de uma forma que reconhece agrupamentos:

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)

Também podemos escrever o tipo usual de procedimentos:

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

Obviamente, a indexação de texto completo também é problemática no design da "tabela única para todas as traduções", pois a indexação de texto completo (com exceção de todas) exige uma configuração de ID de idioma por coluna . O design de várias tabelas descrito por Joop Eggen pode ser indexado em texto completo (embora naturalmente exija um índice por tabela).

A outra opção principal é ter uma coluna por local na tabela 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)
)

Esse arranjo tem uma certa simplicidade e funciona bem com a indexação de texto completo, embora exija que uma nova coluna seja adicionada a cada novo idioma, e muitos desenvolvedores acham esse tipo de estrutura deselegante e insatisfatório para trabalhar.

Cada uma das alternativas tem vantagens e desvantagens e exigirá indireção em algum nível ou outro, portanto, pode depender de onde os desenvolvedores envolvidos se sintam mais felizes ao localizar essa indireção. Eu imagino que a maioria das pessoas prefira o design de várias tabelas para a maioria dos propósitos.


Provavelmente, eu uso uma tabela separada em vez de colunas separadas para um melhor layout físico: foi minha resposta dizendo que inspirou essa pergunta dba.stackexchange.com/a/9954/630
gbn

5

Evidentemente, você deseja uma tabela por idioma: xxx_en , xxx_fr , xxx_eo . Isso seria mais ideal e permitiria agrupamentos dependentes do idioma. Seria até imaginável que você tenha um banco de dados por idioma [en] [xxx] , [fr] [xxx] , [e] [xxx] .

Os detalhes técnicos são então de importância secundária (é possível ou não otimizar mais).

As teclas de texto reais vão para uma tabela xxx .


2
O problema com isso é que é muito não relacional.
quer

Sim, minha experiência é que é difícil integrar relacionalmente a pesquisa de texto, seja com suporte a banco de dados ou com execução automática. Obrigado por dar um ponto de qualquer maneira.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.