Bonjour,
Il existe surement une multitude de manières de résoudre le cas d'école que vous citez. Quelques méthodes me viennent à l'esprit et sont simples à mettre en place.
Tous mes exemples reprennent votre jeu de données dans une clause WITH. Sautez cette partie pour la compréhension.
Votre problématique:
Retourner une seule ligne en fonction d'un paramètre. Vous travaillez dans un SGBD relationnel fonctionnant, lors des requêtes SQL simples sur des tuples.
Conditions:
Retourner la ligne de la langue recherche si trouvée.
OU
Retourner La ligne par défaut si votre recherche n'aboutie pas
OU
Retourner La ligne par défaut si aucune ligne n'est fournie
NOTE: Les exemples ont été réalisés sous base Oracle 10.2.0.3. Cependant la syntaxe SQL utilisée est normalement ANSI ou très proche.
Méthode 0: Conditions + EXISTS
L'important est de garde et de comprendre toutes les conditions qu'implique votre problématique.
Les conditions appliquées sont:
- Si la ligne courante est égale à MA_LANGUE
OU Si la ligne courante est égale à PAR_DEFAUT et qu'il n'existe pas de MA_LANGUE dans la table
OU SI la ligne courante est égale à PAR_DEFAUT et que MA_LANGUE n'est pas renseignée (overkill)
WITH localisation_sites AS (
SELECT 1 as id ,'www.google.fr' as adresse , 'français' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 2 as id , 'www.google.com' as adresse , 'anglais' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 3 as id , NULL as adresse , 'espagnol' as langue , 45 as menu_parent_id FROM DUAL)
SELECT pe.id
,pe.adresse
,pe.langue
,pe.menu_parent_id
FROM localisation_sites pe
WHERE 1 = 1
AND (pe.langue = :ma_langue OR
pe.langue = 'français' AND NOT EXISTS
(SELECT 1
FROM localisation_sites pe
WHERE 1 = 1
AND pe.langue = :ma_langue) OR
:ma_langue IS NULL AND pe.langue = 'français')
Méthode 1: UNION ALL + EXISTS
Le concept est de faire une jointure entre deux requêtes. Une ramenant la ligne de votre langue si celle-ci existe. L'autre de ramener celle de votre valeur par défaut si la valeur que vous souhaitez remonter n'existe pas.
Union ALL est mis pour ne pas faire de DISTINCT inutile entre les lignes.
Le cas du null est géré implicitement par le second UNION.
WITH localisation_sites AS (
SELECT 1 as id ,'www.google.fr' as adresse , 'français' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 2 as id , 'www.google.com' as adresse , 'anglais' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 3 as id , NULL as adresse , 'espagnol' as langue , 45 as menu_parent_id FROM DUAL)
SELECT pe.id, pe.adresse, pe.langue, pe.menu_parent_id
FROM localisation_sites pe
WHERE 1 = 1
AND pe.langue = :ma_langue
UNION ALL
SELECT pe2.id, pe2.adresse, pe2.langue, pe2.menu_parent_id
FROM localisation_sites pe2
WHERE 1 = 1
AND pe2.langue = 'français'
AND NOT EXISTS (SELECT 1
FROM localisation_sites pe
WHERE 1 = 1
AND pe.langue = :ma_langue);
Méthode 2: Jointure Externe
Une autre manière pourrait être l'auto-jointure externe à gauche. La méthode sera surement plus consommatrice en ressources sur des tables mal indexées.
Le principe est de ramener toujours votre ligne par défaut 'français' par exemple et de faire une auto-jointure à gauche afin de ramener, si elle existe la ligne de votre langue.
Le résultat sera un seul et même tuple avec les informations des deux tables, le défaut et si elles existent celle de la langue souhaitée.
Il ne vous reste ensuite qu'à prendre les bonnes valeurs :
- Les valeurs par défaut si le champ de la clé de votre source est vide
- Les valeurs de la langue souhaitée sinon
/** Table de données semblable à l'exemple */
WITH localisation_sites AS (
SELECT 1 as id ,'www.google.fr' as adresse , 'français' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 2 as id , 'www.google.com' as adresse , 'anglais' as langue , 45 as menu_parent_id FROM DUAL
UNION
SELECT 3 as id , NULL as adresse , 'espagnol' as langue , 45 as menu_parent_id FROM DUAL)
SELECT CASE
WHEN pe.id IS NULL THEN
pe_def.id
ELSE
pe.id
END AS id
,CASE
WHEN pe.id IS NULL THEN
pe_def.adresse
ELSE
pe.adresse
END AS adresse
,CASE
WHEN pe.id IS NULL THEN
pe_def.langue
ELSE
pe.langue
END AS langue
,CASE
WHEN pe.id IS NULL THEN
pe_def.menu_parent_id
ELSE
pe.menu_parent_id
END AS menu_parent_id
FROM localisation_sites pe_def
LEFT OUTER JOIN localisation_sites pe
ON (1 = 1)
WHERE 1 = 1
AND pe_def.langue = 'français'
AND pe.langue = nvl(:ma_langue, pe_def.langue) -- Pour gérer le cas d'une langue NULL ... overkill
Bonne étude de cas. Ne vous contentez pas de prendre au hasard, prenez le temps de comprendre les cas :]
Ces deux exemples ne sont que deux possibilités. En m'amusant avec la résolution de ce cas j'ai réalisé 5 autres méthodes de récupération des données. Toutes plus farfelues les unes que les autres certes mais toutes aboutissent.
Cordialement !
Message édité par zyniel le 12-07-2013 à 18:23:28