एक बहुभाषी डेटाबेस के लिए स्कीमा


235

मैं एक बहुभाषी सॉफ्टवेयर विकसित कर रहा हूँ। जहां तक ​​एप्लिकेशन कोड जाता है, स्थानीयता एक मुद्दा नहीं है। हम भाषा विशिष्ट संसाधनों का उपयोग कर सकते हैं और सभी प्रकार के उपकरण हैं जो उनके साथ अच्छी तरह से काम करते हैं।

लेकिन एक बहुभाषी डेटाबेस स्कीमा को परिभाषित करने में सबसे अच्छा तरीका क्या है? मान लें कि हमारे पास बहुत सी तालिकाएँ (100 या अधिक) हैं, और प्रत्येक तालिका में कई स्तंभ हो सकते हैं जिन्हें स्थानीयकृत किया जा सकता है (अधिकांश नवरचचर कॉलम स्थानीय होने चाहिए)। उदाहरण के लिए, तालिकाओं में से एक में उत्पाद जानकारी हो सकती है:

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

मैं NAME और DESCRIPTION कॉलम में बहुभाषी पाठ का समर्थन करने के लिए तीन दृष्टिकोणों के बारे में सोच सकता हूं:

  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. प्रत्येक भाषा के लिए कॉलम के साथ अनुवाद तालिका

    अनुवादित पाठ को संग्रहीत करने के बजाय, अनुवाद तालिका में केवल एक विदेशी कुंजी संग्रहीत है। अनुवाद तालिका में प्रत्येक भाषा के लिए एक कॉलम होता है।

    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. प्रत्येक भाषा के लिए पंक्तियों के साथ अनुवाद तालिका

    अनुवादित पाठ को संग्रहीत करने के बजाय, अनुवाद तालिका में केवल एक विदेशी कुंजी संग्रहीत है। अनुवाद तालिका में केवल एक कुंजी होती है, और एक अलग तालिका में एक भाषा के लिए प्रत्येक अनुवाद के लिए एक पंक्ति होती है।

    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 हालांकि टिप्पणियों को पढ़ना बहुत मददगार है
फरीद अलमृती

3
LANGUAGE_CODEप्राकृतिक कुंजी है, से बचें LANGUAGE_ID
गवेंको

1
मैंने पहले से ही 2. और 3. को देखा / उपयोग किया था, मैं उनकी सिफारिश नहीं करता, आप आसानी से अनाथ पंक्तियों के साथ समाप्त होते हैं। @SunWiKung डिज़ाइन बेहतर IMO लगता है।
गुइलुमेरी

4
मैं SunWuKungs डिजाइन पसंद करता हूं, जो संयोग से हमने लागू किया है। हालांकि, आपको टकराव पर विचार करने की आवश्यकता है। Sql Server में कम से कम, प्रत्येक कॉलम में एक कोलाजेशन प्रॉपर्टी होती है, जो उच्चारण पात्रों की केस सेंसिटिविटी, समतुल्यता (या नहीं) और अन्य भाषा-विशिष्ट विचारों को निर्धारित करती है। चाहे आप भाषा-विशिष्ट कोलाज का उपयोग करें या नहीं, आपके समग्र अनुप्रयोग डिज़ाइन पर निर्भर करता है, लेकिन यदि आप इसे गलत पाते हैं, तो बाद में इसे बदलना मुश्किल होगा। यदि आपको भाषा-विशिष्ट टकरावों की आवश्यकता है, तो आपको प्रति भाषा पंक्ति की आवश्यकता होगी, न कि प्रति भाषा पंक्ति की।
एलारॉय फ्लिनन

जवाबों:


113

प्रत्येक अनुवाद तालिका के लिए संबंधित अनुवाद तालिका होने के बारे में आप क्या सोचते हैं?

बनाएँ तालिका T_PRODUCT (pr_id int, PRICE NUMBER (18, 2))

TATE T_PRODUCT_tr (pr_id INT FK, languagecode varchar, pr_name पाठ, pr_descr टेक्स्ट) बनाएँ

इस तरह यदि आपके पास कई अनुवाद योग्य स्तंभ हैं, तो इसे प्राप्त करने के लिए केवल एक ही सम्मिलित होने की आवश्यकता होगी + क्योंकि आप एक अनुवाद को ऑटोजेनररेट नहीं कर रहे हैं ताकि उनके संबंधित अनुवादों के साथ आइटम आयात करना आसान हो सके।

