다국어 데이터베이스의 스키마


235

다국어 소프트웨어를 개발 중입니다. 응용 프로그램 코드가 진행되는 한 지역화 가능성은 문제가되지 않습니다. 언어 별 리소스를 사용할 수 있고 모든 종류의 도구를 사용할 수 있습니다.

그러나 다국어 데이터베이스 스키마를 정의 할 때 가장 좋은 방법은 무엇입니까? 테이블이 많고 (100 이상) 각 테이블에 현지화 할 수있는 여러 열이있을 수 있습니다 (대부분의 nvarchar 열은 지역화 가능해야 함). 예를 들어, 테이블 중 하나에 제품 정보가있을 수 있습니다.

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을 코멘트를 읽는 것은 매우 유용하지만
파리 드 자카 Alnamrouti

3
LANGUAGE_CODE자연적인 열쇠는 피하십시오 LANGUAGE_ID.
gavenkoa

1
나는 이미 2와 3을 보았거나 사용했지만, 나는 그들을 권장하지 않으며, 당신은 쉽게 고아 행으로 끝납니다. @SunWiKung 디자인은 더 나은 IMO로 보입니다.
Guillaume86

4
우연히 구현 한 SunWuKungs 디자인을 선호합니다. 그러나 데이터 정렬을 고려해야합니다. Sql Server에서 각 열에는 데이터 정렬 속성이 있으며 대 / 소문자 구분, 악센트 문자의 동등성 또는 기타 언어 별 고려 사항을 결정합니다. 언어 별 데이터 정렬 사용 여부는 전체 응용 프로그램 디자인에 따라 다르지만 잘못 지정하면 나중에 변경하기가 어렵습니다. 언어 별 데이터 정렬이 필요한 경우 언어 당 행이 아니라 언어 당 열이 필요합니다.
Elroy Flynn 2019

답변:


113

번역 할 수있는 각 테이블에 대해 관련 변환 테이블이 있다고 생각하십니까?

CREATE TABLE T_PRODUCT (pr_id int, PRICE NUMBER (18, 2))

CREATE TABLE T_PRODUCT_tr (pr_id INT FK, 언어 코드 varchar, pr_name 텍스트, pr_descr 텍스트)

이렇게하면 번역 가능한 열이 여러 개인 경우 translationid를 자동 생성하지 않으므로 관련 번역과 함께 항목을 가져 오는 것이 더 쉬울 수 있으므로 하나의 조인 만 있으면됩니다.

이에 대한 부정적인 측면은 복잡한 언어 대체 메커니즘이있는 경우이를 위해 일부 저장 프로 시저에 의존하는 경우 각 변환 테이블에 대해이를 구현해야 할 수도 있다는 것입니다. 앱에서 그렇게하면 아마도 문제가되지 않을 것입니다.

당신의 생각을 알려주십시오-나는 또한 다음 신청을 위해 이것에 대해 결정하려고합니다. 지금까지 우리는 세 번째 유형을 사용했습니다.


2
이 옵션은 내 옵션 nr 1과 유사하지만 더 좋습니다. 여전히 유지 관리가 어렵고 새로운 언어에 대한 새 테이블을 만들어야하므로 구현을 꺼려합니다.
qbeuek

28
새 언어에 대한 새 테이블이 필요하지 않습니다. 새 언어를 사용하여 해당 _tr 테이블에 새 행을 추가하기 만하면, 새 번역 테이블을 작성하는 경우 새 _tr 테이블 만 작성하면됩니다

3
나는 이것이 좋은 방법이라고 믿습니다. 다른 방법에는 많은 수의 왼쪽 조인이 필요하며 여러 테이블을 조인하는 경우 각 테이블에 3 수준의 깊이와 같은 변환이 있고 각 필드에 3 개의 필드가 있으면 번역에 대해서만 3 * 3 9 개의 왼쪽 조인이 필요합니다. 제약 조건 등을 추가하는 것이 더 쉽고 검색이 더 공명 적이라고 생각합니다.
GorillaApe

