Top 1 con combinación a la izquierda

96

Dada la consulta a continuación, puede haber varias filas en dps_markers con la misma clave de marcador, pero solo queremos unirnos a la primera. Si tomo esta consulta y elimino el 1 superior y ORDER BY obtengo un valor para mbg.marker_value pero ejecuto como está, siempre devuelve nulo

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
fuente

Respuestas:

204

Utilice OUTER APPLY en lugar 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';

A diferencia de JOIN, APPLY le permite hacer referencia al u.id dentro de la consulta interna.

Remus Rusanu
fuente
Gracias @Remus, me ayudó.
Sarthak Shah
3

La clave para depurar situaciones como estas es ejecutar la subconsulta / vista en línea por sí sola para ver cuál es el resultado:

  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

Al ejecutar eso, vería que el profile_idvalor no coincidía con el u.idvalor de u162231993, lo que explicaría por qué mbgregresarían las referencias null(gracias a la combinación izquierda; no obtendría nada si fuera una combinación interna).

Te has codificado en una esquina usando TOP, porque ahora tienes que modificar la consulta si quieres ejecutarla para otros usuarios. Un mejor enfoque sería:

   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'

Con eso, puede cambiar el idvalor en la wherecláusula para verificar los registros de cualquier usuario en el sistema.

Ponis dios mio
fuente
2

Porque TOP 1de la subconsulta ordenada no tiene profile_id = 'u162231993' Eliminar where u.id = 'u162231993'y ver resultados luego.

Ejecute la subconsulta por separado para comprender qué está pasando.

Damir Sudarevic
fuente
ok, creo que veo lo que quieres decir ahora. todavía necesito poder hacer que esto funcione. Básicamente, la tabla dps_markers puede tener más de una fila, lo que provoca engaños en la consulta externa que debemos evitar.
dstarh
1

Damir tiene razón,

Su subconsulta debe asegurarse de que dps_user.id sea igual a um.profile_id; de lo contrario, tomará la fila superior que podría, pero probablemente no sea igual a su ID de 'u162231993'

Su consulta debería verse así:

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
fuente
1
sí, acabo de intentarlo, pero u.id no está visible en la sub-selección. El identificador de varias partes "u.id" no se pudo vincular.
dstarh
1
Puede poner WHERE um.profile_id = 'u162231993'en la subconsulta y WHERE mbg.marker_value IS NOT NULLen el exterior.
Damir Sudarevic
1
No sabré el profile_id, será de otra unión. Esto se extrajo de una consulta mucho más amplia
dstarh
1
bueno, use la variable @SearchFor = 'u162231993'y luego úsela en WHERE, o publique algunos datos y estructuras de tabla para que otras personas puedan ayudar y probarlo.
Damir Sudarevic