database-design - obtener - que es un esquema en base de datos
Esquema para una base de datos multilenguaje (10)
Estoy desarrollando un software multilenguaje. En cuanto al código de la aplicación, la localizabilidad no es un problema. Podemos usar recursos específicos del idioma y tener todo tipo de herramientas que funcionen bien con ellos.
Pero, ¿cuál es el mejor enfoque para definir un esquema de base de datos multilingüe? Digamos que tenemos muchas tablas (100 o más), y cada tabla puede tener varias columnas que pueden ser localizadas (la mayoría de las columnas nvarchar deben ser localizables). Por ejemplo, una de las tablas puede contener información del producto:
CREATE TABLE T_PRODUCT (
NAME NVARCHAR(50),
DESCRIPTION NTEXT,
PRICE NUMBER(18, 2)
)
Puedo pensar en tres enfoques para admitir texto multilingüe en las columnas NOMBRE y DESCRIPCIÓN:
Columna separada para cada idioma
Cuando añadimos un nuevo lenguaje para el sistema, hay que crear columnas adicionales para almacenar el texto traducido, como este:
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) )
Tabla de traducción con columnas para cada idioma.
En lugar de almacenar texto traducido, solo se almacena una clave foránea para la tabla de traducciones. La tabla de traducciones contiene una columna para cada idioma.
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 )
Tablas de traducción con filas para cada idioma.
En lugar de almacenar texto traducido, solo se almacena una clave foránea para la tabla de traducciones. La tabla de traducciones solo contiene una clave, y una tabla separada contiene una fila para cada traducción a un idioma.
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) )
Cada solución tiene sus ventajas y desventajas, y me gustaría saber cuáles son sus experiencias con estos enfoques, qué recomienda y cómo diseñaría un esquema de base de datos en varios idiomas.
"Cuál es el mejor" se basa en la situación del proyecto. El primero es fácil de seleccionar y mantener, y también el rendimiento es mejor, ya que no necesita unir tablas cuando selecciona una entidad. Si confirmó que su proyecto es solo compatible con 2 o 3 idiomas, y no aumentará, puede usarlo.
El segundo es bueno, pero es difícil de entender y mantener. Y el rendimiento es peor que el primero.
El último es bueno en escalabilidad pero malo en rendimiento. La tabla T_TRANSLATION_ENTRY será cada vez más grande, es terrible cuando se desea recuperar una lista de entidades de algunas tablas.
¿Qué piensas acerca de tener una tabla de traducción relacionada para cada tabla traducible?
CREAR T_PRODUCT TABLA (int pr_id, NÚMERO PRECIO (18, 2))
CREAR TABLA T_PRODUCT_tr (pr_id INT FK, varchar de languagecode, pr_name text, pr_descr text)
De esta manera, si tiene varias columnas traducibles, solo se necesitaría una combinación para obtenerla, ya que no está generando automáticamente una traducción, puede ser más fácil importar elementos junto con sus traducciones relacionadas.
El lado negativo de esto es que si tiene un mecanismo complejo de respaldo de lenguaje, es posible que necesite implementar eso para cada tabla de traducción, si está confiando en algún procedimiento almacenado para hacerlo. Si haces eso desde la aplicación, esto probablemente no será un problema.
Déjeme saber lo que piensa. También estoy a punto de tomar una decisión al respecto para nuestra próxima aplicación. Hasta ahora hemos utilizado su 3er tipo.
¿Sería viable el siguiente enfoque? Digamos que tienes tablas donde más de 1 columna necesita traducción. Por lo tanto, para el producto, puede tener tanto el nombre del producto como la descripción del producto que necesita traducir. Podrías hacer lo siguiente:
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)
)
Antes de ir a detalles técnicos y soluciones, debe detenerse por un minuto y hacer algunas preguntas sobre los requisitos. Las respuestas pueden tener un gran impacto en la solución técnica. Ejemplos de tales preguntas serían:
- ¿Se usarán todos los idiomas todo el tiempo?
- ¿Quién y cuándo llenarán las columnas con las diferentes versiones lingüísticas?
- ¿Qué sucede cuando un usuario necesita un determinado idioma de un texto y no hay ninguno en el sistema?
- Solo los textos deben ser localizados o también hay otros elementos (por ejemplo, PRICE se puede almacenar en $ y € porque pueden ser diferentes)
Echa un vistazo a este ejemplo:
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)
)
Creo que no hay necesidad de explicar, la estructura se describe a sí misma.
Estaba buscando algunos consejos para la localización y encontré este tema. Me preguntaba por qué se usa esto:
CREATE TABLE T_TRANSLATION (
TRANSLATION_ID
)
Así que obtienes algo como lo sugiere 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''
¿No puedes simplemente dejar la traducción de la mesa para que obtengas esto?
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''
Este es un tema interesante, así que vamos a necromance.
Empecemos por los problemas del método 1:
Problema: estás desnormalizando para ahorrar velocidad.
En SQL (excepto PostGreSQL con hstore), no puede pasar un lenguaje de parámetros y decir:
SELECT [''DESCRIPTION_'' + @in_language] FROM T_Products
Así que tienes que hacer esto:
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
Lo que significa que tiene que modificar TODAS sus consultas si agrega un nuevo idioma. Esto naturalmente lleva al uso de "SQL dinámico", por lo que no tiene que alterar todas sus consultas.
Por lo general, esto se traduce en algo como esto (y, por cierto, no se puede usar en vistas o funciones con valores de tabla, lo que realmente es un problema si realmente necesita filtrar la fecha de informe)
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
El problema con esto es
a) El formato de fecha es muy específico del idioma, por lo que tiene un problema allí, si no ingresa el formato ISO (que el programador de variedades de jardín generalmente no lo hace, y en caso de un informe, el usuario está seguro). como el infierno no lo hará por usted, incluso si se le indica explícitamente que lo haga).
y
b) lo más importante es que pierdes cualquier tipo de comprobación de sintaxis . Si <insert name of your "favourite" person here>
altera el esquema porque repentinamente los requisitos para el cambio de ala, y se crea una nueva tabla, se deja la antigua pero se cambia el nombre del campo de referencia, no aparece ningún tipo de advertencia. Un informe funciona incluso cuando lo ejecuta sin seleccionar el parámetro de ala (==> guid.empty). Pero de repente, cuando un usuario real selecciona realmente un ala ==> boom . Este método rompe completamente cualquier tipo de prueba.
Método 2:
En pocas palabras: "Gran" idea (advertencia - sarcasmo), combinemos las desventajas del método 3 (velocidad lenta cuando hay muchas entradas) con las desventajas bastante horribles del método 1.
La única ventaja de este método es que mantiene todas las traducciones en una tabla y, por lo tanto, simplifica el mantenimiento. Sin embargo, lo mismo se puede lograr con el método 1 y un procedimiento almacenado de SQL dinámico, y una tabla (posiblemente temporal) que contiene las traducciones, y el nombre de la tabla de destino (y es bastante simple, suponiendo que haya nombrado todos los campos de texto como mismo).
Método 3:
Una tabla para todas las traducciones: Desventaja: debe almacenar n Claves foráneas en la tabla de productos para los campos que desea traducir. Por lo tanto, tienes que hacer n uniones para n campos. Cuando la tabla de traducción es global, tiene muchas entradas y las uniones se vuelven lentas. Además, siempre tiene que unirse a la tabla T_TRANSLATION n veces para n campos. Esto es bastante una sobrecarga. Ahora, ¿qué hace cuando debe adaptar las traducciones personalizadas por cliente? Tendrá que agregar otras 2x n uniones en una tabla adicional. Si tiene que unirse, diga 10 tablas, con 2x2xn = 4n combinaciones adicionales, ¡qué lío! Además, este diseño hace posible utilizar la misma traducción con 2 tablas. Si cambio el nombre del elemento en una tabla, ¿realmente quiero cambiar una entrada en otra tabla también CADA VEZ AL SOLO TIEMPO?
Además, ya no puede eliminar ni volver a insertar la tabla, porque ahora hay claves externas EN LA (S) TABLA (s) DEL PRODUCTO ... Por supuesto, puede omitir la configuración de los FK, y luego <insert name of your "favourite" person here>
puede eliminar la tabla y volver a insertar todas las entradas con newid () [o especificando el ID en el inserto, pero con el inserto de identidad desactivado ], y eso conduciría (y lo hará) a datos de basura (y nulo) -replicaciones de referencia) muy pronto.
-- CREATE TABLE MyTable(myfilename nvarchar(100) NULL, filemeta xml NULL )
;WITH CTE AS
(
-- INSERT INTO MyTable(myfilename, filemeta)
SELECT
''test.mp3'' AS myfilename
--,CONVERT(XML, N''<?xml version="1.0" encoding="utf-16" standalone="yes"?><body>Hello</body>'', 2)
--,CONVERT(XML, N''<?xml version="1.0" encoding="utf-16" standalone="yes"?><body><de>Hello</de></body>'', 2)
,CONVERT(XML
, N''<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<lang>
<de>Deutsch</de>
<fr>Français</fr>
<it>Ital&iano</it>
<en>English</en>
</lang>
''
, 2
) AS filemeta
)
SELECT
myfilename
,filemeta
--,filemeta.value(''body'', ''nvarchar'')
--, filemeta.value(''.'', ''nvarchar(MAX)'')
,filemeta.value(''(/lang//de/node())[1]'', ''nvarchar(MAX)'') AS DE
,filemeta.value(''(/lang//fr/node())[1]'', ''nvarchar(MAX)'') AS FR
,filemeta.value(''(/lang//it/node())[1]'', ''nvarchar(MAX)'') AS IT
,filemeta.value(''(/lang//en/node())[1]'', ''nvarchar(MAX)'') AS EN
FROM CTE
Entonces se puede obtener el valor de XPath en consultas en SQL, donde se puede colocar la cadena en variables
filemeta.value(''(/lang//'' + @in_language + ''/node())[1]'', ''nvarchar(MAX)'') AS bla
Y puedes actualizar el valor así:
UPDATE YOUR_TABLE
SET YOUR_XML_FIELD_NAME.modify(''replace value of (/lang/de/text())[1] with ""I am a ''''value ""'')
WHERE id = 1
Donde puede reemplazar /lang/de/...
con ''.../'' + @in_language + ''/...''
Algo así como el almacén de PostGre, excepto que debido a la sobrecarga del análisis de XML (en lugar de leer una entrada de una matriz asociativa en el almacén de PG) se vuelve demasiado lento, más la codificación xml hace que sea demasiado doloroso para ser útil.
Hagamos un ejemplo para ver esto FUNCIONA:
Primero, crea las tablas:
CREATE TABLE [dbo].[T_Languages](
[Lang_ID] [int] NOT NULL,
[Lang_NativeName] [nvarchar](200) NULL,
[Lang_EnglishName] [nvarchar](200) NULL,
[Lang_ISO_TwoLetterName] [varchar](10) NULL,
CONSTRAINT [PK_T_Languages] PRIMARY KEY CLUSTERED
(
[Lang_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[T_Products](
[PROD_Id] [int] NOT NULL,
[PROD_InternalName] [nvarchar](255) NULL,
CONSTRAINT [PK_T_Products] PRIMARY KEY CLUSTERED
(
[PROD_Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[T_Products_i18n](
[PROD_i18n_PROD_Id] [int] NOT NULL,
[PROD_i18n_Lang_Id] [int] NOT NULL,
[PROD_i18n_Text] [nvarchar](200) NULL,
CONSTRAINT [PK_T_Products_i18n] PRIMARY KEY CLUSTERED
(
[PROD_i18n_PROD_Id] ASC,
[PROD_i18n_Lang_Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
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] WITH CHECK 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] WITH CHECK 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_Languages]
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] [nvarchar](200) NULL,
CONSTRAINT [PK_T_Products_i18n_Cust] PRIMARY KEY CLUSTERED
(
[PROD_i18n_Cust_PROD_Id] ASC,
[PROD_i18n_Cust_Lang_Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages] FOREIGN KEY([PROD_i18n_Cust_Lang_Id])
REFERENCES [dbo].[T_Languages] ([Lang_ID])
GO
ALTER TABLE [dbo].[T_Products_i18n_Cust] CHECK CONSTRAINT [FK_T_Products_i18n_Cust_T_Languages]
GO
--ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH NOCHECK ADD CONSTRAINT [FK_T_Products_i18n_Cust_T_Products] FOREIGN KEY([PROD_i18n_Cust_PROD_Id])
ALTER TABLE [dbo].[T_Products_i18n_Cust] WITH CHECK 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
Luego rellena los datos
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
Y luego consultar los datos:
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
Si es perezoso, entonces también puede usar ISO-TwoLetterName (''DE'', ''EN'', etc.) como clave principal de la tabla de idiomas, entonces no tiene que buscar el ID de idioma. Pero si lo hace, tal vez quiera usar la etiqueta IETF-language , que es mejor, porque obtiene de-CH y de-DE, que en realidad no es la misma ortografía (doble s en lugar de ß en todas partes) , aunque es el mismo lenguaje base. Eso es solo un pequeño detalle que puede ser importante para usted, especialmente considerando que en-US y en-GB / en-CA / en-AU o fr-FR / fr-CA tiene problemas similares.
Cita: no lo necesitamos, solo hacemos nuestro software en inglés.
Respuesta: Sí, ¿pero cuál?
De todos modos, si usa un ID de entero, es flexible y puede cambiar su método en cualquier momento posterior.
Y deberías usar ese entero, porque no hay nada más molesto, destructivo y molesto que un diseño Db fallido.
Ver también RFC 5646 , ISO 639-2 ,
Y, si aún dice "nosotros", solo presentamos nuestra solicitud para "una sola cultura" (como usualmente en-US). Por lo tanto, no necesito ese número entero adicional, este sería un buen momento y lugar para mencionar el Etiquetas de idioma de la IANA , ¿no?
Porque van así:
de-DE-1901
de-DE-1996
y
de-CH-1901
de-CH-1996
(Hubo una reforma de la ortografía en 1996 ...) Trate de encontrar una palabra en un diccionario si está mal escrito; Esto se vuelve muy importante en aplicaciones que tratan con portales legales y de servicio público.
Más importante aún, hay regiones que están cambiando de alfabetos cirílicos a latinos, que pueden ser más molestos que las molestias superficiales de una reforma de ortografía oscura, por lo que esto también puede ser una consideración importante, dependiendo de en qué país vive. De una forma u otra, es mejor tener ese número entero ahí, por si acaso ...
Editar:
Y añadiendo ON DELETE CASCADE
después de
REFERENCES [dbo].[T_Products] ([PROD_Id])
simplemente puede decir: DELETE FROM T_Products
y no obtener ninguna violación de clave externa.
En cuanto a la colación, lo haría así:
A) Ten tu propio DAL
B) Guarde el nombre de colación deseado en la tabla de idiomas.
Es posible que desee poner las colaciones en su propia tabla, por ejemplo:
SELECT * FROM sys.fn_helpcollations()
WHERE description LIKE ''%insensitive%''
AND name LIKE ''%german%''
C) Tenga el nombre de colación disponible en su información auth.user.language
D) Escribe tu SQL así:
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE {#COLLATION}
E) Entonces, puedes hacer esto en tu DAL:
cmd.CommandText = cmd.CommandText.Replace("{#COLLATION}", auth.user.language.collation)
Lo que luego le dará esta consulta SQL perfectamente compuesta.
SELECT
COALESCE(GRP_Name_i18n_cust, GRP_Name_i18n, GRP_Name) AS GroupName
FROM T_Groups
ORDER BY GroupName COLLATE German_PhoneBook_CI_AI
Estoy de acuerdo con el aleatorizador. No veo por qué necesita una tabla "traducción".
Creo que esto es suficiente.
TA_product: ProductID, ProductPrice
TA_Language: LanguageID, Language
TA_Productname: ProductnameID, ProductID, LanguageID, ProductName
La tercera opción es la mejor, por varias razones:
- No requiere alterar el esquema de la base de datos para nuevos idiomas (y, por lo tanto, limitar los cambios de código)
- No requiere mucho espacio para idiomas no implementados o traducciones de un artículo en particular
- Proporciona la mayor flexibilidad.
- No terminas con tablas dispersas
- No tiene que preocuparse por las claves nulas y comprobar que está mostrando una traducción existente en lugar de alguna entrada nula.
- Si cambia o expande su base de datos para abarcar otros elementos / cosas / etc traducibles, puede usar las mismas tablas y el mismo sistema; esto está muy desacoplado del resto de los datos.
-Adán
Por lo general, optaría por este enfoque (no por sql real), esto se corresponde con su última opción.
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''
Porque tener todos los textos traducibles en un solo lugar facilita mucho el mantenimiento. A veces, las traducciones se subcontratan a agencias de traducción, de esta manera puede enviarlas solo un gran archivo de exportación e importarlo de nuevo con la misma facilidad.