Base de données derrière une interface utilisateur multilingue


8

Cette question concerne une question un peu plus compliquée que celle qui a déjà été abordée dans ces anciennes questions, qui sont toutes des doublons les unes des autres:

Suggestion pour la structure de la base de données pour le multilingue (juin 2011)

Quelle est la meilleure structure de base de données pour conserver des données multilingues? (Fév 2010)

Quelles sont les meilleures pratiques pour la conception de bases de données multilingues? (Mai 2009)

Schéma d'une base de données multilingue (2008 nov)


Le schéma de base de données le plus populaire pour sauvegarder des interfaces utilisateur multilingues semble avoir tous les textes traduits de toutes les langues dans un tableau à 3 colonnes: l'identifiant du texte, le code de la langue et le texte lui-même. L'identifiant du texte et le code de langue constituent ensemble la clé primaire.

C'est très bien, mais considérons maintenant une complication: supposons que les textes doivent être consultables. Supposons, par exemple, qu'il s'agit d'une boutique en ligne multilingue. Cela signifie que pour chaque catégorie de produits entrée dans la base de données, le propriétaire de la boutique entrera le nom de la catégorie de produits dans chacune des N langues prises en charge, puis le client pourra rechercher la catégorie de produits par son nom, dans leur propre langue .

Il y a un problème: le classement .

Différentes langues ont des séquences de classement différentes et la séquence de classement qui fonctionne pour une langue ne fonctionne pas pour une autre. Donc, si tous les textes de toutes les langues sont sur une seule colonne, quelle séquence de classement vont-ils avoir? Comment allons-nous interroger la base de données pour trouver l'ID de texte d'un texte spécifique? Alors que dans une recherche de produit Web, la précision et les performances peuvent ne pas être extrêmement importantes, aux fins de cette discussion, supposons qu'elles importent vraiment.

La plupart des administrateurs de base de données connaissent le concept de classement dans le sens de "classement de la base de données". Heureusement, ce n'est que le classement par défaut, qui est utilisé si aucune autre information de classement n'est présente, mais il existe également d'autres endroits, où le classement peut être spécifié:

  • La commande SQL CREATE INDEX prend en charge une spécification de classement. (Bien que des rumeurs disent que Microsoft SQL Server ne le prend pas en charge; quelqu'un le sait-il?)

  • L'instruction SQL SELECT prend également en charge le classement, mais dans ce cas, la spécification de classement fonctionne comme une fonction, provoquant une analyse d'index au lieu d'une recherche d'index, ce qui pourrait être inadmissible si nous voulons des performances. (Là encore, si c'est le meilleur que nous puissions avoir, cela pourrait être mieux que rien.)

  • J'entends également que sur Microsoft SQL Server, vous pouvez avoir des colonnes calculées non persistantes sur lesquelles vous pouvez spécifier le classement et créer un index filtré, bien que je n'en ai jamais entendu parler auparavant, et s'il s'agit uniquement de Microsoft-SQL-Server fonctionnalité, alors je préfère ne pas l'utiliser, peu importe à quel point c'est cool et bien pensé.

Donc, à la lumière de tout cela, comment structurer notre base de données et comment effectuer nos requêtes, si l'objectif est une base de données multilingue pouvant être mise à jour et consultable?


Cette question a été inspirée par une discussion qui a eu lieu ici: comment nvarchar (max) stockera-t-il les données dans la base de données sera-t-il rapide si certaines données contiennent moins de 4000 caractères?


2
Si une fonctionnalité réservée aux produits Microsoft est vraiment géniale et bien pensée, elle devrait avoir de bonnes chances d'obtenir le soutien de produits similaires par d'autres fournisseurs à temps. Juste une pensée.

Réponses:


8

Il est possible de stocker des chaînes avec des classements différents dans la même colonne en utilisant 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)

Cette conception présente plusieurs inconvénients (y compris être limité à 8 000 octets), notamment dans la zone de recherche: SQL_VARIANTne peut pas être indexé en texte intégral et certaines fonctionnalités de comparaison de chaînes (par exemple LIKE) ne peuvent pas être utilisées directement non plus. D'un autre côté, il est possible de créer un index régulier sur SQL_VARIANTet d'effectuer les comparaisons les plus élémentaires (par exemple <, =,>) d'une manière compatible avec le classement:

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)

Nous pouvons également écrire le type habituel de procédures:

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

Bien sûr, l'indexation de texte intégral est également problématique dans la conception "table unique pour toutes les traductions", car l'indexation de texte intégral (tout sauf) nécessite un paramètre d'ID de langue par colonne . La conception de plusieurs tables décrite par Joop Eggen pourrait être indexée en texte intégral (bien qu'elle nécessiterait naturellement un index par table).

L'autre option principale est d'avoir une colonne par locale dans la table de 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)
)

Cet arrangement a une certaine simplicité et fonctionne bien avec l'indexation de texte intégral, bien qu'il nécessite l'ajout d'une nouvelle colonne avec chaque nouvelle langue, et de nombreux développeurs trouvent ce type de structure inélégant et insatisfaisant à utiliser.

Chacune des alternatives présente des avantages et des inconvénients, et nécessitera une indirection à un certain niveau ou à un autre, donc cela pourrait dépendre de l'endroit où les développeurs concernés se sentiront le plus heureux de localiser cette indirection. J'imagine que la plupart des gens préféreront la conception à plusieurs tables pour la plupart des usages.


J'utilise probablement une table séparée plutôt que d' une mise en page des colonnes distinctes pour une meilleure physique: il a été ma réponse en disant que cette question qui a inspiré dba.stackexchange.com/a/9954/630
GBN

5

Évidemment, vous voulez une table par langue: xxx_en , xxx_fr , xxx_eo . Ce serait plus optimal et permettrait des classements dépendants de la langue. Il serait même imaginable que vous ayez une base de données par langue [en] [xxx] , [fr] [xxx] , [eo] [xxx] .

Les détails techniques sont alors d'une importance secondaire (on peut ou ne peut pas optimiser davantage).

Les touches de texte réelles vont sur une table xxx .


2
Le problème avec cela est qu'il est très non relationnel.
Mike Nakis

Oui, mon expérience est que la recherche de texte, qu'elle soit prise en charge par la base de données ou auto-effectuée, est difficile à intégrer relationnellement. Merci d'avoir donné un point de toute façon.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.