इसका नकारात्मक पक्ष यह है कि यदि आपके पास एक जटिल भाषा में कमियां हैं, तो आपको प्रत्येक अनुवाद तालिका के लिए इसे लागू करने की आवश्यकता हो सकती है - यदि आप ऐसा करने के लिए कुछ संग्रहित प्रक्रिया पर भरोसा कर रहे हैं। अगर आप ऐप से ऐसा करते हैं तो यह समस्या नहीं होगी।

मुझे बताएं कि आप क्या सोचते हैं - मैं भी हमारे अगले आवेदन के लिए इस पर निर्णय लेने वाला हूं। अब तक हमने आपके 3 प्रकार का उपयोग किया है।


2
यह विकल्प मेरे विकल्प nr 1 के समान है लेकिन बेहतर है। इसे बनाए रखना अभी भी कठिन है और नई भाषाओं के लिए नई तालिकाएँ बनाने की आवश्यकता है, इसलिए मैं इसे लागू करने में अनिच्छुक रहूंगा।
क्यूबेक

28
एक नई भाषा के लिए नई तालिका की आवश्यकता नहीं है - आप अपनी नई भाषा के साथ उचित _tr तालिका में एक नई पंक्ति जोड़ते हैं, आपको केवल एक नई _tr तालिका बनाने की आवश्यकता होती है यदि आप एक नई अनुवाद योग्य तालिका बनाते हैं

3
मुझे लगता है कि यह एक अच्छा तरीका है। अन्य विधियों में बहुत सारे लेफ्ट जॉन्स की आवश्यकता होती है और जब आप कई टेबलों में शामिल हो रहे होते हैं, जिनमें से प्रत्येक में अनुवाद 3 स्तर की तरह होता है, और प्रत्येक में 3 फ़ील्ड होते हैं, जिनकी आपको 3 * 3 9 की आवश्यकता होती है, केवल अनुवादों के लिए। बाधाओं को जोड़ने के लिए आसान है आदि और मैं खोज करने योग्य है और अधिक गूंजने योग्य है।
गोरिल्लाएप

1
जब T_PRODUCT1 मिलियन पंक्तियाँ होती हैं, T_PRODUCT_trतो 2 मिलियन होती हैं। क्या इससे sql दक्षता बहुत कम हो जाती है?
मिथिला

1
@ मिथाइल किसी भी तरह से आपके पास 2 मिलियन पंक्तियाँ हैं। कम से कम आपको इस विधि से जुड़ने की आवश्यकता नहीं है।
डेविड डी।

56

यह एक दिलचस्प मुद्दा है, तो चलो necromance है।

आइए विधि 1 की समस्याओं से शुरू करें:
समस्या: आप गति को बचाने के लिए अपवित्र कर रहे हैं।
SQL में (पोस्टस्टोर के साथ 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 

यदि आप एक नई भाषा जोड़ते हैं तो आपको अपने सभी प्रश्नों को बदलना होगा। यह स्वाभाविक रूप से "गतिशील एसक्यूएल" का उपयोग करता है, इसलिए आपको अपने सभी प्रश्नों को बदलने की आवश्यकता नहीं है।

यह आमतौर पर कुछ इस तरह से होता है (और इसका उपयोग विचारों या तालिका-मूल्यवान कार्यों में नहीं किया जा सकता है, जो वास्तव में एक समस्या है अगर आपको वास्तव में रिपोर्टिंग तिथि को फ़िल्टर करने की आवश्यकता है)

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

इसके साथ समस्या
एक है) दिनांक-स्वरूपण बहुत भाषा-विशिष्ट है, इसलिए आपको वहां एक समस्या मिलती है, यदि आप आईएसओ प्रारूप में इनपुट नहीं करते हैं (जो औसत उद्यान-किस्म प्रोग्रामर आमतौर पर नहीं करता है, और मामले में एक रिपोर्ट जो उपयोगकर्ता को यकीन है कि नरक आपके लिए नहीं करेगी, भले ही स्पष्ट रूप से ऐसा करने का निर्देश दिया गया हो)।
और
बी) सबसे महत्वपूर्ण रूप से , आप किसी भी प्रकार की वाक्यविन्यास जाँच को ढीला करते हैं । यदि <insert name of your "favourite" person here>स्कीमा को बदल देता है क्योंकि अचानक विंग की आवश्यकताएं बदल जाती हैं, और एक नया टेबल बनाया जाता है, तो पुराना एक छोड़ दिया जाता है, लेकिन संदर्भ क्षेत्र का नाम बदल दिया जाता है, आपको किसी भी प्रकार की चेतावनी नहीं मिलती है। एक रिपोर्ट तब भी काम करती है जब आप इसे विंग पैरामीटर (==> guide.empty) का चयन किए बिना चलाते हैं । लेकिन अचानक, जब एक वास्तविक उपयोगकर्ता वास्तव में एक विंग ==> का चयन करता है बूम कायह विधि किसी भी प्रकार के परीक्षण को पूरी तरह से तोड़ देती है।


