MySQL INNER JOIN выбирает только одну строку из второй таблицы

107

У меня есть usersтаблица и paymentsтаблица, для каждого пользователя, у тех из которых есть платежи, может быть несколько связанных платежей в paymentsтаблице. Я хочу выбрать всех пользователей, у которых есть платежи, но выбрать только их последний платеж. Я пробую этот SQL, но я никогда раньше не пробовал вложенные операторы SQL, поэтому хочу знать, что я делаю неправильно. Цените помощь

SELECT u.* 
FROM users AS u
    INNER JOIN (
        SELECT p.*
        FROM payments AS p
        ORDER BY date DESC
        LIMIT 1
    )
    ON p.user_id = u.id
WHERE u.package = 1
Васим
источник

Ответы:

150

У вас должен быть подзапрос, чтобы получить их последнюю дату согласно user ID.

SELECT  a.*, c.*
FROM users a 
    INNER JOIN payments c
        ON a.id = c.user_ID
    INNER JOIN
    (
        SELECT user_ID, MAX(date) maxDate
        FROM payments
        GROUP BY user_ID
    ) b ON c.user_ID = b.user_ID AND
            c.date = b.maxDate
WHERE a.package = 1
Джон Ву
источник
1
@Fluffeh, у тебя очень хороший ответ Part 1 - Joins and Unions. :) в закладки!
Джон Ву
1
Спасибо, приятель, написал это как раз для таких ситуаций, выскочил быстрый ответ с правильным SQL, а затем предложил ссылку на это чудовищное углубленное чтение, чтобы люди могли понять предлагаемый код. Планируется добавить к нему для дальнейшего расширения. Добро пожаловать, присоединяйтесь, если хотите, я действительно должен открыть скрипку для кода ...
Fluffeh
@JohnWoo спасибо за ваш ответ, он отлично сработал. И спасибо Fluffeh за вопросы и ответы, я займусь этим!
Wasim
Я думаю, что мой ответ проще и быстрее, потому что я использую только одно внутреннее соединение. Может я ошибаюсь?
Михай Матей
2
Есть ли способ выполнить этот запрос без внутренних соединений? Я хочу вернуть максимальное значение из таблицы c ИЛИ вернуть значение null, если строки не совпадают. Очевидно, что изменение JOIN на LEFT JOIN не работает.
Скотт
34
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
    SELECT id
    FROM payments AS p2
    WHERE p2.user_id = u.id
    ORDER BY date DESC
    LIMIT 1
)

Или

SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
    SELECT 1
    FROM payments AS p2
    WHERE
        p2.user_id = p.user_id AND
        (p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)

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

Утонченность
источник
это хороший подход, который вы используете. но у меня есть вопрос о получении одного изображения против одного продукта, например. У одного продукта много (4) изображений, но я хочу показать только одно изображение для этого продукта.
Али Раза
Вам не кажется, что он будет очень медленным в использовании? поскольку вы обрабатываете каждую запись в своем подзапросе для внутреннего соединения. Принятый ответ может быть лучшим ответом после незначительной настройки.
Хамис А. Хан
@ HameesA.Khan, я не проверял выполнение запросов. Возможно, вы правы, но принятое решение делает трехмерное соединение, которое тоже может быть медленным. Боюсь, доработка не будет незначительной (это тема вопроса).
Изящество
12
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC

Проверьте этот sqlfiddle

Михай Матей
источник
6
Предложение limit 1вернет только 1 пользователя, чего не хочет OP.
Fluffeh
6
@MateiMihai, это не работает. Запрос дает только максимальную дату, а не всю строку с максимальной датой. Вы можете увидеть это в скрипте: dateстолбец отличается от max(p.date). Если вы добавите больше столбцов в paymentsтаблицу (например cost), все эти столбцы будут не из нужной строки
zxcat
3
   SELECT u.* 
        FROM users AS u
        INNER JOIN (
            SELECT p.*,
             @num := if(@id = user_id, @num + 1, 1) as row_number,
             @id := user_id as tmp
            FROM payments AS p,
                 (SELECT @num := 0) x,
                 (SELECT @id := 0) y
            ORDER BY p.user_id ASC, date DESC)
        ON (p.user_id = u.id) and (p.row_number=1)
        WHERE u.package = 1
валекс
источник
2

С вашим запросом есть две проблемы:

  1. Каждой таблице и подзапросу нужно имя, поэтому вы должны назвать подзапрос INNER JOIN (SELECT ...) AS p ON ....
  2. Подзапрос в том виде, в котором он у вас есть, возвращает только один период строки, но на самом деле вам нужна одна строка для каждого пользователя . Для этого вам понадобится один запрос, чтобы получить максимальную дату, а затем самосоединиться, чтобы получить всю строку.

Предполагая, что нет никаких связей payments.date, попробуйте:

    SELECT u.*, p.* 
    FROM (
        SELECT MAX(p.date) AS date, p.user_id 
        FROM payments AS p
        GROUP BY p.user_id
    ) AS latestP
    INNER JOIN users AS u ON latestP.user_id = u.id
    INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
    WHERE u.package = 1
lc.
источник
это хороший подход, который вы используете. но у меня есть вопрос о получении одного изображения против одного продукта, например. У одного продукта много (4) изображений, но я хочу показать только одно изображение для этого продукта.
Али Раза
Я нашел это наиболее быстрым в моем случае. Единственное изменение, которое я сделал, - это добавление предложения where в подзапрос для фильтрации выбранных пользовательских данных только тогда, From payments as p Where p.user_id =@user_idкогда запрос выполняет группу по всей таблице.
Викаш Рати
2

Ответ @John Woo помог мне решить аналогичную проблему. Я улучшил его ответ, установив правильный порядок. Это сработало для меня:

SELECT  a.*, c.*
FROM users a 
    INNER JOIN payments c
        ON a.id = c.user_ID
    INNER JOIN (
        SELECT user_ID, MAX(date) as maxDate FROM
        (
            SELECT user_ID, date
            FROM payments
            ORDER BY date DESC
        ) d
        GROUP BY user_ID
    ) b ON c.user_ID = b.user_ID AND
           c.date = b.maxDate
WHERE a.package = 1

Однако я не уверен, насколько это эффективно.

GTCrais
источник
2
SELECT U.*, V.* FROM users AS U 
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id

Это заставит его работать

Судья Эзиефуле
источник
1

Матей Михай дал простое и эффективное решение, но оно не будет работать, пока не будет введена MAX(date)часть SELECT, поэтому этот запрос станет:

SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id

И order by не повлияет на группировку, но может упорядочить конечный результат, предоставленный group by. Я попробовал, и у меня это сработало.

Хасан Дад Хан
источник
1

Мой ответ, прямо вдохновленный @valex, очень полезен, если вам нужно несколько столбцов в предложении ORDER BY.

    SELECT u.* 
    FROM users AS u
    INNER JOIN (
        SELECT p.*,
         @num := if(@id = user_id, @num + 1, 1) as row_number,
         @id := user_id as tmp
        FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
             (SELECT @num := 0) x,
             (SELECT @id := 0) y
        )
    ON (p.user_id = u.id) and (p.row_number=1)
    WHERE u.package = 1
Жером Б
источник
1

Вы можете попробовать это:

SELECT u.*, p.*
FROM users AS u LEFT JOIN (
    SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
    FROM payments  
) AS p ON u.userid = p.userid AND p.RowNo=1
Шехар
источник
1
Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причины вашего предложения кода.
Алессио