多言語データベースのスキーマ


235

多言語ソフトウェアを開発しています。アプリケーションコードに関する限り、ローカライズ性は問題ではありません。言語固有のリソースを使用し、それらとうまく機能するあらゆる種類のツールを使用できます。

しかし、多言語データベーススキーマを定義するための最良のアプローチは何ですか?たくさんのテーブル(100以上)があり、各テーブルにローカライズ可能な複数の列があるとします(nvarchar列のほとんどはローカライズ可能でなければなりません)。たとえば、テーブルの1つに製品情報が含まれている場合があります。

CREATE TABLE T_PRODUCT (
  NAME        NVARCHAR(50),
  DESCRIPTION NTEXT,
  PRICE       NUMBER(18, 2)
)

NAME列とDESCRIPTION列で多言語テキストをサポートするための3つのアプローチを考えることができます。

  1. 言語ごとに個別の列

    システムに新しい言語を追加するときは、次のように、翻訳されたテキストを格納する追加の列を作成する必要があります。

    CREATE TABLE T_PRODUCT (
      NAME_EN        NVARCHAR(50),
      NAME_DE        NVARCHAR(50),
      NAME_SP        NVARCHAR(50),
      DESCRIPTION_EN NTEXT,
      DESCRIPTION_DE NTEXT,
      DESCRIPTION_SP NTEXT,
      PRICE          NUMBER(18,2)
    )
  2. 各言語の列を含む翻訳テーブル

    翻訳されたテキストを保存する代わりに、翻訳テーブルへの外部キーのみが保存されます。translationsテーブルには、各言語の列が含まれています。

    CREATE TABLE T_PRODUCT (
      NAME_FK        int,
      DESCRIPTION_FK int,
      PRICE          NUMBER(18, 2)
    )
    
    CREATE TABLE T_TRANSLATION (
      TRANSLATION_ID,
      TEXT_EN NTEXT,
      TEXT_DE NTEXT,
      TEXT_SP NTEXT
    )
  3. 各言語の行を含む翻訳テーブル

    翻訳されたテキストを保存する代わりに、翻訳テーブルへの外部キーのみが保存されます。translationsテーブルにはキーのみが含まれ、別のテーブルには言語への各翻訳の行が含まれます。

    CREATE TABLE T_PRODUCT (
      NAME_FK        int,
      DESCRIPTION_FK int,
      PRICE          NUMBER(18, 2)
    )
    
    CREATE TABLE T_TRANSLATION (
      TRANSLATION_ID
    )
    
    CREATE TABLE T_TRANSLATION_ENTRY (
      TRANSLATION_FK,
      LANGUAGE_FK,
      TRANSLATED_TEXT NTEXT
    )
    
    CREATE TABLE T_TRANSLATION_LANGUAGE (
      LANGUAGE_ID,
      LANGUAGE_CODE CHAR(2)
    )

それぞれのソリューションには長所と短所があり、これらのアプローチでの経験、推奨事項、および多言語データベーススキーマの設計方法について教えてください。



3
このリンクを確認できます:gsdesign.ro/blog/multilanguage-database-design-approachコメントを読むと非常に役立ちます
Fareed Alnamrouti

3
LANGUAGE_CODE自然な鍵です、避けてくださいLANGUAGE_ID
ジバンコア2014年

1
私はすでに2.と3.を見たり使用したりしましたが、お勧めしません。あなたは簡単に孤立した行になってしまいます。@SunWiKungのデザインはIMOの方が良く見えます。
Guillaume86

4
私はSunWuKungsのデザインを好みます。これは、偶然にも私たちが実装したものです。ただし、照合順序を考慮する必要があります。少なくともSQL Serverでは、各列に照合プロパティがあり、大文字と小文字の区別、アクセント付き文字の等価(または非等価)、その他の言語固有の考慮事項などを決定します。言語固有の照合順序を使用するかどうかは、全体的なアプリケーション設計に依存しますが、それが間違っていると、後で変更するのが難しくなります。言語固有の照合が必要な場合は、言語ごとの行ではなく、言語ごとの列が必要です。
Elroy Flynn、