विधि 2:
संक्षेप में: "महान" विचार (चेतावनी - व्यंग्य), चलो विधि 3 के नुकसान (3 की धीमी गति जब कई प्रविष्टियाँ) विधि के बल्कि भयानक नुकसान के साथ गठबंधन करते हैं
। इस विधि का एकमात्र लाभ यह है कि आप रखते हैं एक तालिका में सभी अनुवाद, और इसलिए रखरखाव को सरल बनाते हैं। हालाँकि, एक ही चीज़ को विधि 1 और एक गतिशील SQL संग्रहीत कार्यविधि के साथ प्राप्त किया जा सकता है, और एक (संभवत: अस्थायी) तालिका जिसमें अनुवाद हैं, और लक्ष्य तालिका का नाम (और यह बहुत ही सरल है कि आपने अपने सभी पाठ-क्षेत्रों को नाम दिया है वही)।


विधि 3:
सभी अनुवादों के लिए एक तालिका: नुकसान: आपको एन फ़ील्ड के लिए जिन उत्पादों का आप अनुवाद करना चाहते हैं, उनके लिए n विदेशी कुंजी को स्टोर करना होगा। इसलिए, आपको एन फ़ील्ड के लिए एन जॉइन करना होगा। जब अनुवाद तालिका वैश्विक होती है, तो इसमें बहुत सी प्रविष्टियाँ होती हैं, और जोड़ धीमी हो जाती हैं। इसके अलावा, आपको हमेशा N_ फ़ील्ड के लिए T_TRANSLATION तालिका में शामिल होना होगा। यह काफी ओवरहेड है। अब, जब आप प्रति ग्राहक कस्टम अनुवाद समायोजित करना चाहते हैं तो आप क्या करते हैं? आपको अतिरिक्त तालिका पर एक और 2x n जुड़ना होगा। यदि आपको जुड़ना है, तो 2x2xn = 4n अतिरिक्त जोड़ के साथ 10 तालिकाओं को कहें, क्या गड़बड़ है! इसके अलावा, यह डिज़ाइन 2 टेबल के साथ एक ही अनुवाद का उपयोग करना संभव बनाता है। यदि मैं एक तालिका में आइटम का नाम बदलता हूं, तो क्या मैं वास्तव में किसी अन्य तालिका में प्रविष्टि को हर बार बदलना चाहता हूं?

इसके अलावा, आप तालिका को हटा नहीं सकते हैं और फिर से नहीं डाल सकते हैं, क्योंकि अब उत्पाद तालिका में विदेशी कुंजी हैं ... आप निश्चित रूप से FKs सेट <insert name of your "favourite" person here>कर सकते हैं, और फिर तालिका हटा सकते हैं, और फिर से सम्मिलित कर सकते हैं सभी प्रविष्टियाँ newid () [या इन्सर्ट में id निर्दिष्ट करके, लेकिन आइडेंट-इन्सर्ट OFF ], और वह (और होगा) डेटा-कचरा (और अशक्त-संदर्भ अपवाद) को वास्तव में जल्द ही ले जाएगा।


विधि 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 की तरह, सिवाय इसके कि XML को पार्स करने के ओवरहेड के कारण (PG hstore में एक साहचर्य सरणी से प्रविष्टि को पढ़ने के बजाय) यह बहुत धीमा हो जाता है और xml एन्कोडिंग उपयोगी होने के लिए बहुत दर्दनाक बनाता है।


