MySQL INNER JOIN виберіть лише один рядок із другої таблиці


104

У мене є 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

Відповіді:


148

Вам потрібно мати підзапит, щоб отримати їхню останню дату 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, а потім запропонуй посилання на цю чудовисько, прочитуване, щоб люди могли зрозуміти запропонований код. Планування додавання до нього для подальшого розширення. Ласкаво просимо приєднатись, якщо вам
захочеться

@JohnWoo дякую за вашу відповідь, яка спрацювала чудово. І дякую Fluffeh за питання і відповіді, я погляну на це!
Васим

Я думаю, що моя відповідь простіша і швидша, тому що я використовую лише одне внутрішнє з'єднання. Може, я помиляюся?
Михай Матей

2
Чи є спосіб зробити цей запит без внутрішніх приєднань? Я хочу повернути максимум з таблиці c АБО повернути нульове значення назад, якщо жодні рядки не збігаються. Зміна ПРИЄДНАЙТЕ НА ЛІВНЕ ПРИЄДНАННЯ очевидно не працює.
Скотт

32
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
)

Це рішення краще, ніж прийнята відповідь, оскільки воно працює правильно, коли є деякі платежі з тим самим користувачем та датою.


це хороший підхід, який ви використовуєте. але у мене є питання щодо отримання одного зображення проти такого продукту, як. В одному продукті є багато (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

Ознайомтеся з цією загадкою


6
limit 1Пункт буде повертати тільки 1 користувач , який не те , що ОП хоче.
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

це хороший підхід, який ви використовуєте. але у мене є питання щодо отримання одного зображення проти такого продукту, як. В одному продукті є багато (4) зображень, але я хочу показати лише одне зображення проти цього продукту.
Алі Раза

Я знайшов це найшвидше у своєму випадку. Єдина зміна, яку я вніс, - це додаток, де в підзапиті використовується фільтр для фільтрації вибраних даних користувачів лише у 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

Я не впевнений, наскільки це ефективно.



1

Matei Mihai отримав просте і ефективне рішення, але воно не працюватиме, поки не помістить 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

І впорядкування по групі не вплине на групування, але він може замовити кінцевий результат, наданий групою. Я спробував це, і це спрацювало на мене.


1

Моя відповідь натхненна на @valex дуже корисно, якщо вам потрібно кілька пунктів у пункті ЗАМОВЛЕННЯ ПО.

    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
Хоча цей фрагмент коду може вирішити питання, зокрема пояснення дійсно допомагає покращити якість вашої публікації. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, і ці люди можуть не знати причини вашої пропозиції щодо коду.
Алессіо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.