1
T_PRODUCT1 개 백만 행이, T_PRODUCT_tr2는 훨씬 SQL 효율성을 감소 million.Would있을 것입니다?
Mithril

1
@Mithril 어느 쪽이든 2 백만 행이 있습니다. 최소한이 방법으로 조인 할 필요는 없습니다.
David D

56

이것은 흥미로운 문제이므로, 괴롭히자.

방법 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 새 테이블이 만들어지기 때문에 스키마가 이전 하나가 남아 있지만, 참조 필드는 이름, 당신은 경고의 어떤 종류를하지 않습니다. 날개 매개 변수 (==> guid.empty) 를 선택하지 않고 보고서 를 실행할 때도 보고서가 작동합니다 . 그러나 갑자기 실제 사용자가 실제로 날개를 선택하면 ==> . 이 방법은 모든 종류의 테스트를 완전히 중단합니다.


방법 2 :
간단히 말해서 : "훌륭한"아이디어 (경고-풍자), 방법 3의 단점 (많은 항목이있을 때 느린 속도)을 방법 1
의 다소 끔찍한 단점과 결합 해 봅시다. 이 방법 의 유일한 장점은 하나의 테이블로 모든 번역을 수행하므로 유지 관리가 간단합니다. 그러나 방법 1과 동적 SQL 저장 프로 시저, 그리고 번역을 포함하는 (일시적인) 테이블 및 대상 테이블의 이름을 사용하여 동일한 작업을 수행 할 수 있습니다 (모든 텍스트 필드의 이름을 가정하면 매우 간단합니다). 같은).


방법 3 :
모든 번역에 대한 하나의 테이블 : 단점 : 번역하려는 n 개의 필드에 대해 제품 테이블에 n 개의 외래 키를 저장해야합니다. 따라서 n 개의 필드에 대해 n 개의 조인을 수행해야합니다. 변환 표가 전역 테이블이면 많은 항목이 있으며 결합이 느려집니다. 또한 항상 n 개의 필드에 대해 T_TRANSLATION 테이블을 n 번 조인해야합니다. 이것은 상당히 오버 헤드입니다. 이제 고객 당 사용자 정의 번역을 수용해야 할 경우 어떻게해야합니까? 추가 테이블에 다른 2x n 조인을 추가해야합니다. 2x2xn = 4n 추가 조인으로 10 개의 테이블을 조인 해야하는 경우 얼마나 엉망입니까! 또한이 디자인을 사용하면 2 개의 테이블에서 동일한 변환을 사용할 수 있습니다. 한 테이블에서 항목 이름을 변경하면 매번 다른 테이블에서도 항목을 변경하고 싶습니까?

또한 이제 제품 테이블에 외래 키가 있으므로 더 이상 테이블을 삭제하고 다시 삽입 할 수 없습니다 ... 물론 FK 설정을 생략 한 다음 <insert name of your "favourite" person here>테이블을 삭제하고 다시 삽입 할 수 있습니다 newid () 또는 삽입에서 ID를 지정하지만 identity-insert가 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로 값을 얻을 수 있습니다. 여기서 string-variable을

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 + '/...'

PG hstore의 연관 배열에서 항목을 읽는 대신 XML 구문 분석의 오버 헤드로 인해 XML이 너무 느려져 XML 인코딩이 너무 어려워서 PostGre hstore와 같은 종류가 유용합니다.


방법 5 (SunWuKung에서 권장하는 방법 중 하나) : 각 "제품"테이블 당 하나의 변환 표. 이는 언어 당 하나의 행과 여러 개의 "텍스트"필드를 의미하므로 N 필드에는 하나의 (왼쪽) 조인 만 필요합니다. 그런 다음 "제품"테이블에 기본 필드를 쉽게 추가하고 변환 테이블을 쉽게 삭제하고 다시 삽입 할 수 있으며 사용자 정의 번역 (요청시)을위한 두 번째 테이블을 만들 수도 있습니다. 다시 삽입)하고 여전히 모든 외래 키가 있습니다.

이 작품을 볼 수있는 예를 만들어 봅시다.