विधि 5 (SunWuKung द्वारा अनुशंसित, जिसे आपको चुनना चाहिए): प्रत्येक "उत्पाद" तालिका के लिए एक अनुवाद तालिका। इसका मतलब है कि प्रति पंक्ति एक पंक्ति, और कई "टेक्स्ट" फ़ील्ड, इसलिए इसे एन फ़ील्ड पर केवल एक (बाएं) की आवश्यकता होती है। फिर आप आसानी से "उत्पाद" में एक डिफ़ॉल्ट-फ़ील्ड जोड़ सकते हैं -इस प्रकार, आप अनुवाद तालिका को आसानी से हटा सकते हैं और फिर से डाल सकते हैं, और आप कस्टम-अनुवाद (मांग पर) के लिए दूसरी तालिका बना सकते हैं, जिसे आप हटा भी सकते हैं और फिर से डालें), और आपके पास अभी भी सभी विदेशी कुंजी हैं।

आइए इस कार्य को देखने के लिए एक उदाहरण बनाएं:

सबसे पहले, टेबल बनाएं:

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' इत्यादि) का भी उपयोग कर सकते हैं, तो आपको भाषा आईडी देखने की आवश्यकता नहीं है। लेकिन यदि आप ऐसा करते हैं, तो आप शायद इसके बजाय IETF- भाषा टैग का उपयोग करना चाहते हैं , जो बेहतर है, क्योंकि आपको डी-सीएच और डी-डीई मिलता है, जो वास्तव में एक ही ऑर्टोग्राफी-वार नहीं है (हर जगह of के बजाय डबल एस) , हालांकि यह वही आधार-भाषा है। यह सिर्फ एक छोटा सा विवरण है जो आपके लिए महत्वपूर्ण हो सकता है, विशेष रूप से यह देखते हुए कि en-US और en-GB / en-CA / en-AU या fr-FR / fr-CA में समान मुद्दे हैं।
Quote: हमें इसकी आवश्यकता नहीं है, हम केवल अपना सॉफ्टवेयर अंग्रेजी में करते हैं।
उत्तर: हां - लेकिन कौन सा ??

वैसे भी, यदि आप एक पूर्णांक आईडी का उपयोग करते हैं, तो आप लचीले हैं, और किसी भी समय अपना तरीका बदल सकते हैं।
और आपको उस पूर्णांक का उपयोग करना चाहिए, क्योंकि एक बॉटेड डीबी डिज़ाइन की तुलना में अधिक कष्टप्रद, विनाशकारी और परेशानी नहीं है।

RFC 5646 , ISO 639-2 , भी देखें

और, यदि आप अभी भी "हम" केवल "केवल एक संस्कृति" के लिए अपना आवेदन करते हैं (जैसे एन-यूएस आमतौर पर) - इसलिए मुझे उस अतिरिक्त पूर्णांक की आवश्यकता नहीं है, यह एक अच्छा समय और जगह का उल्लेख करने के लिए होगा 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और कोई विदेशी कुंजी उल्लंघन न करें।

टक्कर के लिए, मैं इसे इस तरह से करूँगा:

ए) अपने खुद के दाल
बी) भाषा तालिका में वांछित कोलाज़ नाम सहेजें

आप कोलाज को अपनी तालिका में रखना चाहते हैं, उदाहरण के लिए:

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

ग) क्या आपके पास स्थितीकरण नाम उपलब्ध है

डी) अपनी एसक्यूएल को इस तरह लिखें:

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

ORDER BY GroupName COLLATE {#COLLATION}

ई) तब, आप इसे अपने DAL में कर सकते हैं:

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

जो तब आपको यह पूरी तरह से रचित SQL-Query देगा

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 (जिसे आपने इतनी जल्दी "उखाड़ दिया" :) मामूली संशोधन के साथ एक बेहतर विकल्प हो सकता है जो प्रत्येक स्थानीयकृत कॉलम के लिए टारगेट कोलेशन को दर्शाता है।
यूजीन एव्डोकिमोव

