MySQL INNER JOIN seleccione solo una fila de la segunda tabla

104

Tengo una userstabla y una paymentstabla, para cada usuario, aquellos de los cuales tienen pagos, pueden tener múltiples pagos asociados en la paymentstabla. Me gustaría seleccionar a todos los usuarios que tienen pagos, pero solo selecciono su último pago. Estoy probando este SQL, pero nunca antes había probado declaraciones SQL anidadas, así que quiero saber qué estoy haciendo mal. Agradezco la ayuda

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
fuente

Respuestas:

148

Necesita tener una subconsulta para obtener su última fecha por 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
fuente
1
@Fluffeh tienes una muy buena respuesta Part 1 - Joins and Unions. :) marcado!
John Woo
1
Gracias amigo, lo escribí solo para este tipo de situaciones, muestra una respuesta rápida con el SQL correcto y luego sugiere un enlace a esa monstruosa lectura en profundidad para que la gente pueda entender el código sugerido. Planeando agregarle para expandirlo aún más. Bienvenido a unirse si lo desea, debería abrir un violín para el código realmente ...
Fluffeh
@JohnWoo gracias por tu respuesta, ha funcionado a la perfección. Y gracias Fluffeh por las preguntas y respuestas, ¡lo echaré un vistazo!
Wasim
Creo que mi respuesta es más simple y más rápida porque estoy usando solo una combinación interna. ¿Puede ser que esté equivocado?
Mihai Matei
2
¿Hay alguna forma de hacer esta consulta sin las combinaciones internas? Quiero devolver el máximo de la tabla c O obtener un valor nulo si no coinciden las filas. El cambio de JOIN a LEFT JOIN no funciona obviamente.
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
)

Esta solución es mejor que la respuesta aceptada porque funciona correctamente cuando hay algunos pagos con el mismo usuario y fecha.

Finura
fuente
es un buen enfoque que utiliza. pero tengo una pregunta sobre cómo obtener una imagen con un producto como. Un producto tiene muchas (4) imágenes, pero solo quiero mostrar una sola imagen contra ese producto.
Ali Raza
¿No crees que será muy lento de usar? a medida que procesa cada registro en su subconsulta para unión interna. La respuesta aceptada puede ser la mejor respuesta posible después de un pequeño ajuste.
Hamees A. Khan
@ HameesA.Khan, no he examinado la ejecución de las consultas. Puede que tenga razón, pero la solución aceptada hace una unión tridimensional que también puede ser lenta. Me temo que el ajuste no será menor (este es el tema de la pregunta).
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

Mira este sqlfiddle

Mihai Matei
fuente
6
La limit 1cláusula solo devolverá 1 usuario, que no es lo que quiere el OP.
Fluffeh
6
@MateiMihai, esto no funciona. La consulta solo da la fecha máxima, no la fila completa con la fecha máxima. Puedes verlo en violín: la datecolumna es diferente de max(p.date). Si agrega más columnas en la paymentstabla (por ejemplo cost), todas las columnas no serán de la fila necesaria
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
valex
fuente
2

Hay dos problemas con su consulta:

  1. Cada tabla y subconsulta necesita un nombre, por lo que debe nombrar la subconsulta INNER JOIN (SELECT ...) AS p ON ....
  2. La subconsulta tal como la tiene solo devuelve un período de fila, pero en realidad desea una fila para cada usuario . Para eso, necesita una consulta para obtener la fecha máxima y luego volver a unirse para obtener la fila completa.

Suponiendo que no haya vínculos para payments.date, intente:

    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.
fuente
es un buen enfoque que utiliza. pero tengo una pregunta sobre cómo obtener una imagen con un producto como. Un producto tiene muchas (4) imágenes, pero solo quiero mostrar una sola imagen contra ese producto.
Ali Raza
Encontré esto más rápido en mi caso. El único cambio que hice fue agregar una cláusula where en la subconsulta para filtrar los datos de usuario seleccionados solo From payments as p Where p.user_id =@user_idcuando la consulta está haciendo un grupo en toda la tabla.
Vikash Rathee
2

La respuesta de @John Woo me ayudó a resolver un problema similar. También he mejorado su respuesta estableciendo el orden correcto. Esto me ha funcionado:

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

Sin embargo, no estoy seguro de cuán eficiente sea esto.

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

Esto lo hará funcionar

Justicia Eziefule
fuente
1

Matei Mihai dio una solución simple y eficiente, pero no funcionará hasta que coloque una parte MAX(date)en SELECT, por lo que esta consulta se convertirá en:

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

Y ordenar por no hará ninguna diferencia en la agrupación, pero puede ordenar el resultado final proporcionado por grupo. Lo probé y funcionó para mí.

Hassan Dad Khan
fuente
1

Mi respuesta se inspiró directamente en @valex muy útil, si necesita varias columnas en la cláusula 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
Jérôme B
fuente
1

Puedes probar esto:

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
fuente
1
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
Alessio