Верхний 1 с левым стыком

96

Учитывая приведенный ниже запрос, в dps_markers может быть несколько строк с одним и тем же ключом маркера, но мы хотим присоединиться только к первой. Если я возьму этот запрос и удалю верхнюю 1 и ORDER BY, я получу значение для mbg.marker_value, но запустив его как есть, он всегда возвращает 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
источник

Ответы:

204

Используйте OUTER APPLY вместо 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';

В отличие от JOIN, APPLY позволяет вам ссылаться на u.id внутри внутреннего запроса.

Ремус Русану
источник
Спасибо @Remus, мне помогло.
Sarthak Shah
3

Ключом к отладке подобных ситуаций является запуск подзапроса / встроенного представления самостоятельно, чтобы увидеть, каков результат:

  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

Запустив это, вы увидите, что profile_idзначение не соответствует u.idзначению u162231993, что объясняет, почему любые mbgссылки будут возвращаться null(благодаря левому соединению; вы бы ничего не получили, если бы это было внутреннее соединение).

Вы загнали себя в угол с помощью TOP, потому что теперь вам нужно настроить запрос, если вы хотите запустить его для других пользователей. Лучшим подходом было бы:

   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'

При этом вы можете изменить idзначение в whereпредложении, чтобы проверять записи для любого пользователя в системе.

OMG Пони
источник
2

Потому что TOP 1в подзапросе из упорядоченного подзапроса нет profile_id = 'u162231993' Удалить where u.id = 'u162231993'и посмотреть результаты.

Выполните подзапрос отдельно, чтобы понять, что происходит.

Дамир Сударевич
источник
хорошо, я думаю, теперь я понимаю, что ты имеешь в виду. все еще нужно иметь возможность заставить это работать. В принципе, таблица dps_markers может иметь более одной строки, что вызывает дублирование во внешнем запросе, которое нам нужно предотвратить.
dstarh 09
1

Дамир прав,

Ваш подзапрос должен гарантировать, что dps_user.id равен um.profile_id, иначе он захватит верхнюю строку, которая может, но, вероятно, не равна вашему идентификатору u162231993.

Ваш запрос должен выглядеть так:

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'
Натан Куп
источник
1
да, я только что попробовал, но u.id не отображается в подвыборке. Многокомпонентный идентификатор "u.id" не может быть привязан.
dstarh 09
1
Вы можете ввести WHERE um.profile_id = 'u162231993'подзапрос и WHERE mbg.marker_value IS NOT NULLснаружи.
Дамир Сударевич 09
1
Я не буду знать profile_id, он будет из другого соединения. Это было извлечено из гораздо более крупного запроса
dstarh
1
ну, используйте переменную, @SearchFor = 'u162231993'а затем используйте ее WHEREили опубликуйте некоторые структуры данных и таблиц, чтобы другие люди могли помочь и опробовать ее.
Дамир Сударевич 09