MySQL INNER JOIN wählt nur eine Zeile aus der zweiten Tabelle aus

104

Ich habe eine usersTabelle und eine paymentsTabelle, für jeden Benutzer, für den Zahlungen vorliegen, können mehrere Zahlungen in der paymentsTabelle enthalten sein. Ich möchte alle Benutzer auswählen, die Zahlungen haben, aber nur ihre letzte Zahlung auswählen. Ich versuche dieses SQL, aber ich habe noch nie zuvor verschachtelte SQL-Anweisungen ausprobiert, daher möchte ich wissen, was ich falsch mache. Schätzen Sie die Hilfe

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
Wasim
quelle

Antworten:

148

Sie benötigen eine Unterabfrage, um das neueste Datum zu erhalten 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
John Woo
quelle
1
@Fluffeh du hast eine sehr schöne Antwort Part 1 - Joins and Unions. :) Lesezeichen!
John Woo
1
Danke Kumpel, habe es für genau diese Art von Situationen geschrieben, eine schnelle Antwort mit dem richtigen SQL- Code angezeigt und dann einen Link zu diesem Monster vorgeschlagen, damit die Leute den vorgeschlagenen Code verstehen können . Planen Sie, es zu erweitern, um es weiter auszubauen. Willkommen mitzumachen, wenn Sie
möchten
@ JohnWoo danke für deine Antwort, das hat perfekt funktioniert. Und danke Fluffeh für die Fragen und Antworten, ich werde einen Blick darauf werfen!
Wasim
Ich denke, dass meine Antwort einfacher und schneller ist, weil ich nur einen inneren Join verwende. Vielleicht bin ich falsch?
Mihai Matei
2
Gibt es eine Möglichkeit, diese Abfrage ohne die inneren Verknüpfungen durchzuführen? Ich möchte das Maximum aus Tabelle c zurückgeben ODER null zurückerhalten, wenn keine Zeilen übereinstimmen. Das Ändern von JOIN in LEFT JOIN funktioniert offensichtlich nicht.
Scott
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
)

Diese Lösung ist besser als die akzeptierte Antwort, da sie bei einigen Zahlungen mit demselben Benutzer und Datum ordnungsgemäß funktioniert.

Finesse
quelle
Es ist ein guter Ansatz, den Sie verwenden. aber ich habe frage über das abrufen eines bildes gegen ein produkt wie. Ein Produkt hat viele (4) Bilder, aber ich möchte nur ein Bild für dieses Produkt anzeigen.
Ali Raza
Glaubst du nicht, dass die Verwendung sehr langsam sein wird? während Sie für jeden Datensatz in Ihrer Unterabfrage nach innerem Join verarbeiten. Akzeptierte Antwort kann die beste Antwort nach einer kleinen Änderung sein.
Hamees A. Khan
@ HameesA.Khan, ich habe die Ausführung der Abfragen nicht untersucht. Sie haben vielleicht Recht, aber die akzeptierte Lösung führt zu einer dreidimensionalen Verknüpfung, die auch langsam sein kann. Ich fürchte, die Optimierung wird nicht geringfügig sein (dies ist das Thema der Frage).
Finesse
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

Schauen Sie sich diese SQLfiddle an

Mihai Matei
quelle
6
Die limit 1Klausel gibt nur 1 Benutzer zurück, was nicht das OP will.
Fluffeh
6
@MateiMihai, das funktioniert nicht. Die Abfrage gibt nur das maximale Datum an, nicht die ganze Zeile mit dem maximalen Datum. Sie können es in Geige sehen: dateSpalte unterscheidet sich von max(p.date). Wenn Sie weitere Spalten in die paymentsTabelle cost
einfügen
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
Valex
quelle
2

Es gibt zwei Probleme mit Ihrer Abfrage:

  1. Jede Tabelle und Unterabfrage benötigt einen Namen, daher müssen Sie die Unterabfrage benennen INNER JOIN (SELECT ...) AS p ON ... .
  2. Die Unterabfrage, wie Sie sie haben, gibt nur eine Zeilenperiode zurück, aber Sie möchten tatsächlich eine Zeile für jeden Benutzer . Dazu benötigen Sie eine Abfrage, um das maximale Datum zu erhalten, und verbinden sich dann wieder selbst, um die gesamte Zeile abzurufen.

Angenommen, es gibt keine Bindungen für payments.date: Versuchen Sie:

    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.
quelle
Es ist ein guter Ansatz, den Sie verwenden. aber ich habe frage über das abrufen eines bildes gegen ein produkt wie. Ein Produkt hat viele (4) Bilder, aber ich möchte nur ein Bild für dieses Produkt anzeigen.
Ali Raza
Ich fand das in meinem Fall am schnellsten. Die einzige Änderung, die ich vorgenommen habe, ist das Hinzufügen einer where-Klausel in der Unterabfrage, um ausgewählte Benutzerdaten nur zu filtern, From payments as p Where p.user_id =@user_idwenn die Abfrage eine Gruppe nach der gesamten Tabelle ausführt .
Vikash Rathee
2

Die Antwort von @John Woo hat mir geholfen, ein ähnliches Problem zu lösen. Ich habe seine Antwort verbessert, indem ich auch die richtige Reihenfolge eingestellt habe. Das hat bei mir funktioniert:

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

Ich bin mir jedoch nicht sicher, wie effizient dies ist.

GTCrais
quelle
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

Dies wird es zum Laufen bringen

Gerechtigkeit Eziefule
quelle
1

Matei Mihai hat eine einfache und effiziente Lösung gegeben, die jedoch erst funktioniert, wenn ein MAX(date)Teil in SELECT eingefügt wird, sodass diese Abfrage wie folgt lautet:

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

Die Reihenfolge nach macht keinen Unterschied bei der Gruppierung, kann jedoch das Endergebnis nach Gruppe nach bestellen. Ich habe es versucht und es hat bei mir funktioniert.

Hassan Dad Khan
quelle
1

Meine Antwort ist direkt von @valex inspiriert und sehr nützlich, wenn Sie mehrere Spalten in der ORDER BY-Klausel benötigen.

    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
Jérôme B.
quelle
1

Sie können dies versuchen:

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
Shekhar
quelle
1
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Alessio