Çok dilli veritabanı için şema


235

Çok dilli bir yazılım geliştiriyorum. Uygulama koduna gelince, yerelleştirme bir sorun değildir. Dile özgü kaynakları kullanabilir ve onlarla iyi çalışan her türlü araca sahip olabiliriz.

Ancak çok dilli bir veritabanı şemasını tanımlamak için en iyi yaklaşım nedir? Diyelim ki çok sayıda tablonuz var (100 veya daha fazla) ve her tablonun yerelleştirilebilen birden fazla sütunu olabilir (nvarchar sütunlarının çoğu yerelleştirilebilir olmalıdır). Örneğin, tablolardan birinde ürün bilgileri bulunabilir:

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

NAME ve DESCRIPTION sütunlarında çok dilli metni desteklemek için üç yaklaşım düşünebilirim:

  1. Her dil için ayrı sütun

    Sisteme yeni bir dil eklediğimizde, çevrilmiş metni saklamak için ek sütunlar oluşturmalıyız, şöyle:

    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. Her dil için sütun içeren çeviri tablosu

    Çevrilmiş metni saklamak yerine, çeviriler tablosunun yalnızca yabancı bir anahtarı saklanır. Çeviriler tablosu her dil için bir sütun içerir.

    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. Her dil için satır içeren çeviri tabloları

    Çevrilmiş metni saklamak yerine, çeviriler tablosunun yalnızca yabancı bir anahtarı saklanır. Çeviriler tablosu yalnızca bir anahtar içerir ve ayrı bir tablo, bir dile yapılan her çeviri için bir satır içerir.

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

Her çözümün artıları ve eksileri vardır ve bu yaklaşımlarla ilgili deneyimlerinizin neler olduğunu, ne önerdiğinizi ve çok dilli bir veritabanı şeması tasarlamaya nasıl devam edeceğinizi bilmek istiyorum.



3
Bu bağlantıyı kontrol edebilirsiniz: gsdesign.ro/blog/multilanguage-database-design-approach Yorumları okumak çok faydalı olsa da
Fareed Alnamrouti

3
LANGUAGE_CODEdoğal anahtar, kaçının LANGUAGE_ID.
gavenkoa

1
Zaten 2. ve 3.'yi gördüm / kullandım, onları tavsiye etmiyorum, kolayca yetim satırlarla sonuçlanıyorsunuz. @SunWiKung tasarımı daha iyi IMO görünüyor.
Guillaume86

4
Tesadüfen uyguladığımız SunWuKungs tasarımını tercih ediyorum. Ancak, harmanlamaları dikkate almanız gerekir. En azından Sql Server'da, her sütun büyük / küçük harf duyarlılığı, aksanlı karakterlerin denkliği (veya değil) ve diğer dile özgü hususlar gibi şeyleri belirleyen bir harmanlama özelliğine sahiptir. Dile özgü harmanlamalar kullanıp kullanmadığınız genel uygulama tasarımınıza bağlıdır, ancak yanlış anlarsanız daha sonra değiştirmek zor olacaktır. Dile özgü harmanlamalara ihtiyacınız varsa, dil başına bir satıra değil, dil başına bir sütuna ihtiyacınız olacaktır.
Elroy Flynn

Yanıtlar:


113

Her çevrilebilir tablo için ilgili bir çeviri tablosuna sahip olmak hakkında ne düşünüyorsunuz?

TABLO OLUŞTUR T_PRODUCT (pr_id int, PRICE NUMBER (18, 2))

CREATE TABLE T_PRODUCT_tr (pr_id INT FK, dil kodu varchar, pr_name metni, pr_descr metni)

Bu şekilde, birden fazla çevrilebilir sütuna sahipseniz, bunu elde etmek için yalnızca tek bir birleşim gerekir.

Bunun olumsuz yanı, karmaşık bir dil geri dönüş mekanizmanız varsa, bunu her çeviri tablosu için uygulamanız gerekebilir - bunu yapmak için bazı saklı yordamlara güveniyorsanız. Eğer app bunu yaparsanız, bu muhtemelen bir sorun olmayacaktır.

Ne düşündüğünüzü bana bildirin - Bir sonraki başvurumuz için de bir karar vermek üzereyim. Şimdiye kadar 3. tipinizi kullandık.


2
Bu seçenek benim 1 numaralı seçeneğime benzer ancak daha iyidir. Bakımı hala zor ve yeni diller için yeni tablolar oluşturulmasını gerektiriyor, bu yüzden onu uygulamakta isteksiz olurum.
qbeuek

28
yeni bir dil için yeni bir tablo gerektirmez - yeni dilinizle uygun _tr tablosuna yeni bir satır eklemeniz yeterlidir, sadece yeni bir çevrilebilir tablo oluşturursanız yeni bir _tr tablosu oluşturmanız gerekir

