LEFT JOIN только первая строка


138

Я читал много тем о том, как получить только первый ряд левого соединения, но по какой-то причине это не работает для меня.

Вот моя структура (конечно, упрощенная)

Ленты

id |  title | content
----------------------
1  | Feed 1 | ...

Художники

artist_id | artist_name
-----------------------
1         | Artist 1
2         | Artist 2

feeds_artists

rel_id | artist_id | feed_id
----------------------------
1      |     1     |    1 
2      |     2     |    1 
...

Теперь я хочу получить статьи и присоединиться только к первому артисту, и я подумал о чем-то вроде этого:

SELECT *
    FROM feeds 
    LEFT JOIN feeds_artists ON wp_feeds.id = (
        SELECT feeds_artists.feed_id FROM feeds_artists
        WHERE feeds_artists.feed_id = feeds.id 
    LIMIT 1
    )
WHERE feeds.id = '13815'

просто получить только первую строку feeds_artists, но уже это не работает.

Я не могу использовать TOPиз-за своей базы данных, и я не могу сгруппировать результаты, так feeds_artists.artist_idкак мне нужно отсортировать их по дате (я получил результаты, сгруппировав их таким образом, но результаты не самые новые)

Пробовал кое-что с OUTER APPLY, но безуспешно. Честно говоря, я не могу себе представить, что происходит в этих строках - наверное, самая главная причина, почему я не могу заставить это работать.

РЕШЕНИЕ:

SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
    SELECT artist_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.id
    LIMIT 1
)
WHERE f.id = '13815'



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

Я думаю, что вы правы - я также добавил это как ответ.
KddC

Ответы:


97

Если вы можете предположить, что идентификаторы исполнителей со временем увеличиваются, то это MIN(artist_id)будет самый ранний.

Так что попробуйте что-нибудь вроде этого (непроверено ...)

SELECT *
  FROM feeds f
  LEFT JOIN artists a ON a.artist_id = (
    SELECT
      MIN(fa.artist_id) a_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.feed_id
  ) a

Спасибо за ваш быстрый ответ. Это был не точный ответ, но полностью вернул меня на правильный путь. Я всегда старался объединить оба на одном уровне вместо того, чтобы делать одно зависимым от другого. Большое спасибо за то, что привели меня на правильный путь. Отредактировано первое сообщение
KddC

5
Разве на этом этапе простой подзапрос не будет лучше? Потому что теперь у вас есть объединение и подзапрос. Просто спрашиваю, потому что я ищу решение той же проблемы :)
galdikas

2
подзапрос выполняется слишком медленно.
Sinux,

1
@Sinux определяет "слишком медленно". Это зависит от количества записей и от заданных требований.
наблюдатель

1
Это не сработает! Подзапрос НЕ позволяет передавать поле из родительского запроса !!!
Чт Син,

53

Версия без подвыбора:

   SELECT f.title,
          f.content,
          MIN(a.artist_name) artist_name
     FROM feeds f
LEFT JOIN feeds_artists fa ON fa.feed_id = f.id
LEFT JOIN artists a ON fa.artist_id = a.artist_id
 GROUP BY f.id

2
Я не думаю, что это будет первая строка (первый идентификатор или первый из чего-то еще), просто случайным образом выберите одну из левых строк соединения.
Sinux

27
Этот запрос неверен. Будет выбрано имя «самого низкого» исполнителя, а не имя первых исполнителей в наборе.
Глапа

5
Неправильно для вопроса ... но именно то, что я хочу. В моем случае мне просто нужен первый идентификатор из таблицы, к которой я присоединяюсь.
Дрю Стивенс

2
Проблема здесь в том, что если вы решите сделать СЧЁТ или СУММ, вы испортите данные. Проблема с Subselect заключается в том, что получать данные тяжелее, поскольку они создают временную таблицу ... Я бы хотел, чтобы MySql мог иметь LIMIT на уровне LEFT JOIN.
Shadoweb

