Đây là một vấn đề thú vị, vì vậy hãy xem xét.
Hãy bắt đầu bằng các vấn đề của phương pháp 1:
Vấn đề: Bạn đang không chuẩn hóa để tiết kiệm tốc độ.
Trong SQL (ngoại trừ PostGreSQL với hstore), bạn không thể truyền ngôn ngữ tham số và nói:
SELECT ['DESCRIPTION_' + @in_language] FROM T_Products
Vì vậy, bạn phải làm điều này:
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
Điều đó có nghĩa là bạn phải thay đổi TẤT CẢ các truy vấn của mình nếu bạn thêm một ngôn ngữ mới. Điều này tự nhiên dẫn đến việc sử dụng "SQL động", do đó bạn không phải thay đổi tất cả các truy vấn của mình.
Điều này thường dẫn đến một cái gì đó như thế này (và nó không thể được sử dụng trong các khung nhìn hoặc các hàm có giá trị bảng bằng cách này, đây thực sự là một vấn đề nếu bạn thực sự cần phải lọc ngày báo cáo)
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
Vấn đề với điều này là
a) Định dạng ngày rất cụ thể về ngôn ngữ, do đó bạn gặp vấn đề ở đó, nếu bạn không nhập ở định dạng ISO (điều mà lập trình viên làm vườn trung bình thường không làm được và trong trường hợp một báo cáo người dùng chắc chắn như địa ngục sẽ không làm cho bạn, ngay cả khi được chỉ dẫn rõ ràng để làm như vậy).
và
b) đáng kể nhất , bạn mất bất kỳ loại kiểm tra cú pháp . Nếu <insert name of your "favourite" person here>
thay đổi lược đồ vì đột nhiên các yêu cầu thay đổi cánh và một bảng mới được tạo, bảng cũ còn lại nhưng trường tham chiếu được đổi tên, bạn không nhận được bất kỳ loại cảnh báo nào. Một báo cáo thậm chí hoạt động khi bạn chạy nó mà không chọn tham số cánh (==> guide.empty). Nhưng đột nhiên, khi một người dùng thực sự thực sự chọn một cánh ==>bùng nổ . Phương pháp này hoàn toàn phá vỡ bất kỳ loại thử nghiệm.
Phương pháp 2:
Tóm lại: Ý tưởng "Tuyệt vời" (cảnh báo - châm biếm), hãy kết hợp các nhược điểm của phương pháp 3 (tốc độ chậm khi có nhiều mục) với các nhược điểm khá kinh khủng của phương pháp 1.
Ưu điểm duy nhất của phương pháp này là bạn giữ được tất cả các bản dịch trong một bảng, và do đó làm cho bảo trì đơn giản. Tuy nhiên, điều tương tự có thể đạt được với phương thức 1 và quy trình lưu trữ SQL động và bảng (có thể là tạm thời) có chứa các bản dịch và tên của bảng mục tiêu (và khá đơn giản giả sử bạn đặt tên cho tất cả các trường văn bản của mình là tương tự).
Phương pháp 3:
Một bảng cho tất cả các bản dịch: Nhược điểm: Bạn phải lưu trữ n Khóa ngoài trong bảng sản phẩm cho n trường bạn muốn dịch. Do đó, bạn phải thực hiện n tham gia cho n trường. Khi bảng dịch là toàn cầu, nó có nhiều mục và tham gia trở nên chậm. Ngoài ra, bạn luôn phải tham gia bảng T_TRANSLATION n lần cho n trường. Đây là một chi phí khá cao. Bây giờ, bạn sẽ làm gì khi bạn phải điều chỉnh các bản dịch tùy chỉnh cho mỗi khách hàng? Bạn sẽ phải thêm 2x n tham gia vào một bảng bổ sung. Nếu bạn phải tham gia, hãy nói 10 bảng, với 2x2xn = 4n tham gia bổ sung, thật là một mớ hỗn độn! Ngoài ra, thiết kế này cho phép sử dụng cùng một bản dịch với 2 bảng. Nếu tôi thay đổi tên mục trong một bảng, tôi có thực sự muốn thay đổi mục trong bảng khác MERYI LẦN MỘT LẦN KHÔNG?
Ngoài ra, bạn không thể xóa và chèn lại bảng nữa, vì hiện tại đã có khóa ngoại trong BẢNG SẢN PHẨM ... tất nhiên bạn có thể bỏ qua cài đặt FK, sau đó <insert name of your "favourite" person here>
có thể xóa bảng và chèn lại tất cả các mục có newid () [hoặc bằng cách chỉ định id trong phần chèn, nhưng việc chèn nhận dạng TẮT ], và điều đó sẽ (và sẽ) dẫn đến rác dữ liệu (và ngoại lệ tham chiếu null) thực sự sớm.
Phương pháp 4 (không được liệt kê): Lưu trữ tất cả các ngôn ngữ trong trường XML trong cơ sở dữ liệu. ví dụ
-- 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
Sau đó, bạn có thể nhận giá trị bằng XPath-Query trong SQL, nơi bạn có thể đặt biến chuỗi trong
filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla
Và bạn có thể cập nhật giá trị như thế này:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with ""I am a ''value ""')
WHERE id = 1
Nơi bạn có thể thay thế /lang/de/...
bằng'.../' + @in_language + '/...'
Giống như hstore PostGre, ngoại trừ do chi phí phân tích cú pháp XML (thay vì đọc một mục từ một mảng kết hợp trong PG hstore), nó trở nên quá chậm cộng với mã hóa xml khiến nó quá đau để trở nên hữu ích.
Phương pháp 5 (theo khuyến nghị của SunWuKung, người bạn nên chọn): Một bảng dịch cho mỗi bảng "Sản phẩm". Điều đó có nghĩa là một hàng cho mỗi ngôn ngữ và một số trường "văn bản", do đó, nó chỉ yêu cầu MỘT (trái) tham gia trên N trường. Sau đó, bạn có thể dễ dàng thêm trường mặc định trong phần "Sản phẩm", bạn có thể dễ dàng xóa và chèn lại bảng dịch và bạn có thể tạo bảng thứ hai để dịch tùy chỉnh (theo yêu cầu), bạn cũng có thể xóa và chèn lại) và bạn vẫn có tất cả các khóa ngoại.
Hãy làm một ví dụ để xem CÔNG TRÌNH này:
Đầu tiên, tạo các bảng:
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
Sau đó điền dữ liệu
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
Và sau đó truy vấn dữ liệu:
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
Nếu bạn lười biếng, thì bạn cũng có thể sử dụng ISO-TwoLetterName ('DE', 'EN', v.v.) làm khóa chính của bảng ngôn ngữ, sau đó bạn không phải tra cứu id ngôn ngữ. Nhưng nếu bạn làm như vậy, bạn có thể muốn sử dụng thẻ ngôn ngữ IETF thay vào đó, điều này tốt hơn, bởi vì bạn nhận được de-CH và de-DE, đây thực sự không phải là thông minh ortography (gấp đôi thay vì ß ở mọi nơi) , mặc dù đó là ngôn ngữ cơ bản giống nhau. Đó chỉ là một chi tiết nhỏ có thể quan trọng đối với bạn, đặc biệt khi xem xét rằng en-US và en-GB / en-CA / en-AU hoặc fr-FR / fr-CA có vấn đề tương tự.
Trích dẫn: chúng tôi không cần nó, chúng tôi chỉ làm phần mềm bằng tiếng Anh.
Trả lời: Có - nhưng cái nào ??
Dù sao, nếu bạn sử dụng ID số nguyên, bạn linh hoạt và có thể thay đổi phương pháp của mình bất kỳ lúc nào.
Và bạn nên sử dụng số nguyên đó, bởi vì không có gì khó chịu, phá hoại và rắc rối hơn một thiết kế Db bị lỗi.
Xem thêm RFC 5646 , ISO 639-2 ,
Và, nếu bạn vẫn nói "chúng tôi" chỉ tạo ra ứng dụng của mình cho "chỉ một nền văn hóa" (như en-US thường) - do đó tôi không cần thêm số nguyên đó, đây sẽ là thời điểm tốt để đề cập đến Thẻ ngôn ngữ IANA , phải không?
Bởi vì họ đi như thế này:
de-DE-1901
de-DE-1996
và
de-CH-1901
de-CH-1996
(đã có một cải cách chính tả vào năm 1996 ...) Hãy thử tìm một từ trong từ điển nếu nó sai chính tả; điều này trở nên rất quan trọng trong các ứng dụng liên quan đến cổng dịch vụ công cộng và pháp lý.
Quan trọng hơn, có những khu vực đang thay đổi từ bảng chữ cái cyrillic sang bảng chữ cái Latinh, điều này có thể gây rắc rối hơn so với sự phiền toái bề ngoài của một số cải cách chỉnh hình tối nghĩa, đó là lý do tại sao điều này cũng có thể được xem xét quan trọng, tùy thuộc vào quốc gia bạn sống. Bằng cách này hay cách khác, tốt hơn là có số nguyên đó trong đó, chỉ trong trường hợp ...
Chỉnh sửa:
Và bằng cách thêm vào ON DELETE CASCADE
sau
REFERENCES dbo.T_Products( PROD_Id )
bạn có thể chỉ cần nói: DELETE FROM T_Products
và không vi phạm khóa ngoại.
Đối với đối chiếu, tôi sẽ làm như thế này:
A) Có DAL
B của riêng bạn ) Lưu tên đối chiếu mong muốn trong bảng ngôn ngữ
Bạn có thể muốn đặt các bộ sưu tập vào bảng riêng của họ, ví dụ:
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%'
C) Có tên đối chiếu có sẵn trong thông tin auth.user. Ngôn ngữ của bạn
D) Viết SQL của bạn như thế này:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) Sau đó, bạn có thể làm điều này trong DAL của bạn:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
Sau đó sẽ cung cấp cho bạn truy vấn SQL được soạn hoàn hảo này
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE German_PhoneBook_CI_AI