3
Bunun iyi bir yöntem olduğuna inanıyorum. diğer yöntemler tonlarca sol birleşim gerektirir ve her biri 3 seviye derinliğinde çeviri olan birden fazla tabloya katılırken ve her birinin 3 alanı vardır, sadece çeviriler için 3 * 3 9 sol birleşim gerekir .. diğer akıllıca 3. Ayrıca kısıtlamalar vb eklemek daha kolaydır ve ben arama daha resonable inanıyorum.
GorillaApe

1
Ne zaman T_PRODUCT1 milyon satır vardır, T_PRODUCT_tr2 çok sql verimini azaltan million.Would olurdu?
Mithril

1
@Mithril Her iki durumda da 2 milyon satırınız var. En azından bu yöntemle birleştirmeye ihtiyacınız yok.
David D

56

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

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 "&quot;I am a ''value &quot;"')
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_Productsve 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

İyi ayrıntılı yanıt, çok teşekkürler. Ancak, Yöntem 5 çözümündeki harmanlama sorunları hakkında ne düşünüyorsunuz? Bu, çok dilli ortamda çevrilmiş metni farklı harmanlamalarla sıralamanız veya filtrelemeniz gerektiğinde en iyi yol değildir. Ve bu durumda, Yöntem 2 (çok hızlı bir şekilde "dışlanmış olduğunuz" :) daha iyi bir seçenek olabilir.
Eugene Evdokimov

2
@Eugene Evdokimov: Evet, ancak "ORDER BY" her zaman bir sorun olacaktır, çünkü bunu değişken olarak belirtemezsiniz. Benim yaklaşımım harmanlama adını dil tablosuna kaydetmek ve bunu userinfo'ya yerleştirmek olacaktır. Daha sonra, her SQL İfadesinde ORDER BY COLUMN_NAME {#collation} diyebilirsiniz ve ardından dal (cmd.CommandText = cmd.CommandText.Replace ("{# COLLATION}"), auth.user adresinde bir değiştirme yapabilirsiniz. Alternatif olarak, uygulama kodunuzu örneğin LINQ kullanarak sıralayabilirsiniz.Bu da veritabanınızdan biraz işlem yükü alır Raporlar için rapor yine de sıralar.
Stefan Steiger

oo Bu gördüğüm en uzun SO yanıtı olmalı ve insanların cevaplarda bütün programlar yaptığını gördüm. İyisin.
Domino

SunWuKung'un çözümünün en iyisi olduğunu tamamen kabul edebilir
Domi

48

Üçüncü seçenek, birkaç nedenden dolayı en iyisidir:

  • Yeni diller için veritabanı şemasının değiştirilmesini gerektirmez (ve dolayısıyla kod değişikliklerini sınırlar)
  • Uygulanmayan diller veya belirli bir öğenin çevirileri için çok fazla alan gerektirmez
  • En esnekliği sağlar
  • Sonunda seyrek tablolarla bitmiyorsun
  • Boş anahtarlar ve boş bir giriş yerine var olan bir çeviriyi görüntülediğinizi kontrol etmeniz gerekmez.
  • Veritabanınızı diğer çevrilebilir öğeleri / şeyleri / vb. Kapsayacak şekilde değiştirir veya genişletirseniz, aynı tabloları ve sistemi kullanabilirsiniz - bu, verilerin geri kalanından çok ayrılır.

-Adam


1
Kişisel anahtarların uygulanmasına izin vermek için kişisel olarak her ana tablo için yerelleştirilmiş bir tabloya sahip olacağımı kabul ediyorum.
Neil Barnwell

1
Üçüncü seçenek, sorunun en temiz ve sağlam uygulaması olmasına rağmen, ilk seçenekten daha karmaşıktır. Genel sürümün görüntülenmesi, düzenlenmesi, raporlanması, her zaman kabul edilemeyecek kadar fazla çaba gerektirdiğini düşünüyorum. Her iki çözümü de uyguladım, kullanıcılar "ana" uygulama dilinin salt okunur (bazen eksik) çevirisine ihtiyaç duyduklarında daha basitti.
rics

12
Ürün tablosu çevrilmiş birkaç alan içeriyorsa ne olur? Ürünleri alırken, tercüme edilen her alan için bir ek birleştirme yapmanız gerekecek ve bu da ciddi performans sorunlarına neden olacaktır. Ekleme / güncelleme / silme için (IMO) ek karmaşıklık da vardır. Bunun tek avantajı daha az sayıda tablo olmasıdır. SunWuKung'un önerdiği yöntemi tercih ederim: Performans, karmaşıklık ve bakım sorunları arasında iyi bir denge olduğunu düşünüyorum.
Soğuk Z