У этого решения проблемы с производительностью. Вместо этого используйте раствор Min / Max.
Садех PM

25

Ответ @Matt Dodges поставил меня на правильный путь. Еще раз спасибо за ответы, которые помогли многим ребятам. Это работает так:

SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
    SELECT artist_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.id
    LIMIT 1
)
WHERE f.id = '13815'

Это ответ, работающий только для первой строки. Разрывается, когда нет f.idусловия.
Tajni

2

Я использовал кое-что еще (думаю, лучше ...) и хочу этим поделиться:

Я создал ПРОСМОТР, в котором есть предложение "группа"

CREATE VIEW vCountries AS SELECT * PROVINCES GROUP BY country_code

SELECT * FROM client INNER JOIN vCountries on client_province = province_id

Я хочу сказать еще, что я думаю, что мы должны сделать это решение, ПОТОМУ ЧТО МЫ ЧТО-ТО НЕПРАВИЛЬНО ПРИ АНАЛИЗЕ ... по крайней мере, в моем случае ... но иногда это дешевле сделать, чтобы перепроектировать все ...

Я надеюсь, что это помогает!


Предполагая, что для каждой имеется несколько строк (провинций?) country_code, GROUP BYЭто неправильно. См ONLY_FULL_GROUP_BY.
Рик Джеймс

Вам не нужно создавать представление, которое будет использоваться исключительно для LEFT / INNER JOIN ?! Можно использовать подзапрос или WITH x AS (предложение?
kiradotee

2

Основываясь на нескольких ответах здесь, я нашел то, что сработало для меня, и я хотел обобщить и объяснить, что происходит.

перерабатывать:

LEFT JOIN table2 t2 ON (t2.thing = t1.thing)

чтобы:

LEFT JOIN table2 t2 ON (t2.p_key = (SELECT MIN(t2_.p_key) 
    FROM table2 t2_ WHERE (t2_.thing = t1.thing) LIMIT 1))

условие, которое связывает t1 и t2, перемещается из ONи во внутренний запрос WHERE. MIN(primary key)или LIMIT 1гарантируют , что только одна строка возвращается внутренним запросом.

после выбора одной конкретной строки нам нужно указать, ONкакая это строка. вот почему ONсравнивается первичный ключ объединенной таблицы.

вы можете поиграть с внутренним запросом (то есть порядком + лимитом), но он должен возвращать один первичный ключ желаемой строки, который укажет ONточную строку для присоединения.


примечания: это также будет работать только LIMITили только MIN. ONусловие должно быть на присоединяемой таблицы первичного ключа для снижения производительности избежать.
Ориадам

1

Я хочу дать более общий ответ . Тот, который будет обрабатывать любой случай, когда вы хотите выбрать только первый элемент в LEFT JOIN .

Вы можете использовать подзапрос, который GROUP_CONCATS, что вы хотите (отсортированный тоже!), А затем просто разделить результат GROUP_CONCAT и взять только его первый элемент, например так ...

LEFT JOIN Person ON Person.id = (
    SELECT SUBSTRING_INDEX(
        GROUP_CONCAT(FirstName ORDER BY FirstName DESC SEPARATOR "_" ), '_', 1)
    ) FROM Person
);

Поскольку у нас есть DESC в качестве опции ORDER BY , это вернет идентификатор человека для кого-то вроде "Zack". Если бы мы хотели кого-то с таким именем, как «Энди», мы бы изменили ORDER BY FirstName DESC на ORDER BY FirstName ASC .

Это ловко, так как это дает возможность упорядочения полностью в ваших руках. Но после большого тестирования он не будет хорошо масштабироваться в ситуации с большим количеством пользователей и большим количеством данных.

Это, однако, полезно при запуске отчетов с интенсивным использованием данных для администратора.


GROUP_CONCATТрюк хорошо до тех пор , как число значений ограничено. Ограничение по умолчанию - 1024 символа.
Рик Джеймс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.