먼저 테이블을 작성하십시오.

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를 얻으므로 더 좋습니다. 동일한 기본 언어이지만. 특히 en-US 및 en-GB / en-CA / en-AU 또는 fr-FR / fr-CA에 비슷한 문제가 있다는 점을 고려할 때 매우 중요합니다.
견적 : 우리는 그것을 필요로하지 않으며 소프트웨어는 영어로만합니다.
답 : 예-그러나 어느 것 ??

어쨌든 정수 ID를 사용하면 유연하고 나중에 메소드를 변경할 수 있습니다.
그리고 그 정수를 사용해야합니다. 왜냐하면 botched Db 디자인보다 더 성 가시고 파괴적이며 번거로운 것이 없기 때문입니다.

참조 RFC 5646 , ISO 639-2을 ,

그리고, 당신은 여전히 "우리가"말을하는 경우 에만 "오직 우리의 응용 프로그램을 하나 개 (보통 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

좋은 자세한 답변, 많은 감사합니다. 그러나 방법 5 솔루션의 데이터 정렬 문제에 대해 어떻게 생각하십니까? 다국어 환경에서 번역 된 텍스트를 다른 데이터 정렬로 정렬하거나 필터링해야 할 때 이것이 가장 좋은 방법은 아닌 것 같습니다. 이러한 경우 방법 2 (귀하가 너무 빨리 "추방 된")는 현지화 된 각 열의 대상 데이터 정렬을 나타내는 약간의 수정으로 더 나은 옵션이 될 수 있습니다.
Eugene Evdokimov

