Joindre gauche avec clause Where

162

J'ai besoin de récupérer tous les paramètres par défaut du tableau des paramètres, mais également de saisir le paramètre de caractère s'il existe pour le caractère x.

Mais cette requête ne récupère que les paramètres où le caractère est = 1, pas les paramètres par défaut si l'utilisateur n'a défini personne.

SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT JOIN `character_settings` 
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1'  

Je devrais donc avoir besoin de quelque chose comme ça:

array(
    '0' => array('somekey' => 'keyname', 'value' => 'thevalue'),
    '1' => array('somekey2' => 'keyname2'),
    '2' => array('somekey3' => 'keyname3')
)

Où clé 1 et 2 sont les valeurs par défaut lorsque la clé 0 contient la valeur par défaut avec la valeur de caractère.

Sein Kraft
la source

Réponses:

346

La whereclause filtre les lignes où le left joinne réussit pas. Déplacez-le vers la jointure:

SELECT  `settings`.*, `character_settings`.`value`
FROM    `settings`
LEFT JOIN 
       `character_settings` 
ON     `character_settings`.`setting_id` = `settings`.`id`
        AND `character_settings`.`character_id` = '1'  
Andomar
la source
55

Lors de la création de jointures externes (ANSI-89 ou ANSI-92), l'emplacement du filtrage est important car les critères spécifiés dans la ONclause sont appliqués avant que la jointure ne soit effectuée . Les critères par rapport à une table OUTER JOINed fournie dans la WHEREclause sont appliqués après la création de JOIN . Cela peut produire des ensembles de résultats très différents. En comparaison, peu importe pour les INNER JOINs si les critères sont fournis dans les clauses ONou WHERE- le résultat sera le même.

  SELECT  s.*, 
          cs.`value`
     FROM SETTINGS s
LEFT JOIN CHARACTER_SETTINGS cs ON cs.setting_id = s.id
                               AND cs.character_id = 1
Poneys OMG
la source
21

Si je comprends bien votre question, vous voulez des enregistrements de la base de données des paramètres s'ils n'ont pas de jointure à la table character_settings ou si cet enregistrement joint a character_id = 1.

Vous devriez donc faire

SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT OUTER JOIN `character_settings` 
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1' OR
`character_settings`.character_id is NULL
marque
la source
7
Cela risque de retourner de faux positifs
OMG Ponies
1
@OMGPonies Je ne comprends pas, quels pourraient être les cas où cela comporte des risques. Dans mon cas, j'ai appliqué le AND avec jointure et aucun résultat n'était là, mais lorsque j'ai utilisé la solution ci-dessus, j'ai eu des résultats. Mais je veux être sûr des problèmes auxquels je peux faire face avec cette option.
Chetan Sharma
2

Vous trouverez peut-être plus facile à comprendre en utilisant une simple sous-requête

SELECT `settings`.*, (
    SELECT `value` FROM `character_settings`
    WHERE `character_settings`.`setting_id` = `settings`.`id`
      AND `character_settings`.`character_id` = '1') AS cv_value
FROM `settings`

La sous-requête est autorisée à renvoyer null, vous n'avez donc pas à vous soucier de JOIN / WHERE dans la requête principale.

Parfois, cela fonctionne plus rapidement dans MySQL, mais comparez-le au formulaire LEFT JOIN pour voir ce qui fonctionne le mieux pour vous.

SELECT s.*, c.value
FROM settings s
LEFT JOIN character_settings c ON c.setting_id = s.id AND c.character_id = '1'
RichardTheKiwi
la source
1

Le résultat est correct d'après l'instruction SQL. La jointure gauche renvoie toutes les valeurs de la table de droite et uniquement les valeurs correspondantes de la table de gauche.

Les colonnes ID et NAME proviennent du tableau de droite et sont donc renvoyées.

Le score provient du tableau de gauche et 30 est renvoyé, car cette valeur se rapporte au nom "Flow". Les autres noms sont NULL car ils ne concernent pas le nom "Flow".

Le ci-dessous renverrait le résultat que vous attendiez:

    SELECT  a.*, b.Score
FROM    @Table1 a
    LEFT JOIN @Table2 b
       ON a.ID = b.T1_ID 
WHERE 1=1
AND a.Name = 'Flow'

Le SQL applique un filtre sur la table de droite.

Dan
la source
0

Pour ce problème, comme pour beaucoup d'autres impliquant des jointures à gauche non triviales, telles que la jointure à gauche sur des tables jointes en interne, je trouve pratique et un peu plus lisible de fractionner la requête avec une withclause. Dans votre exemple,

with settings_for_char as (
  select setting_id, value from character_settings where character_id = 1
)
select
  settings.*,
  settings_for_char.value
from
  settings
  left join settings_for_char on settings_for_char.setting_id = settings.id;
P-Gn
la source