回答:


113

翻訳可能な各テーブルに関連する変換テーブルがあることについてどう思いますか?

CREATE TABLE T_PRODUCT(pr_id int、PRICE NUMBER(18、2))

CREATE TABLE T_PRODUCT_tr(pr_id INT FK、languagecode varchar、pr_name text、pr_descr text)

このように、翻訳可能な列が複数ある場合、それを取得するには単一の結合のみが必要になります。translationidを自動生成しないため、関連する翻訳とともにアイテムをインポートする方が簡単な場合があります。

これのマイナス面は、複雑な言語のフォールバックメカニズムがある場合、各変換テーブルにそれを実装する必要があるかもしれないということです-ストアドプロシージャに依存している場合。アプリからそれを行う場合、これはおそらく問題にはなりません。

あなたの考えを教えてください-私も次のアプリケーションのためにこれについて決定しようとしています。これまでは、3番目のタイプを使用しました。


2
このオプションは私のオプションnr 1に似ていますが、より優れています。維持するのはまだ難しく、新しい言語用の新しいテーブルを作成する必要があるため、実装するのをためらいます。
qbeuek 2008年

28
新しい言語用の新しいテーブルは必要ありません。新しい言語で適切な_trテーブルに新しい行を追加するだけで、新しい翻訳可能なテーブルを作成する場合にのみ、新しい_trテーブルを作成する必要があります

3
これは良い方法だと思います。他の方法では、大量の左結合が必要であり、複数のテーブルを結合する場合、それぞれに3レベルの深さのような変換があり、それぞれに3つのフィールドがあり、変換のためにのみ3 * 3 9左結合が必要です。その他の場合は3。制約などを追加する方が簡単で、検索のほうが適切です。
GorillaApe、2012年

1
ときT_PRODUCT100万行を持っている、T_PRODUCT_trそれは多くのSQLの効率を下げる2 million.Wouldを持っているでしょうか?
ミスリル

1
@ミスリルどちらにしても、200万行あります。少なくとも、このメソッドでは結合は必要ありません。
David D

56

これは興味深い問題なので、ネクロマンスにしましょう。

方法1の問題から始めましょう。
問題:速度を節約するために非正規化しています。
SQL(hstoreを使用したPostGreSQLを除く)では、パラメーター言語を渡すことはできません。

SELECT ['DESCRIPTION_' + @in_language]  FROM T_Products

だからあなたはこれをしなければなりません:

SELECT 
    Product_UID 
    ,
    CASE @in_language 
        WHEN 'DE' THEN DESCRIPTION_DE 
        WHEN 'SP' THEN DESCRIPTION_SP 
        ELSE DESCRIPTION_EN 
    END AS Text 
FROM T_Products 

つまり、新しい言語を追加する場合は、すべてのクエリを変更する必要があります。これは当然「動的SQL」の使用につながるため、すべてのクエリを変更する必要はありません。

これは通常、このような結果になります(そして、ビューやテーブル値関数で使用することはできません。これは、実際にレポート日付をフィルターする必要がある場合に問題になります)。

CREATE PROCEDURE [dbo].[sp_RPT_DATA_BadExample]
     @in_mandant varchar(3) 
    ,@in_language varchar(2) 
    ,@in_building varchar(36) 
    ,@in_wing varchar(36) 
    ,@in_reportingdate varchar(50) 
