Database dietro un'interfaccia utente multilingue


8

Questa domanda riguarda un problema un po 'più complicato di quello che è già stato affrontato in queste vecchie domande, tutte duplicate l'una dell'altra:

Suggerimento per la struttura del database per multilingua (giugno 2011)

Qual è la migliore struttura di database per conservare i dati multilingue? (2010 febbraio)

Quali sono le migliori pratiche per la progettazione di database multilingue? (Maggio 2009)

Schema per un database multilingue (novembre 2008)


Lo schema di database più popolare per il supporto di interfacce utente multilingue sembra avere tutti i testi tradotti di tutte le lingue in una tabella con 3 colonne: l'id del testo, il codice della lingua e il testo stesso. L'ID testo e il codice lingua insieme costituiscono la chiave primaria.

Va tutto bene, ma ora considera una complicazione: supponi che i testi debbano essere ricercabili. Supponiamo, ad esempio, che si tratti di un negozio elettronico multilingue. Ciò significa che per ogni categoria di prodotto inserita nel database, il proprietario del negozio inserirà il nome della categoria di prodotto in ciascuna delle N lingue supportate e quindi l'acquirente sarà in grado di cercare la categoria di prodotto per nome, nella loro lingua .

C'è un problema: regole di confronto .

Lingue diverse hanno sequenze di confronto diverse e la sequenza di confronto che funziona per una lingua non funziona per un'altra. Quindi, se tutti i testi di tutte le lingue sono su una singola colonna, quale sequenza di regole avranno? Come interrogheremo il database per trovare l'id di testo di un testo specifico? Mentre in un prodotto Web la precisione e le prestazioni della ricerca potrebbero non essere terribilmente importanti, ai fini di questa discussione supponiamo che contino davvero.

La maggior parte degli amministratori di database ha familiarità con il concetto di regole di confronto nel senso di "regole di confronto del database". Fortunatamente, questa è solo la collazione predefinita, che viene utilizzata se non sono presenti altre informazioni sulla collazione, ma esistono anche altri luoghi in cui è possibile specificare la collazione:

  • Il comando SQL CREATE INDEX supporta una specifica di confronto. (Anche se si dice che Microsoft SQL Server non lo supporti; qualcuno lo sa?)

  • L'istruzione SQL SELECT supporta anche le regole di confronto, ma in questo caso la specifica delle regole di confronto funziona come una funzione, causando una scansione dell'indice anziché una ricerca dell'indice, qualcosa che potrebbe essere inammissibile se vogliamo prestazioni. (Ancora una volta, se questo è il meglio che possiamo avere, potrebbe essere meglio di niente.)

  • Ho anche sentito che su Microsoft SQL Server puoi avere colonne calcolate e non persistenti su cui puoi specificare regole di confronto e creare un indice filtrato, anche se non ne ho mai sentito parlare prima e se si tratta di un solo Microsoft-SQL-Server caratteristica, quindi preferirei astenermi dall'usarlo, non importa quanto sia bello e ben pensato.

Quindi, alla luce di tutto ciò, come strutturiamo il nostro database e come eseguiamo le nostre query, se l'obiettivo è un database multilingue aggiornabile e ricercabile?


Questa domanda è stata ispirata da una discussione che ha avuto luogo qui: in che modo nvarchar (max) memorizzerà i dati nel database sarà veloce se alcuni dati sono inferiori a 4000 caratteri?


2
Se una funzionalità del solo prodotto Microsoft è davvero interessante e ben studiata, dovrebbe avere le giuste possibilità di ottenere il supporto in prodotti simili da altri fornitori in tempo. Solo un pensiero.

Risposte:


8

È possibile memorizzare stringhe con regole di confronto diverse nella stessa colonna utilizzando 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)

Questo design presenta diversi inconvenienti (incluso il fatto di essere limitato a 8000 byte), non da ultimo nell'area di ricerca: SQL_VARIANTnon può essere indicizzato full-text e alcune funzioni di confronto delle stringhe (ad es. LIKE) Non possono essere utilizzate direttamente. D'altra parte, è possibile creare un indice regolare su SQL_VARIANTed eseguire i confronti più basilari (ad es. <, =,>) In modo sensibile alle regole di confronto:

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)

Possiamo anche scrivere il solito tipo di procedure:

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

Naturalmente, l'indicizzazione full-text è problematica anche nella progettazione della "tabella singola per tutte le traduzioni", poiché l'indicizzazione full-text (tutti tranne) richiede un'impostazione id lingua per colonna . Il design di più tabelle descritto da Joop Eggen potrebbe essere indicizzato full-text (sebbene richiederebbe naturalmente un indice per tabella).

L'altra opzione principale è avere una colonna per locale nella tabella di 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)
)

Questa disposizione ha una certa semplicità e funziona bene con l'indicizzazione full-text, anche se richiede l'aggiunta di una nuova colonna con ogni nuova lingua e molti sviluppatori trovano questo tipo di struttura inelegante e insoddisfacente con cui lavorare.

Ognuna delle alternative presenta vantaggi e svantaggi e richiederà l'indirizzamento indiretto a un livello o ad un altro, quindi potrebbe dipendere da dove gli sviluppatori interessati si sentono più felici nel trovare quell'indirizzamento. Immagino che la maggior parte delle persone preferirà il design a più tavoli per la maggior parte degli scopi.


Probabilmente uso una tabella separata anziché una colonna separata per un migliore layout fisico: è stata la mia risposta a dire che ciò ha ispirato questa domanda dba.stackexchange.com/a/9954/630
gbn

5

Evidentemente vuoi una tabella per lingua: xxx_en , xxx_fr , xxx_eo . Sarebbe più ottimale e consentirebbe regole di confronto dipendenti dalla lingua. Sarebbe persino immaginabile che tu abbia un database per lingua [en] [xxx] , [fr] [xxx] , [eo] [xxx] .

I dettagli tecnici sono quindi di importanza secondaria (si può o non si può ottimizzare di più).

Le chiavi di testo effettive vanno su una tabella xxx .


2
Il problema è che è molto poco relazionale.
Mike Nakis,

Sì, la mia esperienza è che la ricerca di testo, supportata da db o fatta da sé, è difficile da integrare relazionalmente. Grazie per aver dato un punto comunque.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.