2
@ यूजीन एव्डोकिमोव: हाँ, लेकिन "ORDER BY" हमेशा एक समस्या होने वाली है, क्योंकि आप इसे एक चर के रूप में निर्दिष्ट नहीं कर सकते। मेरा दृष्टिकोण भाषा तालिका में टकराव के नाम को बचाने के लिए होगा, और userinfo में यह होगा। फिर, प्रत्येक एसक्यूएल-स्टेटमेंट पर आप ORDER BY COLUMN_NAME {#collation} कह सकते हैं, और फिर आप अपनी दाल (cmd.CommandText = cmd.CommandText.Replace) ("{# COLLATION}", schem.user में एक रिप्लेसमेंट कर सकते हैं। Language.collation)। वैकल्पिक रूप से, आप अपने एप्लिकेशन कोड में सॉर्ट कर सकते हैं, जैसे कि LINQ का उपयोग करना। यह आपके डेटाबेस से कुछ प्रोसेसिंग भी लेगा। रिपोर्ट के लिए, रिपोर्ट वैसे भी होती है।
स्टीफन स्टीगर

oo यह सबसे लंबा SO उत्तर होना चाहिए जो मैंने देखा है, और मैंने देखा कि लोग उत्तर में पूरे कार्यक्रम बनाते हैं। तुम अच्छे हो।
डोमिनोज

पूरी तरह से SunWuKung के समाधान सहमत कर सकते हैं सबसे अच्छा है
Domi

48

तीसरा विकल्प कुछ कारणों से सबसे अच्छा है:

  • नई भाषाओं के लिए डेटाबेस स्कीमा को बदलने की आवश्यकता नहीं है (और इस प्रकार कोड परिवर्तनों को सीमित करना)
  • किसी विशेष आइटम के अनइम्प्लीमेंटेड भाषाओं या अनुवादों के लिए बहुत अधिक स्थान की आवश्यकता नहीं होती है
  • सबसे अधिक लचीलापन प्रदान करता है
  • आप विरल तालिकाओं के साथ समाप्त नहीं होते हैं
  • आपको अशक्त कुंजियों और जाँच के बारे में चिंता करने की ज़रूरत नहीं है कि आप कुछ अशक्त प्रविष्टि के बजाय एक मौजूदा अनुवाद प्रदर्शित कर रहे हैं।
  • यदि आप अपने डेटाबेस को बदलकर या अन्य अनुवाद योग्य वस्तुओं / चीजों / चीजों को शामिल करने के लिए विस्तार करते हैं, तो आप एक ही टेबल और सिस्टम का उपयोग कर सकते हैं - यह बाकी डेटा से बहुत अधिक नहीं है।

-Adam


1
मैं सहमत हूं, हालांकि व्यक्तिगत रूप से मेरे पास प्रत्येक मुख्य तालिका के लिए एक स्थानीयकृत तालिका होगी, ताकि विदेशी कुंजियों को लागू किया जा सके।
नील बर्नवेल 11

1
यद्यपि तीसरा विकल्प समस्या का सबसे स्वच्छ और ध्वनि कार्यान्वयन है, लेकिन यह पहले अधिक जटिल है। मुझे लगता है कि सामान्य संस्करण को प्रदर्शित करने, संपादित करने, रिपोर्टिंग करने के लिए इतना अतिरिक्त प्रयास करने की आवश्यकता है कि यह हमेशा स्वीकार्य न हो। मैंने दोनों समाधानों को लागू किया है, सरल तब पर्याप्त था जब उपयोगकर्ताओं को "मुख्य" अनुप्रयोग भाषा के केवल पढ़ने के लिए (कभी-कभी गायब) अनुवाद की आवश्यकता होती थी।
rics

12
क्या होगा यदि उत्पाद तालिका में कई अनुवादित फ़ील्ड शामिल हैं? उत्पादों को पुनः प्राप्त करते समय, आपको प्रति अनुवादित फ़ील्ड में एक अतिरिक्त जुड़ाव करना होगा, जिसके परिणामस्वरूप गंभीर प्रदर्शन समस्याएँ होंगी। सम्मिलित / अद्यतन / हटाने के लिए (IMO) अतिरिक्त जटिलता भी है। इसका एकल लाभ तालिकाओं की कम संख्या है। मैं SunWuKung द्वारा प्रस्तावित विधि के लिए जाऊंगा: मुझे लगता है कि यह प्रदर्शन, जटिलता और रखरखाव के मुद्दों के बीच एक अच्छा संतुलन है।
फ्रॉस्टी जेड

@ rics- मैं सहमत हूँ, अच्छी तरह से आप क्या सुझाव देते हैं ...?
कृपाण

@ एडम- मैं उलझन में हूं, शायद मुझे गलतफहमी हुई। आपने तीसरे को सुझाव दिया, है ना? कृपया इसे और अधिक विस्तार से बताएं कि उन तालिकाओं के बीच संबंध कैसे होने वाले हैं? आपका मतलब है कि हमें डीबी में प्रत्येक टेबल के लिए अनुवाद और ट्रांसलेशनइंट्री टेबल को लागू करना होगा?
कृपाण 4

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)?
प्रबुद्ध

