Bu ilginç bir konudur, hadi büyücülük yapalım.
Yöntem 1'in sorunları ile başlayalım:
Sorun: Hız kazanmak için normalleştiriyorsunuz.
SQL'de (hstore ile PostGreSQL hariç), bir parametre dili iletemez ve şunları söyleyemezsiniz:
SELECT ['DESCRIPTION_' + @in_language] FROM T_Products
Yani bunu yapmak zorundasınız:
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
Bu, yeni bir dil eklerseniz TÜM sorgularınızı değiştirmeniz gerektiği anlamına gelir. Bu, doğal olarak "dinamik SQL" kullanılmasına yol açar, böylece tüm sorgularınızı değiştirmeniz gerekmez.
Bu genellikle böyle bir şeyle sonuçlanır (ve bu arada görünümlerde veya tablo değerli işlevlerde kullanılamaz, bu da raporlama tarihini gerçekten filtrelemeniz gerekiyorsa gerçekten bir sorundur)
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
Buradaki sorun
a) Tarih biçimlendirme çok dile özgüdür, bu nedenle ISO biçiminde girmezseniz (ortalama bahçe çeşidi programcısının genellikle yapmadığı ve açıkça belirtilse bile, kullanıcıyı cehennemde görmeyeceğinden emin olan bir rapor).
ve
b) en önemlisi , her türlü sözdizimi denetimini kaybedersiniz . Eğer <insert name of your "favourite" person here>
değiştirir aniden kanat değişikliği için gereklilikler ve aa yeni tablo oluşturulur, çünkü şema, eskisinin sol ama referans alan adını, sen her türlü uyarı alamadım. Rapor , wing parametresini (==> guid.empty) seçmeden çalıştırdığınızda bile çalışır . Ama aniden, gerçek bir kullanıcı gerçekten bir kanat seçtiğinde ==>patlama . Bu yöntem her türlü testi tamamen bozar.
Yöntem 2:
Özetle: "Harika" bir fikir (uyarı - alaycılık), yöntem 3'ün dezavantajlarını (birçok giriş olduğunda yavaş hız) yöntem 1'in oldukça dezavantajlarıyla birleştirelim
. Bu yöntemin tek avantajı, tüm çevirileri tek bir tabloda yapın ve böylece bakımı basitleştirin. Bununla birlikte, aynı şey yöntem 1 ve dinamik bir SQL saklı yordamı ve çevirileri ve hedef tablonun adını içeren (muhtemelen geçici) bir tablo ile elde edilebilir (ve tüm metin alanlarınızı aynı).
Yöntem 3:
Tüm çeviriler için bir tablo: Dezavantaj: Çevirmek istediğiniz n alan için ürün tablosunda n Yabancı Anahtarları depolamanız gerekir. Bu nedenle, n alanları için n birleştirme yapmanız gerekir. Çeviri tablosu genel olduğunda, birçok girişi vardır ve birleştirmeler yavaşlar. Ayrıca, her zaman n alan için T_TRANSLATION tablosuna n kez katılmanız gerekir. Bu oldukça genel bir yük. Şimdi, müşteri başına özel çeviriler yapmanız gerektiğinde ne yaparsınız? Ek bir tabloya başka bir 2x n eklemesi eklemeniz gerekir. Eğer katılmak zorundaysanız, 2x2xn = 4n ek birleştirme ile 10 tablo deyin, ne dağınıklık! Ayrıca, bu tasarım aynı çeviriyi 2 tablo ile kullanmayı mümkün kılar. Bir tablodaki öğe adını değiştirirsem, HER TEK BİR ZAMAN da başka bir tablodaki girişi değiştirmek ister miyim?
Ayrıca, tabloyu artık silemez ve yeniden ekleyemezsiniz, çünkü artık ÜRÜN TABLO (ları) nda yabancı anahtarlar var ... elbette FK'leri ayarlamayı atlayabilir ve sonra <insert name of your "favourite" person here>
tabloyu silebilir ve yeniden takabilirsiniz newid () [veya ek içindeki kimliği belirterek ancak kimlik ekleme KAPALI olan ] tüm girişler ve bu çok yakında veri çöpüne (ve null-referans istisnalarına) yol açar (ve eder).
Yöntem 4 (listelenmemiş): Tüm dilleri veritabanındaki XML alanında depolama. Örneğin
-- 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
Sonra değeri SQL'de XPath-Query ile alabilirsiniz, burada string değişkenini
filemeta.value('(/lang//' + @in_language + '/node())[1]', 'nvarchar(MAX)') AS bla
Ve değeri şu şekilde güncelleyebilirsiniz:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify('replace value of (/lang/de/text())[1] with ""I am a ''value ""')
WHERE id = 1
Nerede yerini alabilir /lang/de/...
ile'.../' + @in_language + '/...'
PostGre hstore gibi bir tür, XML ayrıştırma yükü nedeniyle (PG hstore'da ilişkilendirilebilir bir diziden bir girdi okumak yerine) çok yavaş hale gelir ve xml kodlamasının yararlı olması için çok acı verir.
Yöntem 5 (SunWuKung tarafından önerildiği gibi seçmelisiniz): Her "Ürün" tablosu için bir çeviri tablosu. Bu, dil başına bir satır ve birkaç "metin" alanı anlamına gelir, bu nedenle N alanlarında yalnızca BİR (sol) birleşim gerektirir. Daha sonra "Ürün" tablosuna kolayca bir varsayılan alan ekleyebilir, çeviri tablosunu kolayca silebilir ve yeniden ekleyebilir ve ayrıca özel çeviriler için (istek üzerine) ikinci bir tablo oluşturabilirsiniz. ve yeniden takın) ve yine de tüm yabancı anahtarlara sahipsiniz.
Bu ÇALIŞMALARI görmek için bir örnek verelim:
İlk olarak, tabloları oluşturun:
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
Sonra verileri doldurun
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
Ve sonra verileri sorgulayın:
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
Tembelseniz, dil tablosunun birincil anahtarı olarak ISO-TwoLetterName'i ('DE', 'EN' vb.) De kullanabilirsiniz, o zaman dil kimliğini aramanız gerekmez. Ancak bunu yaparsanız, bunun yerine IETF dili etiketini kullanmak isteyebilirsiniz , bu daha iyidir, çünkü de-CH ve de-DE'yi elde edersiniz, bu da aynı ortografi açısından değildir (her yerde ß yerine çift s) , aynı temel dil olmasına rağmen. Bu, özellikle en-US ve en-GB / en-CA / en-AU veya fr-FR / fr-CA'nın benzer sorunları olduğunu göz önünde bulundurarak sizin için önemli olabilecek küçük bir ayrıntı gibi.
Alıntı: buna ihtiyacımız yok, yazılımımızı sadece İngilizce yapıyoruz.
Cevap: Evet - ama hangisi ??
Her neyse, bir tamsayı kimliği kullanırsanız, esneksinizdir ve yönteminizi daha sonra değiştirebilirsiniz.
Ve bu tamsayıyı kullanmalısınız, çünkü botlu bir Db tasarımından daha can sıkıcı, yıkıcı ve zahmetli bir şey yoktur.
Ayrıca bakınız RFC 5646 , ISO 639-2 ,
Ve eğer hala " sadece " kültürümüzü "sadece bir kültür" için yapıyoruz (genellikle ABD gibi) - bu yüzden bu ekstra tamsayıya ihtiyacım yok, bu bahsetmek için iyi bir zaman ve yer olurdu. IANA dil etiketleri , değil mi?
Çünkü böyle gidiyorlar:
de-DE-1901
de-DE-1996
ve
de-CH-1901
de-CH-1996
(1996'da bir yazım reformu vardı ...) Yanlış yazılmışsa sözlükte bir kelime bulmaya çalışın; yasal ve kamu hizmeti portalları ile ilgili uygulamalarda bu çok önemlidir.
Daha da önemlisi, kiril alfabeden latin alfabelere değişen bölgeler var, bu da bazı belirsiz ortografi reformunun yüzeysel sıkıntısından daha zahmetli olabilir, bu yüzden hangi ülkede yaşadığınıza bağlı olarak bu da önemli bir husus olabilir. Öyle ya da böyle, o tamsayıyı içeride tutmak daha iyi, ne olur ne olmaz ...
Düzenleme:
Ve ON DELETE CASCADE
sonra ekleyerek
REFERENCES dbo.T_Products( PROD_Id )
basitçe şunu söyleyebilirsiniz: DELETE FROM T_Products
ve yabancı anahtar ihlali yoktur.
Harmanlama gelince, bunu şöyle yaparım:
A) Kendi DAL'niz olsun
B) İstediğiniz harmanlama adını dil tablosuna kaydedin
Harmanlamaları kendi tablolarına koymak isteyebilirsiniz, örneğin:
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE '%insensitive%'
AND name LIKE '%german%'
C) Harmanlama adını auth.user.language bilgilerinizde bulundurun
D) SQL'inizi şöyle yazın:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) Ardından, bunu DAL'nizde yapabilirsiniz:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
Bu size mükemmel bir şekilde oluşturulmuş SQL-Query verecek
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE German_PhoneBook_CI_AI