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

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

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.


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


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

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

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.


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


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í.


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

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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.