これは興味深い問題なので、ネクロマンスにしましょう。
方法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&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 ""I am a ''value ""')
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 5646、ISO 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