多语言数据库的模式

我正在开发一个多语言软件。就应用程序代码而言,本地化不是问题。我们可以使用特定于语言的资源,并拥有各种与之配合良好的工具。

但是,定义多语言数据库模式的最佳方法是什么?假设我们有很多表(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. Translation table with columns for each language

    Instead of storing translated text, only a foreign key to the translations table is stored. The translations table contains a column for each language.

    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. Translation tables with rows for each language

    Instead of storing translated text, only a foreign key to the translations table is stored. The translations table contains only a key, and a separate table contains a row for each translation to a language.

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

There are pros and cons to each solution, and I would like to know what are your experiences with these approaches, what do you recommend and how would you go about designing a multilanguage database schema.

111557 次浏览

第三种选择是最好的,原因如下:

  • 不需要为新语言更改数据库模式(从而限制代码更改)
  • 不需要为未实现的语言或特定项目的翻译占用大量空间
  • 提供最大的灵活性
  • 你最终不会得到稀疏表
  • 您不必担心空键,也不必检查您显示的是一个现有的翻译,而不是某个空条目。
  • 如果您更改或扩展数据库以包含其他可翻译的项目/事物/等等,您可以使用相同的表和系统-这与其他数据非常不耦合。

亚当

我通常会选择这种方法(不是实际的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'

因为将所有可翻译文本放在一个地方可以使维护工作更加容易。有时翻译外包给翻译机构,这样你就可以给他们发送一个大的导出文件,然后很容易地导入回来。

对于每个可翻译表都有一个相关的翻译表,您怎么看?

创建表T_PRODUCT (pr_id int, PRICE NUMBER(18,2))

创建表T_PRODUCT_tr (pr_id INT FK,语言码varchar, pr_name文本,pr_descr文本)

通过这种方式,如果你有多个可翻译的列,它只需要一个连接就可以得到它+因为你没有自动生成一个translationid,它可能更容易导入项目及其相关的翻译。

这样做的负面影响是,如果您有一个复杂的语言回退机制,您可能需要为每个翻译表实现该机制——如果您依赖于某个存储过程来实现该机制的话。如果你从应用程序中这样做,这可能不是一个问题。

让我知道你的想法-我也即将为我们的下一个申请做出决定。 到目前为止,我们使用的是你的第三种类型
我在寻找一些本地化的技巧,发现了这个主题。 我想知道为什么使用这个:

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'

你能不能把Translation表去掉,这样你就得到了这个:

    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'

我同意随机发生器。我不明白你为什么需要一个表“翻译”。

我想,这就足够了:

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

“哪一个是最好的”是基于项目的情况。第一种方法易于选择和维护,并且在选择实体时不需要连接表,性能最好。如果你确认你的项目只支持2或3种语言,并且不会增加,你可以使用它。

第二个是可以的,但是很难理解和维护。而且表现比第一个还差。

最后一种方法在可伸缩性方面很好,但在性能方面很差。T_TRANSLATION_ENTRY表将变得越来越大,当你想从一些表中检索一个实体列表时,这是很糟糕的。

看看这个例子:

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

我认为没有必要解释,结构本身就说明了这一点。

这是一个有趣的问题,让我们来看看死灵传说。

让我们从方法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>因为翅膀的需求突然改变而改变了模式,并且创建了一个新表,旧表保留,但引用字段重命名,你不会得到任何类型的警告。一个报告甚至可以工作当你运行它时没有选择机翼参数 (==>guid.empty)。但突然间,当一个实际用户选择了一个翅膀==>繁荣这种方法完全打破了任何类型的测试。 < / p >

方法二:
简而言之:“伟大”;想法(警告-讽刺),让我们结合方法3的缺点(大量输入时速度较慢)和方法1相当可怕的缺点。
这种方法的唯一优点是将所有翻译保存在一个表中,因此维护起来很简单。然而,同样的事情也可以通过方法1和一个动态SQL存储过程、一个包含翻译的表(可能是临时的)和目标表的名称(假设所有文本字段的名称都相同,这很简单)来实现


方法三:
一个表用于所有翻译: 劣势: 您必须在产品表中存储n个外键,用于您想要转换的n个字段。 因此,你必须对n个字段做n个连接。 当翻译表是全局的时,它有很多条目,连接变得很慢。 同样,你总是需要为n个字段加入T_TRANSLATION表n次。 这是一笔不小的开销。 现在,如果必须为每个客户提供定制翻译,该怎么办? 你必须在一个额外的表上添加另外2x n个连接。 如果您必须连接10个表,使用2x2xn = 4n个附加连接,那将会非常混乱! 此外,这种设计可以对两个表使用相同的翻译。 如果我改变了一个表中的条目名称,我真的想每次都改变另一个表中的条目吗

另外,你不能再删除和重新插入表,因为现在在产品表中有外键…当然,你可以省略设置fk,然后<insert name of your "favourite" person here>可以删除表,并重新插入所有带有newid ()[或在插入中指定id,但带有identity-insert掉]的条目,这将(并将)很快导致数据垃圾(和空引用异常)。


方法四(未列出): 将所有语言存储在数据库中的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

哪里可以用'.../' + @in_language + '/...'替换/lang/de/...

有点像PostGre hstore,除了由于解析XML的开销(而不是从PG hstore中的关联数组中读取条目),它变得非常缓慢,加上XML编码使它太痛苦而没有用。


方法五(这是孙悟空的建议,你应该选择): 每个“Product”表对应一个翻译表。 这意味着每种语言有一行,以及几个“文本”字段,因此它只需要对N个字段进行一个(左)连接。 然后,您可以轻松地在“Product”表中添加一个默认字段,可以轻松地删除和重新插入翻译表,还可以为自定义翻译(按需)创建第二个表,也可以删除和重新插入),并且仍然拥有所有的外键。

让我们举个例子来看看这是有效的:

首先,创建表:

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-language标签,这是更好的,因为你得到de-CH和de-DE,这确实不是相同的字型(双s而不是处处ß),尽管它是相同的基本语言。这只是一个可能对你很重要的小细节,特别是考虑到en-US和en-GB/en-CA/en-AU或fr-FR/fr-CA有类似的问题。我们不需要英语,我们只用英语做软件。答案:是的,但是哪一个??

无论如何,如果你使用整数ID,你是灵活的,并且可以在任何时候改变你的方法。
你应该使用这个整数,因为没有什么比一个拙劣的Db设计更烦人、更具破坏性和更麻烦的了

另见RFC 5646ISO 639 - 2

如果你还在说“我们”;只有使我们的申请“仅为一个文化”;(就像通常的en-US)-因此我不需要额外的整数,这将是一个很好的时间和地点提到IANA语言标签,不是吗?
因为它们是这样的:

de-DE-1901
de-DE-1996

而且

de-CH-1901
de-CH-1996

(1996年进行了正字法改革…) 试着在字典里找一个拼错的单词;这在处理法律和公共服务门户的应用程序中变得非常重要。
更重要的是,有些地区正在从西里尔字母转变为拉丁字母,这可能比一些模糊的正字法改革的表面麻烦更麻烦,这就是为什么这可能也是一个重要的考虑因素,这取决于你生活在哪个国家。不管怎样,这里最好有那个整数,以防万一。

< p > 编辑:
通过在

后添加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-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

您需要记住,在创建多语言数据库时,您正在从产品表中删除名称或描述等字段,并将其移动到已翻译的资源中。

翻译后的资源可以是另一个表,就像我的例子一样,它被设计成与SQL视图一起工作,以简化查询和友好地开发底层应用程序

多语言数据库

我分离了LabelTranslations,因为这是一个网页上字段标签的全局翻译表。你可以随意称呼它,它们是无状态的,不依赖于特定的产品或类别。

ProductTranslations的CategoryTranslations是有状态的,这意味着“;name”的描述符;将是实际的产品名称。

与简单的SQL视图相比,使用物化视图可以获得更好的性能(使用存储空间的成本和更多的精力用于底层应用程序开发来刷新它们),或者如果你想要使用更重的SQL视图。

Postgres中创建类别物化视图:

CREATE MATERIALIZED VIEW VCategories AS (
SELECT cat.id, lng.iso_639_1_code, ct.descriptor, ct.value
FROM Categories cat
JOIN CategoryTranslations ct ON ct.category_id = cat.id
JOIN Languages lng ON lng.id = ct.language_id
);

查询ID为120的类别的所有翻译

SELECT * FROM VCategories WHERE id = 120 AND iso_639_1_code = 'en'

我觉得在使用应用程序的代码时很方便,您可以编写非常简单的代码来查询翻译和搜索记录