Top 1 avec une jointure à gauche

92

Compte tenu de la requête ci-dessous, il peut y avoir plusieurs lignes dans dps_markers avec la même clé de marqueur, mais nous ne voulons joindre que la première. Si je prends cette requête et supprime le top 1 et ORDER BY, j'obtiens une valeur pour mbg.marker_value mais l'exécute tel quel, il retourne toujours null

SELECT u.id, mbg.marker_value 
FROM dps_user u
LEFT JOIN 
    (SELECT TOP 1 m.marker_value, um.profile_id
     FROM dps_usr_markers um (NOLOCK)
         INNER JOIN dps_markers m (NOLOCK) 
             ON m.marker_id= um.marker_id AND 
                m.marker_key = 'moneyBackGuaranteeLength'
     ORDER BY m.creation_date
    ) MBG ON MBG.profile_id=u.id 
WHERE u.id = 'u162231993'
dstarh
la source

Réponses:

196

Utilisez OUTER APPLY au lieu de LEFT JOIN:

SELECT u.id, mbg.marker_value 
FROM dps_user u
OUTER APPLY 
    (SELECT TOP 1 m.marker_value, um.profile_id
     FROM dps_usr_markers um (NOLOCK)
         INNER JOIN dps_markers m (NOLOCK) 
             ON m.marker_id= um.marker_id AND 
                m.marker_key = 'moneyBackGuaranteeLength'
     WHERE um.profile_id=u.id 
     ORDER BY m.creation_date
    ) AS MBG
WHERE u.id = 'u162231993';

Contrairement à JOIN, APPLY vous permet de référencer l'u.id dans la requête interne.

Remus Rusanu
la source
Merci @Remus, cela m'a aidé.
Sarthak Shah
2

La clé pour déboguer des situations comme celles-ci est d'exécuter la sous-requête / vue en ligne seule pour voir quelle est la sortie:

  SELECT TOP 1 
         dm.marker_value, 
         dum.profile_id
    FROM DPS_USR_MARKERS dum (NOLOCK)
    JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id 
                                AND dm.marker_key = 'moneyBackGuaranteeLength'
ORDER BY dm.creation_date

En exécutant cela, vous verriez que la profile_idvaleur ne correspond pas à la u.idvaleur de u162231993, ce qui expliquerait pourquoi des mbgréférences reviendraient null(grâce à la jointure de gauche; vous n'obtiendrez rien s'il s'agissait d'une jointure interne).

Vous vous êtes codé dans un coin en utilisant TOP, car vous devez maintenant modifier la requête si vous souhaitez l'exécuter pour d'autres utilisateurs. Une meilleure approche serait:

   SELECT u.id, 
          x.marker_value 
     FROM DPS_USER u
LEFT JOIN (SELECT dum.profile_id,
                  dm.marker_value,
                  dm.creation_date
             FROM DPS_USR_MARKERS dum (NOLOCK)
             JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id 
                                         AND dm.marker_key = 'moneyBackGuaranteeLength'
           ) x ON x.profile_id = u.id
     JOIN (SELECT dum.profile_id,
                  MAX(dm.creation_date) 'max_create_date'
             FROM DPS_USR_MARKERS dum (NOLOCK)
             JOIN DPS_MARKERS dm (NOLOCK) ON dm.marker_id= dum.marker_id 
                                         AND dm.marker_key = 'moneyBackGuaranteeLength'
         GROUP BY dum.profile_id) y ON y.profile_id = x.profile_id
                                   AND y.max_create_date = x.creation_date
    WHERE u.id = 'u162231993'

Avec cela, vous pouvez changer le id valeur de la whereclause pour vérifier les enregistrements de n'importe quel utilisateur du système.

Poneys OMG
la source
1

Parce que la TOP 1sous-requête ordonnée n'a pas profile_id = 'u162231993' Supprimerwhere u.id = 'u162231993' et voir les résultats alors.

Exécutez la sous-requête séparément pour comprendre ce qui se passe.

Damir Sudarevic
la source
ok je pense que je vois ce que tu veux dire maintenant. doivent encore être en mesure de faire fonctionner cela. Fondamentalement, la table dps_markers table peut avoir plus d'une ligne, ce qui provoque des dupes dans la requête externe que nous devons éviter.
dstarh le
0

Damir a raison,

Votre sous-requête doit s'assurer que dps_user.id est égal à um.profile_id, sinon il récupérera la ligne du haut qui pourrait, mais probablement pas égaler votre id de 'u162231993'

Votre requête doit ressembler à ceci:

SELECT u.id, mbg.marker_value 
FROM dps_user u
LEFT JOIN 
    (SELECT TOP 1 m.marker_value, um.profile_id
     FROM dps_usr_markers um (NOLOCK)
         INNER JOIN dps_markers m (NOLOCK) 
             ON m.marker_id= um.marker_id AND 
                m.marker_key = 'moneyBackGuaranteeLength'
     WHERE u.id = um.profile_id
     ORDER BY m.creation_date
    ) MBG ON MBG.profile_id=u.id 
WHERE u.id = 'u162231993'
Nathan Koop
la source
oui, j'ai juste essayé mais u.id n'est pas visible dans la sous-sélection L'identifiant en plusieurs parties "u.id" n'a pas pu être lié.
dstarh le
Vous pouvez mettre WHERE um.profile_id = 'u162231993'dans la sous-requête et WHERE mbg.marker_value IS NOT NULLà l'extérieur.
Damir Sudarevic
je ne connais pas le profile_id, ce sera d'une autre jointure. Cela a été retiré d'une requête beaucoup plus grande
dstarh
Eh bien, utilisez la variable @SearchFor = 'u162231993', puis utilisez-la dans WHERE, ou publiez des données et des structures de tableau afin que d'autres personnes puissent vous aider et l'essayer.
Damir Sudarevic le