2
@Eugene Evdokimov : 예. 그러나 변수로 지정할 수 없기 때문에 "ORDER BY"는 항상 문제가됩니다. 내 접근 방식은 데이터 정렬 이름을 언어 테이블에 저장하고 userinfo에 저장하는 것입니다. 그런 다음 각 SQL 문에서 ORDER BY COLUMN_NAME {#collation}을 말할 수 있으며 dal (cmd.CommandText = cmd.CommandText.Replace ( "{# COLLATION}", auth.user)에서 바꿀 수 있습니다. . language.collation) 양자 택일로, 당신은 LINQ를 사용하여 예를 들어, 응용 프로그램 코드에서 정렬 할 수 있습니다 이것은 또한 데이터베이스 떨어져 약간의 처리 부하가 걸릴 것이라고 보고서의 경우 보고서 종류 어쨌든...
스테판 스타 이거

oo 이것은 내가 본 가장 긴 SO 답변이어야하며 사람들이 전체 프로그램을 답변으로 만드는 것을 보았습니다. 잘 하네
Domino

SunWuKung의 솔루션이 최고임을 전적으로 동의 할 수 있습니다
Domi

48

세 번째 옵션은 몇 가지 이유로 가장 좋습니다.

  • 새로운 언어에 대한 데이터베이스 스키마를 변경할 필요가 없으므로 코드 변경이 제한됩니다.
  • 구현되지 않은 언어 또는 특정 항목의 번역을 위해 많은 공간이 필요하지 않습니다.
  • 최고의 유연성 제공
  • 스파 스 테이블로 끝나지 않습니다.
  • null 키에 대해 걱정할 필요가 없으며 일부 null 항목 대신 기존 번역이 표시되는지 확인할 필요가 없습니다.
  • 다른 번역 가능한 항목 / 사물 등을 포함하도록 데이터베이스를 변경하거나 확장하는 경우 동일한 테이블과 시스템을 사용할 수 있습니다. 이는 나머지 데이터와 매우 관련이 없습니다.

-아담


1
개인적으로 각 외래 키를 구현할 수 있도록 각 기본 테이블에 대해 현지화 된 테이블이 있다는 데 동의합니다.
닐 반웰

1
세 번째 옵션은 문제의 가장 깨끗하고 건전한 구현이지만 첫 번째 옵션보다 더 복잡합니다. 일반 버전을 표시, 편집,보고하는 것은 많은 노력이 필요하다고 생각합니다. 두 솔루션을 모두 구현했는데, 사용자가 "주"응용 프로그램 언어의 읽기 전용 (때로는 누락 된) 번역이 필요할 때 더 간단했습니다.
rics

12
제품 테이블에 번역 된 필드가 여러 개 있으면 어떻게됩니까? 제품을 검색 할 때 변환 된 필드 당 하나의 추가 조인을 수행해야하므로 심각한 성능 문제가 발생합니다. 삽입 / 업데이트 / 삭제에는 추가적인 복잡성이 있습니다 (IMO). 이것의 유일한 장점은 적은 수의 테이블입니다. SunWuKung이 제안한 방법을 사용하려고합니다. 성능, 복잡성 및 유지 관리 문제 사이의 균형이 적절하다고 생각합니다.
Frosty Z

@ rics- 동의합니다. 글쎄, 당신은 무엇을 제안합니까 ...?
saber

@ 아담-나는 혼란 스러울 수 있습니다. 당신은 세 번째를 제안 했습니까? 이 테이블들 사이의 관계는 어떻게 될까요? DB의 각 테이블에 대해 Translation 및 TranslationEntry 테이블을 구현해야 함을 의미합니까?
saber

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

샘플 어딘가에 실제 예제가 있습니까? 그것을 사용하여 문제가 발생 했습니까?
David Létourneau

물론, 나는 다국어 부동산 프로젝트를 가지고 있으며, 우리는 4 개 언어를 지원합니다. 검색은 약간 복잡하지만 빠릅니다. 물론 큰 프로젝트에서는 필요 이상으로 느려질 수 있습니다. 중소 프로젝트에서 괜찮습니다.
bamburik

8

나는 일반적 으로이 접근법 (실제 SQL이 아님)을 원할 것입니다. 이것은 마지막 옵션과 일치합니다.

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칼럼 은 어떤 용도로 사용됩니까?
DanMan

4

기술적 인 세부 사항 및 솔루션으로 이동하기 전에 잠시 멈추고 요구 사항에 대해 몇 가지 질문을해야합니다. 답변은 기술 솔루션에 큰 영향을 줄 수 있습니다. 이러한 질문의 예는 다음과 같습니다
.-모든 언어가 항상 사용됩니까?
-언제 어떤 언어 버전으로 열을 채울까요?
-사용자에게 텍스트의 특정 언어가 필요하고 시스템에없는 경우 어떻게됩니까?
-텍스트 만 현지화하거나 다른 항목도 있습니다 (예 : PRICE는 다를 수 있으므로 $와 €에 저장할 수 있음)


현지화가 훨씬 광범위한 주제라는 것을 알고 있으며, 내가 주목해야 할 문제를 알고 있지만 현재는 스키마 설계의 매우 구체적인 문제에 대한 답을 찾고 있습니다. 나는 새로운 언어가 점진적으로 추가 될 것이고 각각의 언어는 거의 완전히 번역 될 것이라고 가정한다.
qbeuek

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생각을. 더 의미가 있습니다.
DanMan

1

나는 randomizer에 동의합니다. 왜 "번역"테이블이 필요한지 모르겠습니다.

나는 이것으로 충분하다고 생각한다.

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

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

"최고의 것"은 프로젝트 상황에 근거합니다. 첫 번째는 선택 및 유지 관리가 쉽고 엔티티를 선택할 때 테이블을 조인 할 필요가 없으므로 성능이 가장 좋습니다. 당신의 poject가 단지 2 개 또는 3 개의 언어 만 지원한다는 것을 확인했고, 증가하지 않을 경우, 그것을 사용할 수 있습니다.

두 번째는 괜찮지 만 이해하고 유지하기가 어렵습니다. 그리고 성능은 첫 번째보다 나쁩니다.

마지막은 확장성에는 좋지만 성능에는 좋지 않습니다. T_TRANSLATION_ENTRY 테이블이 점점 커지고 일부 테이블에서 엔티티 목록을 검색하려고 할 때 끔찍합니다.


0

문서 는 가능한 솔루션과 각 방법의 장단점에 대해 설명합니다. 새 언어를 추가 할 때 DB 스키마를 수정할 필요가 없기 때문에 "행 지역화"를 선호합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.