Datenbank hinter einer mehrsprachigen Benutzeroberfläche

8

Bei dieser Frage handelt es sich um ein etwas komplizierteres Thema als das, das bereits in diesen alten Fragen behandelt wurde, die alle Duplikate voneinander sind:

Vorschlag für eine Datenbankstruktur für mehrsprachig (2011 Jun)

Was ist die beste Datenbankstruktur, um mehrsprachige Daten zu speichern? (2010 Feb)

Was sind Best Practices für das mehrsprachige Datenbankdesign? (2009 Mai)

Schema für eine mehrsprachige Datenbank (2008 Nov)


Das beliebteste Datenbankschema zum Sichern mehrsprachiger Benutzeroberflächen scheint darin zu bestehen, alle übersetzten Texte aller Sprachen in einer Tabelle mit drei Spalten zu haben: der Text-ID, dem Sprachcode und dem Text selbst. Die Text-ID und der Sprachcode bilden zusammen den Primärschlüssel.

Das ist alles sehr gut, aber jetzt betrachten Sie eine Komplikation: Nehmen wir an, dass die Texte durchsuchbar sein müssen. Angenommen, dies ist ein mehrsprachiger E-Shop. Dies bedeutet, dass der Ladenbesitzer für jede in die Datenbank eingegebene Produktkategorie den Namen der Produktkategorie in jeder der N unterstützten Sprachen eingibt und der Käufer dann nach Namen nach der Produktkategorie suchen kann. in ihrer eigenen Sprache .

Es gibt ein Problem: Sortierung .

Verschiedene Sprachen haben unterschiedliche Kollatierungssequenzen, und die Kollatierungssequenz, die für eine Sprache funktioniert, funktioniert nicht für eine andere. Wenn sich also alle Texte aller Sprachen in einer einzigen Spalte befinden, welche Sortierreihenfolge haben sie? Wie werden wir die Datenbank abfragen, um die Text-ID eines bestimmten Textes zu finden? Während bei der Suche nach einem Webprodukt Genauigkeit und Leistung möglicherweise nicht besonders wichtig sind, nehmen wir für die Zwecke dieser Diskussion an, dass sie wirklich wichtig sind.

Die meisten Datenbankadministratoren kennen das Konzept der Kollatierung im Sinne von "Kollatierung der Datenbank". Glücklicherweise ist dies nur die Standardkollatierung, die verwendet wird, wenn keine anderen Kollatierungsinformationen vorhanden sind, aber es gibt auch andere Stellen, an denen die Kollatierung angegeben werden kann:

  • Der Befehl SQL CREATE INDEX unterstützt eine Sortierspezifikation. (Obwohl Gerüchte besagen, dass Microsoft SQL Server es nicht unterstützt; weiß jemand davon?)

  • Die SQL SELECT-Anweisung unterstützt auch die Kollatierung. In diesem Fall funktioniert die Kollatierungsspezifikation jedoch als Funktion und verursacht einen Index-Scan anstelle einer Index-Suche. Dies ist möglicherweise unzulässig, wenn wir Leistung wünschen. (Andererseits, wenn das das Beste ist, was wir haben können, ist es vielleicht besser als nichts.)

  • Ich habe auch gehört, dass Sie unter Microsoft SQL Server nicht persistente, berechnete Spalten haben können, in denen Sie die Sortierung angeben und einen gefilterten Index erstellen können, obwohl ich noch nie davon gehört habe und wenn es sich nur um Microsoft-SQL Server handelt Feature, dann würde ich es lieber nicht benutzen, egal wie cool und gut durchdacht es ist.

Wie strukturieren wir angesichts all dessen unsere Datenbank und wie führen wir unsere Abfragen durch, wenn das Ziel eine aktualisierbare und durchsuchbare mehrsprachige Datenbank ist?


Diese Frage wurde von einer Diskussion inspiriert, die hier stattfand: Wie speichert nvarchar (max) Daten in der Datenbank? Wird es schnell gehen, wenn einige Daten weniger als 4000 Zeichen enthalten?

Mike Nakis
quelle
2
Wenn eine reine Microsoft-Produktfunktion wirklich cool und gut durchdacht ist, sollte sie faire Chancen haben, rechtzeitig Unterstützung für ähnliche Produkte von anderen Anbietern zu erhalten. Nur ein Gedanke.

Antworten:

8

Es ist möglich , zu speichern Strings mit unterschiedlichen Sortierungen in der gleichen Spalte 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)

Dieses Design weist mehrere Nachteile auf (einschließlich der Beschränkung auf 8000 Byte), nicht zuletzt im Suchbereich: Es SQL_VARIANTkann nicht im Volltext indiziert werden, und einige Zeichenfolgenvergleichsfunktionen (z. B. LIKE) können auch nicht direkt verwendet werden. Auf der anderen Seite, es ist möglich , einen regulären Index auf erstellen SQL_VARIANTund durchzuführen , die grundlegen Vergleiche (zB <, =,>) in einem Sortierungsbewussten Art und Weise:

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)

Wir können auch die üblichen Verfahren schreiben:

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

Natürlich ist die Volltextindizierung auch im Entwurf "Einzelne Tabelle für alle Übersetzungen" problematisch, da für die Volltextindizierung (alle außer) eine Sprach-ID-Einstellung pro Spalte erforderlich ist . Das von Joop Eggen beschriebene Design mit mehreren Tabellen könnte im Volltext indiziert werden (obwohl natürlich ein Index pro Tabelle erforderlich wäre).

Die andere Hauptoption besteht darin, eine Spalte pro Gebietsschema in der Basistabelle zu haben:

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)
)

Diese Anordnung hat eine gewisse Einfachheit und funktioniert gut mit der Volltextindizierung, obwohl für jede neue Sprache eine neue Spalte hinzugefügt werden muss, und viele Entwickler finden diese Art von Struktur unelegant und unbefriedigend.

Jede der Alternativen hat Vor- und Nachteile und erfordert auf der einen oder anderen Ebene eine Indirektion. Dies hängt möglicherweise davon ab, wo sich die betroffenen Entwickler am glücklichsten fühlen, wenn sie diese Indirektion finden. Ich kann mir vorstellen, dass die meisten Leute das Design mit mehreren Tischen für die meisten Zwecke bevorzugen werden.

Paul White 9
quelle
Ich verwende wahrscheinlich eine separate Tabelle anstelle einer separaten Spalte für ein besseres physisches Layout: Es war meine Antwort, die diese Frage inspirierte. Dba.stackexchange.com/a/9954/630
gbn
5

Offensichtlich möchten Sie eine Tabelle pro Sprache: xxx_en , xxx_fr , xxx_eo . Das wäre optimaler und würde sprachabhängige Kollatierungen ermöglichen. Es ist sogar vorstellbar, dass Sie eine Datenbank pro Sprache [en] [xxx] , [fr] [xxx] , [eo] [xxx] haben .

Technische Details sind dann von untergeordneter Bedeutung (entweder kann man mehr optimieren oder nicht).

Die eigentlichen Textschlüssel befinden sich in einer Tabelle xxx .

Joop Eggen
quelle
2
Das Problem dabei ist, dass es sehr relational ist.
Mike Nakis
Ja, meine Erfahrung ist, dass die Textsuche, ob db-unterstützt oder selbst durchgeführt, schwer relational zu integrieren ist. Danke, dass du trotzdem einen Punkt gegeben hast.