AS
BEGIN
    DECLARE @sql varchar(MAX), @reportingdate datetime

    -- Abrunden des Eingabedatums auf 00:00:00 Uhr
    SET @reportingdate = CONVERT( datetime, @in_reportingdate) 
    SET @reportingdate = CAST(FLOOR(CAST(@reportingdate AS float)) AS datetime)
    SET @in_reportingdate = CONVERT(varchar(50), @reportingdate) 

    SET NOCOUNT ON;


    SET @sql='SELECT 
         Building_Nr AS RPT_Building_Number 
        ,Building_Name AS RPT_Building_Name 
        ,FloorType_Lang_' + @in_language + ' AS RPT_FloorType 
        ,Wing_No AS RPT_Wing_Number 
        ,Wing_Name AS RPT_Wing_Name 
        ,Room_No AS RPT_Room_Number 
        ,Room_Name AS RPT_Room_Name 
    FROM V_Whatever 
    WHERE SO_MDT_ID = ''' + @in_mandant + ''' 

    AND 
    ( 
        ''' + @in_reportingdate + ''' BETWEEN CAST(FLOOR(CAST(Room_DateFrom AS float)) AS datetime) AND Room_DateTo 
        OR Room_DateFrom IS NULL 
        OR Room_DateTo IS NULL 
    ) 
    '

    IF @in_building    <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Building_UID  = ''' + @in_building + ''') '
    IF @in_wing    <> '00000000-0000-0000-0000-000000000000' SET @sql=@sql + 'AND (Wing_UID  = ''' + @in_wing + ''') '

    EXECUTE (@sql) 

END


GO

これの問題は
a)日付の書式設定は非常に言語固有であるため、ISO形式で入力しないと問題が発生します(平均的な庭園の品種のプログラマーは通常はそうしませんが、明示的に指示された場合でも、地獄はあなたのためにしないとユーザーに確認してください)。
および
b)の最も重要な、あなたは構文チェックのいずれかの種類を失います。場合は<insert name of your "favourite" person here>変更が突然翼の変更の要件、およびAA新しいテーブルが作成されるため、スキーマは、古いものが残ったが、参照フィールドの名前が変更、警告のいずれかの種類を得ることはありません。レポートはさえ機能しは、wingパラメーター(==> guid.empty)を選択せず​​に実行した場合します。しかし突然、実際のユーザーが実際に翼を選択すると==>ブームこの方法は、あらゆる種類のテストを完全に壊します。


方法2:
一言で言えば、「素晴らしい」アイデア(警告-皮肉)、方法3の欠点(多くのエントリがある場合は速度が遅い)と方法1
のかなり恐ろしい欠点を組み合わせてみましょう。 この方法の唯一の利点は、すべての変換が1つのテーブルにあるため、メンテナンスが簡単になります。ただし、メソッド1と動的SQLストアドプロシージャ、および翻訳を含む(場合によっては一時的な)テーブルとターゲットテーブルの名前を使用して同じことを実現できます(すべてのテキストフィールドに同じ)。


方法3:
すべての翻訳用の1つのテーブル:短所:翻訳するn個のフィールドの製品テーブルにn個の外部キーを格納する必要があります。したがって、n個のフィールドに対してn個の結合を行う必要があります。変換テーブルがグローバルの場合、エントリが多くなり、結合が遅くなります。また、nフィールドのT_TRANSLATIONテーブルを常にn回結合する必要があります。これはかなりのオーバーヘッドです。では、顧客ごとのカスタム翻訳に対応する必要がある場合はどうしますか?追加のテーブルに別の2x n結合を追加する必要があります。結合する必要がある場合、たとえば、10個のテーブルに2x2xn = 4n個の追加結合を追加すると、なんてめちゃくちゃなことになります。また、この設計により、2つのテーブルで同じ変換を使用できるようになります。あるテーブルでアイテム名を変更した場合、本当に別のテーブルのエントリも毎回変更しますか?

さらに、製品テーブルに外部キーが含まれているため、テーブルを削除して再挿入することはできません。もちろん、FKの設定を省略して<insert name of your "favourite" person here>、テーブルを削除してから再挿入できます。newid()含むすべてのエントリまたは、挿入でIDを指定するが、identity-insert OFFを使用する)。これにより、すぐにデータガベージ(およびnull参照例外)が発生します。


方法4(リストされていません):すべての言語をデータベースのXMLフィールドに格納します。例えば

-- CREATE TABLE MyTable(myfilename nvarchar(100) NULL, filemeta xml NULL )


;WITH CTE AS 
(
      -- INSERT INTO MyTable(myfilename, filemeta) 
      SELECT 
             'test.mp3' AS myfilename 
            --,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body>Hello</body>', 2) 
            --,CONVERT(XML, N'<?xml version="1.0" encoding="utf-16" standalone="yes"?><body><de>Hello</de></body>', 2) 
            ,CONVERT(XML
            , N'<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<lang>
      <de>Deutsch</de>
      <fr>Français</fr>
      <it>Ital&amp;iano</it>
      <en>English</en>
</lang>
            ' 
            , 2 
            ) AS filemeta 
) 

SELECT 
       myfilename
      ,filemeta
      --,filemeta.value('body', 'nvarchar') 
      --, filemeta.value('.', 'nvarchar(MAX)') 

      ,filemeta.value('(/lang//de/node())[1]', 'nvarchar(MAX)') AS DE
      ,filemeta.value('(/lang//fr/node())[1]', 'nvarchar(MAX)') AS FR
      ,filemeta.value('(/lang//it/node())[1]', 'nvarchar(MAX)') AS IT
      ,filemeta.value('(/lang//en/node())[1]', 'nvarchar(MAX)') AS EN
FROM CTE 

次に、SQLのXPath-Queryによって値を取得できます。ここで、文字列変数を

filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla

そして、あなたはこのように値を更新することができます:

UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with "&quot;I am a ''value &quot;"')
WHERE id = 1 

どこに置き換えることができる/lang/de/...'.../' + @in_language + '/...'

PostGre hstoreに似ていますが、(PG hstoreの連想配列からエントリを読み取る代わりに)XMLを解析するオーバーヘッドが原因で、処理が非常に遅くなり、XMLエンコーディングが非常に面倒になり、便利になりません。


方法5(SunWuKungが推奨する1つを選択する必要があります):「製品」テーブルごとに1つの変換テーブル。つまり、言語ごとに1つの行といくつかの「テキスト」フィールドがあるため、N個のフィールドで結合する必要があるのは1つ(左)だけです。次に、「製品」テーブルにデフォルトフィールドを簡単に追加し、変換テーブルを簡単に削除して再挿入し、カスタム翻訳用の2番目のテーブルを(オンデマンドで)作成できます。そして再挿入)、そしてあなたはまだすべての外部キーを持っています。

このWORKSを見る例を作ってみましょう:

まず、テーブルを作成します。

CREATE TABLE dbo.T_Languages
(
     Lang_ID int NOT NULL
    ,Lang_NativeName national character varying(200) NULL
    ,Lang_EnglishName national character varying(200) NULL
    ,Lang_ISO_TwoLetterName character varying(10) NULL
    ,CONSTRAINT PK_T_Languages PRIMARY KEY ( Lang_ID )
);

GO




CREATE TABLE dbo.T_Products
(
     PROD_Id int NOT NULL
    ,PROD_InternalName national character varying(255) NULL
    ,CONSTRAINT PK_T_Products PRIMARY KEY ( PROD_Id )
); 

GO



CREATE TABLE dbo.T_Products_i18n
(
     PROD_i18n_PROD_Id int NOT NULL
    ,PROD_i18n_Lang_Id int NOT NULL
    ,PROD_i18n_Text national character varying(200) NULL
    ,CONSTRAINT PK_T_Products_i18n PRIMARY KEY (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id)
);

GO

-- ALTER TABLE dbo.T_Products_i18n  WITH NOCHECK ADD  CONSTRAINT FK_T_Products_i18n_T_Products FOREIGN KEY(PROD_i18n_PROD_Id)
ALTER TABLE dbo.T_Products_i18n  
    ADD CONSTRAINT FK_T_Products_i18n_T_Products 
    FOREIGN KEY(PROD_i18n_PROD_Id)
    REFERENCES dbo.T_Products (PROD_Id)
ON DELETE CASCADE 
GO

ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO

ALTER TABLE dbo.T_Products_i18n 
    ADD  CONSTRAINT FK_T_Products_i18n_T_Languages 
    FOREIGN KEY( PROD_i18n_Lang_Id )
    REFERENCES dbo.T_Languages( Lang_ID )
ON DELETE CASCADE 
GO

ALTER TABLE dbo.T_Products_i18n CHECK CONSTRAINT FK_T_Products_i18n_T_Products
GO



CREATE TABLE dbo.T_Products_i18n_Cust
(
     PROD_i18n_Cust_PROD_Id int NOT NULL
    ,PROD_i18n_Cust_Lang_Id int NOT NULL
    ,PROD_i18n_Cust_Text national character varying(200) NULL
    ,CONSTRAINT PK_T_Products_i18n_Cust PRIMARY KEY ( PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id )
);

GO

ALTER TABLE dbo.T_Products_i18n_Cust  
    ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Languages 
    FOREIGN KEY(PROD_i18n_Cust_Lang_Id)
    REFERENCES dbo.T_Languages (Lang_ID)

ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Languages

GO



ALTER TABLE dbo.T_Products_i18n_Cust  
    ADD CONSTRAINT FK_T_Products_i18n_Cust_T_Products 
    FOREIGN KEY(PROD_i18n_Cust_PROD_Id)
REFERENCES dbo.T_Products (PROD_Id)
GO

ALTER TABLE dbo.T_Products_i18n_Cust CHECK CONSTRAINT FK_T_Products_i18n_Cust_T_Products
GO

次に、データを入力します

DELETE FROM T_Languages;
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (1, N'English', N'English', N'EN');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (2, N'Deutsch', N'German', N'DE');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (3, N'Français', N'French', N'FR');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (4, N'Italiano', N'Italian', N'IT');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (5, N'Russki', N'Russian', N'RU');
INSERT INTO T_Languages (Lang_ID, Lang_NativeName, Lang_EnglishName, Lang_ISO_TwoLetterName) VALUES (6, N'Zhungwen', N'Chinese', N'ZH');

DELETE FROM T_Products;
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (1, N'Orange Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (2, N'Apple Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (3, N'Banana Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (4, N'Tomato Juice');
INSERT INTO T_Products (PROD_Id, PROD_InternalName) VALUES (5, N'Generic Fruit Juice');

DELETE FROM T_Products_i18n;
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 1, N'Orange Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 2, N'Orangensaft');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 3, N'Jus d''Orange');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (1, 4, N'Succo d''arancia');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 1, N'Apple Juice');
INSERT INTO T_Products_i18n (PROD_i18n_PROD_Id, PROD_i18n_Lang_Id, PROD_i18n_Text) VALUES (2, 2, N'Apfelsaft');

DELETE FROM T_Products_i18n_Cust;
INSERT INTO T_Products_i18n_Cust (PROD_i18n_Cust_PROD_Id, PROD_i18n_Cust_Lang_Id, PROD_i18n_Cust_Text) VALUES (1, 2, N'Orangäsaft'); -- Swiss German, if you wonder

次に、データをクエリします。

DECLARE @__in_lang_id int
SET @__in_lang_id = (
    SELECT Lang_ID
    FROM T_Languages
    WHERE Lang_ISO_TwoLetterName = 'DE'
)

SELECT 
     PROD_Id 
    ,PROD_InternalName -- Default Fallback field (internal name/one language only setup), just in ResultSet for demo-purposes
    ,PROD_i18n_Text  -- Translation text, just in ResultSet for demo-purposes
    ,PROD_i18n_Cust_Text  -- Custom Translations (e.g. per customer) Just in ResultSet for demo-purposes
    ,COALESCE(PROD_i18n_Cust_Text, PROD_i18n_Text, PROD_InternalName) AS DisplayText -- What we actually want to show 
FROM T_Products 

LEFT JOIN T_Products_i18n 
    ON PROD_i18n_PROD_Id = T_Products.PROD_Id 
    AND PROD_i18n_Lang_Id = @__in_lang_id 

LEFT JOIN T_Products_i18n_Cust 
    ON PROD_i18n_Cust_PROD_Id = T_Products.PROD_Id
    AND PROD_i18n_Cust_Lang_Id = @__in_lang_id

怠惰な場合は、ISO-TwoLetterName( 'DE'、 'EN'など)を言語テーブルの主キーとして使用することもでき、言語IDを検索する必要はありません。ただし、そうする場合は、代わりにIETF言語タグを使用することをお勧めします。これは、de-CHとde-DEを取得するためです。これは、実際には同じオルトグラフィーではありません(すべての場所でßの代わりに二重s)。 、それは同じ基本言語ですが。これは、特にen-USおよびen-GB / en-CA / en-AUまたはfr-FR / fr-CAに同様の問題があることを考えると、あなたにとって重要である可能性があるほんの小さな詳細としてです。
引用:私たちはそれを必要としません、私たちはソフトウェアを英語でのみ行います。
回答:はい-しかし、どれですか?

とにかく、整数IDを使用する場合、柔軟性があり、メソッドを後でいつでも変更できます。
また、この整数を使用する必要があります。これは、失敗したDbデザインよりも煩わしく、破壊的で面倒なものは何もないためです。

RFC 5646ISO 639-2も参照してください。

また、「私たち」が「1つの文化のみ」のアプリケーションを作成するだけである場合(通常、en-USのように)、そのため、追加の整数は必要ありません。これは、IANA言語タグですね。 彼らはこのように行くので:

de-DE-1901
de-DE-1996

そして

de-CH-1901
de-CH-1996

(1996年に正書法の改訂がありました...)スペルが間違っている単語を辞書で見つけてみてください。これは、法律および公共サービスポータルを扱うアプリケーションで非常に重要になります。
さらに重要なのは、キリル文字からラテン文字に変更されている地域があり、これは一部の曖昧な正書法の改革の表面的な煩わしさよりも厄介な場合があるため、住んでいる国によってはこれも重要な考慮事項となる場合があるためです。いずれにせよ、念のためにその整数をそこに置いた方がいいです...

編集:
そしてON DELETE CASCADE 後に追加することによって

REFERENCES dbo.T_Products( PROD_Id )

あなたは単純に言うことができます:DELETE FROM T_Productsそして、外部キー違反を取得しません。

照合については、次のようにします。

A)独自のDALを
用意するB)目的の照合名を言語テーブルに保存する

照合順序を独自のテーブルに配置することもできます。例:

SELECT * FROM sys.fn_helpcollations() 
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%' 

C)auth.user.language情報で照合名を使用できるようにする

D)次のようにSQLを記述します。

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE {#COLLATION}

E)次に、DALでこれを実行できます。

cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)

これにより、この完全に構成されたSQLクエリが得られます

SELECT 
    COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName 
FROM T_Groups 

ORDER BY GroupName COLLATE German_PhoneBook_CI_AI

細かい対応が良く、感謝します。しかし、方法5ソリューションの照合問題についてどう思いますか。異なる照合順序の多言語環境で翻訳されたテキストをソートまたはフィルタリングする必要がある場合、これは最良の方法ではないようです。そのような場合、方法2(非常に迅速に "追放"します:))は、ローカライズされた各列のターゲット照合を示すわずかな変更を加えた優れたオプションになる可能性があります。
Eugene Evdokimov 2015

2
@Eugene Evdokimov:はい。しかし、 "ORDER BY"は変数として指定できないため、常に問題になります。私のアプローチは、照合順序名を言語テーブルに保存し、これをuserinfoに含めることです。次に、各SQLステートメントでORDER BY COLUMN_NAME {#collat​​ion}と言うことができます。次に、dalで置換を行うことができます(cmd.CommandText = cmd.CommandText.Replace( "{#COLLATION}"、auth.user。 。language.collat​​ion)代わりに、LINQを使用して例えば、アプリケーションコードで並べ替えることができます。これはまた、あなたのデータベースをオフいくつかの処理負荷がかかるだろうレポートでは、レポートの種類、とにかく。。。
ステファン・スタイガー

ooこれは私が見た中で最も長いSO回答であるに違いありません。人々がプログラム全体を回答で作成するのを見ました。あなたは上手い。
2015年

SunWuKungのソリューションが最高であることに完全に同意することができます
Domi

48

いくつかの理由から、3番目のオプションが最適です。

  • 新しい言語のデータベーススキーマを変更する必要がない(したがって、コードの変更を制限する)
  • 特定のアイテムの実装されていない言語や翻訳のために多くのスペースを必要としません
  • 最も柔軟性が高い
  • あなたは疎なテーブルで終わることはありません
  • nullキーについて心配したり、nullエントリの代わりに既存の翻訳を表示していることを確認したりする必要はありません。
  • データベースを変更または拡張して、他の翻訳可能なアイテム/ものなどを含める場合、同じテーブルとシステムを使用できます。これは、残りのデータとは非常に切り離されています。

-アダム


1
個人的には、メインテーブルごとにローカライズされたテーブルを用意して、外部キーを実装できるようにすることに同意します。
Neil Barnwell、

1
3番目のオプションは問題の最もクリーンで健全な実装ですが、最初のオプションよりも複雑です。一般的なバージョンの表示、編集、報告は、常に許容できるわけではないほど多くの追加作業が必要だと思います。私は両方のソリューションを実装しましたが、ユーザーが「メイン」のアプリケーション言語の読み取り専用(時々欠落している)翻訳を必要とする場合は、シンプルな方法で十分でした。
rics

12
製品テーブルにいくつかの翻訳済みフィールドが含まれている場合はどうなりますか?製品を取得するときは、翻訳されたフィールドごとに1つの追加結合を実行する必要があります。これにより、深刻なパフォーマンスの問題が発生します。挿入/更新/削除の複雑さも(IMO)あります。これの唯一の利点は、テーブルの数が少ないことです。SunWuKungが提案した方法を採用します。これは、パフォーマンス、複雑さ、およびメンテナンスの問題の間の適切なバランスだと思います。
Frosty Z

@ rics-私は同意します、まああなたは何を提案しますか...?
セイバー、

@アダム-私は混乱している、多分私は誤解している。3つ目を提案しましたよね?これらのテーブル間の関係はどうなるのか、詳しく説明してください。DBのテーブルごとにTranslationテーブルとTranslationEntryテーブルを実装する必要があるということですか?
セイバー、

9

この例を見てみましょう:

PRODUCTS (
    id   
    price
    created_at
)

LANGUAGES (
    id   
    title
)

TRANSLATIONS (
    id           (// id of translation, UNIQUE)
    language_id  (// id of desired language)
    table_name   (// any table, in this case PRODUCTS)
    item_id      (// id of item in PRODUCTS)
    field_name   (// fields to be translated)
    translation  (// translation text goes here)
)

私は説明する必要はないと思います、構造はそれ自体を説明します。


これはいい。しかし、どのように検索しますか(たとえば、product_name)?
イルミナティ

サンプルのどこかにライブサンプルがありますか?それを使って何か問題がありましたか?
DavidLétourneau16年

もちろん、私は多言語の不動産プロジェクトを持っています。私たちは4つの言語をサポートしています。検索は少し複雑ですが、高速です。もちろん、大規模なプロジェクトでは必要以上に遅くなる可能性があります。中小規模のプロジェクトでは問題ありません。
バンブリク2016年

8

私は通常、このアプローチを採用します(実際のSQLではありません)。これは、最後のオプションに対応します。

table Product
productid INT PK, price DECIMAL, translationid INT FK

table Translation
translationid INT PK

table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)

view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'

翻訳可能なすべてのテキストを1か所に配置すると、メンテナンスが非常に簡単になるためです。翻訳は翻訳会社にアウトソーシングされる場合があります。これにより、1つの大きなエクスポートファイルだけを送信して、簡単にインポートすることができます。


1
TranslationテーブルまたはTranslationItem.translationitemid列の目的は何ですか?
DanMan

4

技術的な詳細と解決策に進む前に、少し立ち止まって、要件についていくつか質問してください。答えは、技術的なソリューションに大きな影響を与える可能性があります。このような質問の例は次のとおりです
。-すべての言語が常に使用されますか?
-いつ、誰が列に異なる言語バージョンを入力しますか?
-ユーザーがテキストの特定の言語を必要とし、システムに何もない場合はどうなりますか?
-テキストのみがローカライズされるか、他のアイテムもあります(たとえば、PRICEは$と€に格納できます。それらは異なる場合があるためです)


ローカリゼーションはより広範なトピックであることを知っています。あなたが私の注意を引く問題を認識していますが、現在、スキーマ設計の非常に具体的な問題に対する回答を探しています。新しい言語が段階的に追加され、それぞれがほぼ完全に翻訳されると思います。
qbeuek 2008年

3

ローカリゼーションのヒントを探していたところ、このトピックが見つかりました。これがなぜ使用されるのか疑問に思いました:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

したがって、user39603が示唆するような結果が得られます。

table Product
productid INT PK, price DECIMAL, translationid INT FK

table Translation
translationid INT PK

table TranslationItem
translationitemid INT PK, translationid INT FK, text VARCHAR, languagecode CHAR(2)

view ProductView
select * from Product
inner join Translation
inner join TranslationItem
where languagecode='en'

テーブルTranslationをそのままにしておけば、次のようになります。

    table Product
    productid INT PK, price DECIMAL

    table ProductItem
    productitemid INT PK, productid INT FK, text VARCHAR, languagecode CHAR(2)

    view ProductView
    select * from Product
    inner join ProductItem
    where languagecode='en'

1
承知しました。私は呼びたいProductItemようなテーブル何かProductTextsまたはProductL10nかかわらを。もっと理にかなっています。
DanMan、2014

1

ランダマイザーに同意します。テーブル「翻訳」が必要な理由がわかりません。

これで十分だと思います:

TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName

1

以下のアプローチは実行可能でしょうか?複数の列の翻訳が必要なテーブルがあるとします。したがって、製品については、翻訳が必要な製品名と製品の説明の両方を持つことができます。次のことをしていただけませんか:

CREATE TABLE translation_entry (
      translation_id        int,
      language_id           int,
      table_name            nvarchar(200),
      table_column_name     nvarchar(200),
      table_row_id          bigint,
      translated_text       ntext
    )

    CREATE TABLE translation_language (
      id int,
      language_code CHAR(2)
    )   

0

「どちらがいいか」は、プロジェクトの状況に基づいています。最初のものは選択と保守が簡単で、エンティティを選択するときにテーブルを結合する必要がないため、パフォーマンスも最高です。プロジェクトが2つか3つの言語しかサポートしていないことを確認し、それが増加しない場合は、それを使用できます。

2つ目は問題ありませんが、理解および保守が困難です。そして、パフォーマンスは最初のものよりも悪いです。

最後の1つはスケーラビリティには優れていますが、パフォーマンスには劣ります。T_TRANSLATION_ENTRYテーブルはますます大きくなり、いくつかのテーブルからエンティティのリストを取得したい場合はひどいです。


0

このドキュメントでは、各方法の可能な解決策と長所と短所について説明します。新しい言語を追加するときにDBスキーマを変更する必要がないため、「行のローカライズ」を好みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.