क्या आपके पास आपके नमूने का कहीं एक जीवंत उदाहरण है? क्या इसके इस्तेमाल से आपको कोई समस्या हुई?
डेविड लेटरन्यू

निश्चित रूप से, मेरे पास बहुभाषी अचल संपत्ति परियोजना है, हम 4 भाषाओं का समर्थन करते हैं। खोज थोड़ी जटिल है, लेकिन इसकी तेजी है। बेशक बड़ी परियोजनाओं में यह धीमी गति से हो सकता है जितना कि यह होना चाहिए। छोटी या मध्यम परियोजनाओं में इसकी ठीक है।
बाम्बुरिक

8

मैं आमतौर पर इस दृष्टिकोण के लिए जाता हूं (वास्तविक एसक्यूएल नहीं), यह आपके अंतिम विकल्प के साथ मेल खाता है।

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
Translationतालिका या TranslationItem.translationitemidस्तंभ किस उद्देश्य से कार्य करता है ?
दानमेन

4

तकनीकी विवरण और समाधान पर जाने से पहले, आपको एक मिनट के लिए रुकना चाहिए और आवश्यकताओं के बारे में कुछ प्रश्न पूछना चाहिए। उत्तर तकनीकी समाधान पर भारी प्रभाव डाल सकते हैं। ऐसे प्रश्नों के उदाहरण होंगे:
- क्या सभी भाषाओं का उपयोग हर समय किया जाएगा?
- कौन और कब विभिन्न भाषा संस्करणों के साथ कॉलम भरेंगे?
- क्या होता है जब उपयोगकर्ता को किसी पाठ की एक निश्चित भाषा की आवश्यकता होगी और सिस्टम में कोई नहीं है?
- केवल ग्रंथों को स्थानीयकृत किया जाना है या अन्य वस्तुएं भी हैं (उदाहरण के लिए PRICE को $ और € में संग्रहीत किया जा सकता है क्योंकि वे अधिक हो सकते हैं)


मुझे पता है कि स्थानीयकरण एक बहुत व्यापक विषय है और मुझे उन मुद्दों के बारे में पता है जो आप मेरे ध्यान में लाते हैं, लेकिन वर्तमान में मैं स्कीमा डिज़ाइन की एक बहुत ही विशिष्ट समस्या के लिए एक उत्तर की तलाश कर रहा हूं। मेरा मानना ​​है कि नई भाषाओं को वृहद रूप से जोड़ा जाएगा और प्रत्येक का लगभग पूरी तरह से अनुवाद किया जाएगा।
क्यूबेक

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'

क्या आप केवल तालिका का अनुवाद नहीं कर सकते हैं ताकि आपको यह मिल जाए:

    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फिर जैसे कहूंगा । अधिक मायने रखता है।
डैनमैन

1

मैं रैंडमाइज़र से सहमत हूँ। मैं यह नहीं देखता कि आपको टेबल "अनुवाद" की आवश्यकता क्यों है।

मुझे लगता है, यह पर्याप्त है:

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

1

क्या नीचे का दृष्टिकोण व्यवहार्य होगा? मान लें कि आपके पास ऐसी तालिकाएँ हैं जहाँ 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 भाषाओं का समर्थन करता है, और यह नहीं बढ़ेगा, तो आप इसका उपयोग कर सकते हैं।

दूसरा ओके है, लेकिन समझना और बनाए रखना कठिन है। और प्रदर्शन पहले की तुलना में खराब है।

अंतिम स्केलेबिलिटी में अच्छा है लेकिन प्रदर्शन में बुरा है। T_TRANSLATION_ENTRY तालिका बड़ी और बड़ी हो जाएगी, यह भयानक है जब आप कुछ तालिकाओं से संस्थाओं की सूची प्राप्त करना चाहते हैं।


0

यह दस्तावेज़ संभावित समाधान और प्रत्येक विधि के फायदे और नुकसान का वर्णन करता है। मैं "पंक्ति स्थानीयकरण" पसंद करता हूं क्योंकि आपको नई भाषा जोड़ने पर DB स्कीमा को संशोधित करने की आवश्यकता नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.