Bonjour,
l'on m'a très souvent posé cette question, alors voici un très petit tutoriel sur une manière assez basique de gèrer ce problème.
Les avantages d'une telle méthode sont :
Explication de l'exemple :
Une base de données de produits, disposant d'un champ banal (Prix), et d'un champ à internationaliser (Nom).
Cette base va se diviser en deux tables : _produits (un underscore) et __produits (deux underscores). La différence ? L'une contient les données structurelles (Prix), l'autre les traductions (Nom).
Elles sont définies et indexées comme suit :
Dans le cas présent, si la base de données ne trouve pas de nom correspondant au produit en espagnol, elle cherchera en anglais puis, en dernier lieu, en français.
Assurez-vous simplement de posséder au moins un nom (quelque soit la langue) pour chaque produit.
MySQL (MyISAM, sans contraintes donc) :
Requête type (testée sur MySQL 5.1) :
Oracle (attention, deux espaces se sont glissés (LOC ALE), supprimez-les) :
Requête type (testée sur Oracle 10g) :
Les deux cas devraient renvoyer :
Ce tutoriel est destiné à donner une idée générale au travers d'un exemple basique, il est de votre responsabilité de modifier, d'indexer, de contraindre et de sécuriser votre version en fonction de vos besoins.
Si une erreur surgissait, ou qu'une meilleure solution vous sautait aux yeux, n'hésitez pas à l'indiquer. Ce tutoriel signe l'arrêt d'une trêve de deux ans entre MySQL et moi, il est donc probable qu'une solution plus économique existe (hors procédure).
l'on m'a très souvent posé cette question, alors voici un très petit tutoriel sur une manière assez basique de gèrer ce problème.
Les avantages d'une telle méthode sont :
:arrow: la simplicité,
:arrow: l'évolutivité.
:arrow: l'évolutivité.
Explication de l'exemple :
Une base de données de produits, disposant d'un champ banal (Prix), et d'un champ à internationaliser (Nom).
Cette base va se diviser en deux tables : _produits (un underscore) et __produits (deux underscores). La différence ? L'une contient les données structurelles (Prix), l'autre les traductions (Nom).
Elles sont définies et indexées comme suit :
_Produits :
ID (entier non-signé, clef primaire), identifieur d'un produit.
Prix (nombre décimal).
__Produits : Prix (nombre décimal).
ID (entier non-signé, clef primaire (avec Locale)), identifieur d'un produit.
Locale (char, clef primaire (avec ID)), identifieur d'une langue.
Nom (varchar).
Uniquement Oracle : Locale (char, clef primaire (avec ID)), identifieur d'une langue.
Nom (varchar).
_Locales (Code (char, clef primaire), Nom (varchar)).
__Produits ID & Locale sont contraints avec les champs externes _Produits (ID) et _Locales (Code).
__Produits ID & Locale sont contraints avec les champs externes _Produits (ID) et _Locales (Code).
Dans le cas présent, si la base de données ne trouve pas de nom correspondant au produit en espagnol, elle cherchera en anglais puis, en dernier lieu, en français.
Assurez-vous simplement de posséder au moins un nom (quelque soit la langue) pour chaque produit.
MySQL (MyISAM, sans contraintes donc) :
Code:
#DROP TABLE `tutoriel_produits` CASCADE;
#DROP TABLE `tutoriel__produits` CASCADE;
CREATE TABLE `tutoriel_produits` (
`ID` int(10) unsigned NOT NULL auto_increment,
`Prix` decimal(8,2) NOT NULL default '0.00',
PRIMARY KEY (`ID`)
) ENGINE=MyISAM AUTO_INCREMENT=0 AUTO_INCREMENT=0;
CREATE TABLE `tutoriel__produits` (
`ID` int(10) unsigned NOT NULL,
`Locale` char(5) NOT NULL,
`Nom` varchar(32) NOT NULL,
PRIMARY KEY (`ID`,`Locale`)
) ENGINE=MyISAM;
INSERT INTO `tutoriel_produits` (`ID`, `Prix`) VALUES (1, 1.00), (2, 0.05), (3, 0.50);
INSERT INTO `tutoriel__produits` (`id`, `locale`, `nom`) VALUES (1, 'fr_FR', 'Banane'), (2, 'es_ES', 'Aceituna'), (2, 'fr_FR', 'Olive'), (3, 'en_EN', 'Pepper'), (3, 'fr_FR', 'Poivre');
Code:
SELECT `P`.`ID` , `P`.`Prix` , (
SELECT `PL`.`Nom`
FROM `tutoriel__produits` PL
WHERE `PL`.`ID` = `P`.`ID`
ORDER BY FIELD( `PL`.`Locale` , 'es_ES', 'en_EN', 'fr_FR' )
LIMIT 1
) `Nom`
FROM `tutoriel_produits` P
GROUP BY `P`.`ID`
Oracle (attention, deux espaces se sont glissés (LOC ALE), supprimez-les) :
Code:
#DROP TABLE "TUTORIEL_LOCALES" CASCADE CONSTRAINTS;
#DROP TABLE "TUTORIEL_PRODUITS" CASCADE CONSTRAINTS;
#DROP TABLE "TUTORIEL__PRODUITS" CASCADE CONSTRAINTS;
#DROP SEQUENCE "SQ_TUTORIEL_PRODUITS_ID";
CREATE TABLE "TUTORIEL_LOCALES"
( "CODE" CHAR(5) NOT NULL ENABLE,
"NOM" VARCHAR(32),
CONSTRAINT "IX_TUTORIEL_LOCALES_CODE" PRIMARY KEY ("CODE") ENABLE
);
CREATE TABLE "TUTORIEL_PRODUITS"
( "ID" NUMBER NOT NULL ENABLE,
"PRIX" NUMBER(8,2),
CONSTRAINT "IX_TUTORIEL_PRODUITS_ID" PRIMARY KEY ("ID") ENABLE
);
CREATE SEQUENCE "SQ_TUTORIEL_PRODUITS_ID" MINVALUE 1000 INCREMENT BY 1 START WITH 1000;
CREATE TABLE "TUTORIEL__PRODUITS"
( "ID" NUMBER NOT NULL ENABLE,
"LOCALE" CHAR(5),
"NOM" VARCHAR(32),
CONSTRAINT "IX_TUTORIEL__PRODUITS_IDLOCALE" PRIMARY KEY ("ID", "LOCALE") ENABLE,
CONSTRAINT "FK_TUTORIEL__PRODUITS_ID" FOREIGN KEY ("ID") REFERENCES "TUTORIEL_PRODUITS" ("ID") ENABLE,
CONSTRAINT "FK_TUTORIEL__PRODUITS_LOCALE" FOREIGN KEY ("LOCALE") REFERENCES "TUTORIEL_LOCALES" ("CODE") ENABLE
);
INSERT INTO "TUTORIEL_LOCALES" ("CODE", "NOM") VALUES ('fr_FR', 'Français');
INSERT INTO "TUTORIEL_LOCALES" ("CODE", "NOM") VALUES ('en_EN', 'English');
INSERT INTO "TUTORIEL_LOCALES" ("CODE", "NOM") VALUES ('es_ES', 'Español');
INSERT INTO "TUTORIEL_PRODUITS" ("ID", "PRIX") VALUES (SQ_TUTORIEL_PRODUITS_ID.NEXTVAL, '1,00');
INSERT INTO "TUTORIEL_PRODUITS" ("ID", "PRIX") VALUES (SQ_TUTORIEL_PRODUITS_ID.NEXTVAL, '0,05');
INSERT INTO "TUTORIEL_PRODUITS" ("ID", "PRIX") VALUES (SQ_TUTORIEL_PRODUITS_ID.NEXTVAL, '0,50');
INSERT INTO "TUTORIEL__PRODUITS" ("ID", "LOCALE", "NOM") VALUES (1000, 'fr_FR', 'Banane');
INSERT INTO "TUTORIEL__PRODUITS" ("ID", "LOCALE", "NOM") VALUES (1001, 'es_ES', 'Aceituna');
INSERT INTO "TUTORIEL__PRODUITS" ("ID", "LOCALE", "NOM") VALUES (1001, 'fr_FR', 'Olive');
INSERT INTO "TUTORIEL__PRODUITS" ("ID", "LOCALE", "NOM") VALUES (1002, 'en_EN', 'Pepper');
INSERT INTO "TUTORIEL__PRODUITS" ("ID", "LOCALE", "NOM") VALUES (1002, 'fr_FR', 'Poivre');
Code:
SELECT P.ID, P.PRIX, (MAX (PL.NOM) KEEP (
DENSE_RANK FIRST
ORDER BY CASE PL.LOCALE
WHEN 'es_ES' THEN 1
WHEN 'en_EN' THEN 2
WHEN 'fr_FR' THEN 3
END)
) NOM
FROM "TUTORIEL_PRODUITS" P
INNER JOIN "TUTORIEL__PRODUITS" PL ON P.ID = PL.ID
GROUP BY P.ID, P.PRIX;
Les deux cas devraient renvoyer :
Code:
ID PRIX NOM
--------------------- ------------------ --------------------
1000 1 Banane
1001 0,05 Aceituna
1002 0,5 Pepper
Ce tutoriel est destiné à donner une idée générale au travers d'un exemple basique, il est de votre responsabilité de modifier, d'indexer, de contraindre et de sécuriser votre version en fonction de vos besoins.
Si une erreur surgissait, ou qu'une meilleure solution vous sautait aux yeux, n'hésitez pas à l'indiquer. Ce tutoriel signe l'arrêt d'une trêve de deux ans entre MySQL et moi, il est donc probable qu'une solution plus économique existe (hors procédure).