Top 1 com junção à esquerda

92

Dada a consulta abaixo, pode haver várias linhas em dps_markers com a mesma chave do marcador, mas queremos unir apenas com a primeira. Se eu pegar esta consulta e remover o primeiro e ORDER BY, obtenho um valor para mbg.marker_value, mas executado como está, sempre retorna 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
fonte

Respostas:

196

Use OUTER APPLY em vez 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';

Ao contrário de JOIN, APPLY permite que você faça referência ao u.id dentro da consulta interna.

Remus Rusanu
fonte
Obrigado @Remus, me ajudou.
Sarthak Shah
2

A chave para depurar situações como essas é executar a subconsulta / visualização em linha por conta própria para ver qual é o 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

Executando isso, você veria que o profile_idvalor não corresponde ao u.idvalor de u162231993, o que explicaria por que todas as mbgreferências retornariam null(graças à junção à esquerda; você não obteria nada se fosse uma junção interna).

Você se codificou em um canto usando TOP, porque agora você tem que ajustar a consulta se quiser executá-la para outros usuários. Uma abordagem melhor seria:

   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'

Com isso, você pode alterar o idvalor da wherecláusula para verificar os registros de qualquer usuário do sistema.

Pôneis OMG
fonte
1

Porque a TOP 1partir da subconsulta ordenada não tem profile_id = 'u162231993' Remover where u.id = 'u162231993'e ver os resultados então.

Execute a subconsulta separadamente para entender o que está acontecendo.

Damir Sudarevic
fonte
ok eu acho que entendo o que você quer dizer agora. ainda precisa ser capaz de fazer isso funcionar. Basicamente, a tabela dps_markers pode ter mais de uma linha, o que causa erros na consulta externa que precisamos evitar.
dstarh
0

Damir está correto,

Sua subconsulta precisa garantir que dps_user.id seja igual a um.profile_id, caso contrário, ela agarrará a linha superior que pode, mas provavelmente não é igual ao seu id de 'u162231993'

Sua consulta deve ser semelhante a esta:

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
fonte
Sim, acabei de tentar isso, mas u.id não está visível na sub-seleção O identificador de várias partes "u.id" não pôde ser vinculado.
dstarh
Você pode colocar WHERE um.profile_id = 'u162231993'na subconsulta e WHERE mbg.marker_value IS NOT NULLdo lado de fora.
Damir Sudarevic
não vou saber o profile_id, será de outro join. Isso foi retirado de uma consulta muito maior
dstarh
bem, use a variável @SearchFor = 'u162231993'e então use-a WHERE, ou poste alguns dados e estruturas de tabela para que outras pessoas possam ajudar e experimentar.
Damir Sudarevic