@ rics- Katılıyorum, ne öneriyorsun ...?
saber

@ Adam- Kafam karıştı, belki yanlış anladım. Üçüncüsünü önerdin, değil mi? Lütfen bu tablolar arasındaki ilişkiler nasıl olacak? Yani DB'deki her tablo için Translation ve TranslationEntry tablolarını uygulamalıyız?
saber

9

Bu örneğe bir göz atın:

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

Bence açıklamaya gerek yok, yapı kendini anlatıyor.


bu iyi. ancak nasıl arama yaparsınız (örneğin ürün_adı)?
Illuminati

Örneğinizin bir yerinde canlı bir örneğiniz var mı? Kullanarak herhangi bir sorunla karşılaştınız mı?
David Létourneau

Tabii, çok dilli gayrimenkul projem var, 4 dili destekliyoruz. Arama biraz karmaşık ama hızlı. Tabii ki büyük projelerde olması gerekenden daha yavaş olabilir. Küçük veya orta ölçekli projelerde sorun değil.
bamburik

8

Genellikle bu yaklaşıma giderdim (gerçek sql değil), bu son seçeneğinize karşılık gelir.

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'

Çünkü tüm çevrilebilir metinlerin tek bir yerde bulunması bakımı çok daha kolay hale getirir. Bazen çeviriler çeviri bürolarına gönderilir, bu şekilde bunları yalnızca büyük bir dışa aktarma dosyası gönderebilir ve kolayca geri alabilirsiniz.


1
TranslationTablo veya TranslationItem.translationitemidsütun hangi amaca hizmet eder?
DanMan

4

Teknik detaylara ve çözümlere gitmeden önce bir dakika durmalı ve gereksinimler hakkında birkaç soru sormalısınız. Cevapların teknik çözüm üzerinde büyük etkisi olabilir. Bu tür sorulara örnek olarak şunlar verilebilir:
- Tüm diller her zaman kullanılacak mı?
- Sütunları farklı dil sürümleriyle kim ve ne zaman dolduracak?
- Bir kullanıcı belirli bir metnin diline ihtiyaç duyduğunda ve sistemde hiçbir dil yoksa ne olur?
- Yalnızca metinler yerelleştirilecek veya başka öğeler de olacak (örneğin PRICE $ ve € olarak depolanabilir çünkü farklı olabilirler)


Yerelleştirmenin çok daha geniş bir konu olduğunu biliyorum ve dikkatime sunduğunuz sorunların farkındayım, ancak şu anda şema tasarımının çok özel bir sorununa cevap arıyorum. Yeni dillerin aşamalı olarak ekleneceğini ve her birinin neredeyse tamamen çevrileceğini varsayıyorum.
qbeuek

3

Yerelleştirme için bazı ipuçları arıyordum ve bu konuyu buldum. Bunun neden kullanıldığını merak ediyordum:

CREATE TABLE T_TRANSLATION (
   TRANSLATION_ID
)

Yani user39603 gibi bir şey alırsınız:

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'

Bu tabloyu almak için sadece tabloyu terk edemez misin:

    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
Elbette. Ben bunu ProductItemgibi masa şey ProductTextsya ProductL10ngerçi. Daha mantıklı.
DanMan

1

Randomizöre katılıyorum. Neden bir masaya "çeviriye" ihtiyacınız olduğunu anlamıyorum.

Bence bu yeterli:

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

1

Aşağıdaki yaklaşım uygulanabilir mi? 1'den fazla sütunun çevrilmesi gereken tablolarınız olduğunu varsayalım. Yani ürün için hem çevirmene ihtiyaç duyan hem ürün adı hem de ürün açıklaması olabilir. Aşağıdakileri yapabilir misiniz?

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

"Hangisi en iyisi" proje durumuna bağlıdır. Birincisinin seçilmesi ve bakımı kolaydır ve ayrıca varlık seçildiğinde tablolara katılmaya gerek olmadığı için performans en iyisidir. Teklifinizin sadece 2 veya 3 dili desteklediğini doğruladıysanız ve artmayacaksa, kullanabilirsiniz.

İkincisi okey ama anlaşılması ve bakımı zor. Ve performans birincisinden daha kötü.

Sonuncusu ölçeklenebilirlikte iyidir, ancak performansta kötüdür. T_TRANSLATION_ENTRY tablosu daha da büyüyecek, bazı tablolardan varlıkların listesini almak istediğinizde korkunç olacak.


0

Bu belge , her yöntemin olası çözümlerini ve avantaj ve dezavantajlarını açıklamaktadır. Yeni bir dil eklerken DB şemasını değiştirmeniz gerekmediği için "satır yerelleştirmesi" ni tercih ederim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.