¿Cómo consulto sql para una última fecha de registro para cada usuario?

228

Tengo una tabla que es una colección de entradas sobre cuándo un usuario inició sesión.

username, date,      value
--------------------------
brad,     1/2/2010,  1.1
fred,     1/3/2010,  1.0
bob,      8/4/2009,  1.5
brad,     2/2/2010,  1.2
fred,     12/2/2009, 1.3

etc..

¿Cómo creo una consulta que me dé la última fecha para cada usuario?

Actualización: olvidé que necesitaba tener un valor que coincida con la última fecha.

cabeza de pez
fuente
77
¿Qué base de datos estás utilizando? MySQL, SQL-Server, Oracle, ...?
Peter Lang
1
¿Necesita el valor que corresponde con la última fecha, o el valor máximo Y la fecha máxima?
Matthew Jones
Posible duplicado de Cómo obtener el último registro por grupo en SQL
Patrick Honorez

Respuestas:

381
select t.username, t.date, t.value
from MyTable t
inner join (
    select username, max(date) as MaxDate
    from MyTable
    group by username
) tm on t.username = tm.username and t.date = tm.MaxDate
RedFilter
fuente
3
Al trabajar con postgresql, ¿sería esta versión más rápida que usar un IN (subconsulta) en lugar de la unión interna?
TheOne
3
@TheOne como mi experiencia, el uso de la unión interna es más rápido que en condiciones
dado el
14
Cuidado con este enfoque: puede devolver más de una fila por usuario si tienen más de un registro por fecha ( max(date)devolvería una fecha que uniría múltiples registros). Para evitar este problema, sería preferible utilizar la solución de @ dotjoe: stackoverflow.com/a/2411763/4406793 .
Marco Roy
@RedFilter Esto funcionó perfecto para mi problema. Muchas gracias por una consulta tan técnica. Por cierto, usé datetime en lugar de date para evitar obtener múltiples resultados para una fecha en particular
Muhammad Khan el
¿por qué necesita 'y t.date = tm.MaxDate' no sería suficiente agrupar?
duldi
125

Uso de funciones de ventana (funciona en Oracle, Postgres 8.4, SQL Server 2005, DB2, Sybase, Firebird 3.0, MariaDB 10.3)

select * from (
    select
        username,
        date,
        value,
        row_number() over(partition by username order by date desc) as rn
    from
        yourtable
) t
where t.rn = 1
dotjoe
fuente
1
Vale la pena aclarar qué producto / versión de Sybase. No funciona en Sybase ASE 16.
levant pied
2
Un gran beneficio de este enfoque es que se garantiza que siempre devolverá solo una fila por partición ( usernameen este caso) y ni siquiera requiere un campo único "ordenable" (como unirse max(date)en otras respuestas).
Marco Roy
1
Solo para agregar algo a lo que dijo @MarcoRoy, si tiene más de un registro con la misma fecha máxima, si cambia la consulta, como cuando la está depurando, un registro diferente puede recibir un número de fila de 1, por lo que Los resultados pueden ser inconsistentes. Pero mientras realmente no te importe, esto no debería ser un problema. Esto se puede resolver si agrega el PK después de la fecha. Por ejemplo: order by date desc, id desc).
Andrew
40

Veo que la mayoría de los desarrolladores usan una consulta en línea sin considerar su impacto en los datos de gran tamaño.

Simplemente, puede lograr esto al:

SELECT a.username, a.date, a.value
FROM myTable a
LEFT OUTER JOIN myTable b
ON a.username = b.username 
AND a.date < b.date
WHERE b.username IS NULL
ORDER BY a.date desc;
sujeet
fuente
3
en realidad, esto solo funciona para duplicados, si tiene más de 2 valores, la condición a.date <b.date no funciona, lo que significa que no es una solución general, aunque la idea de trabajar con la IZQUIERDA EXTERIOR IZQUIERDA es importante cosa en esta respuesta.
iversoncru
Curiosamente, Sybase ASE 16 funciona bien para tablas más pequeñas (<10k filas), pero con las más grandes (> 100k filas) se cuelga ... Pensé que este sería el ejemplo perfecto que las bases de datos relacionales deberían sobresalir en ...
levant pied
1
@levantpied ... Sí, la unión izquierda es costosa en conjuntos de datos más grandes. Puede ajustar un rendimiento poniendo la condición de filtro en la unión para manejarlo de alguna manera si es posible.
sujeet
21

Para obtener toda la fila que contiene la fecha máxima para el usuario:

select username, date, value
from tablename where (username, date) in (
    select username, max(date) as date
    from tablename
    group by username
)
Alison R.
fuente
1
Trabajando para MySQL
School Boy
1
Tenga en cuenta que esto le dará duplicados si hay más de un registro con la misma fecha para un usuario específico. Puede o no querer esto.
Andrew
Este sql es lento en Oracle con una cláusula, no usará el índice
meadlai
9
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
Manix
fuente
44
Si bien esta es otra posible solución, normalmente no es una buena manera de resolver esto. Hacerlo de esta manera hará que la consulta interna se ejecute una vez para cada nombre en la tabla, lo que provocará una desaceleración importante para cualquier tabla de tamaño significativo. Hacer una consulta por separado que no tenga un elemento de la primera consulta en la cláusula where y luego unir las dos tablas generalmente será más rápido.
Scott Chamberlain
Esto tiene la buena característica de ser una de las soluciones más comprensibles que no es específica de la implementación.
Michael Szczepaniak
7

Según mi experiencia, la forma más rápida es tomar cada fila para la que no hay una fila más nueva en la tabla.

Otra ventaja es que la sintaxis utilizada es muy simple y que el significado de la consulta es bastante fácil de entender (tome todas las filas de manera que no exista una fila más nueva para el nombre de usuario que se está considerando).

NO EXISTE

SELECT username, value
FROM t
WHERE NOT EXISTS (
  SELECT *
  FROM t AS witness
  WHERE witness.username = t.username AND witness.date > t.date
);

NUMERO DE FILA

SELECT username, value
FROM (
  SELECT username, value, row_number() OVER (PARTITION BY username ORDER BY date DESC) AS rn
  FROM t
) t2
WHERE rn = 1

UNIR INTERNAMENTE

SELECT t.username, t.value
FROM t
INNER JOIN (
  SELECT username, MAX(date) AS date
  FROM t
  GROUP BY username
) tm ON t.username = tm.username AND t.date = tm.date;

IZQUIERDA COMBINACIÓN EXTERNA

SELECT username, value
FROM t
LEFT OUTER JOIN t AS w ON t.username = w.username AND t.date < w.date
WHERE w.username IS NULL
Fabian Pijcke
fuente
Tengo dificultades para entender la versión NO EXISTENTE. ¿No te estás perdiendo una agregación en la parte de subconsulta? Si ejecuto esto en mi mesa, solo obtengo 3 registros de empleados de 40 empleados que tengo en la mesa. Debería obtener al menos 40 registros. En la consulta interna, ¿no deberíamos también coincidir por nombre de usuario?
Narshe
A mí me funciona usando lo siguiente:SELECT username, value FROM t WHERE NOT EXISTS ( SELECT * FROM t AS witness WHERE witness.date > t.date AND witness.username = t.username );
Narshe
He mirado el NO EXISTE y parece que solo devuelve la entrada más alta para todos los usuarios en lugar de: "una consulta que me daría la última fecha para cada usuario".
Tasos Zervos
Tienes razón, actualizo mi consulta. Gracias por tu comentario! @Narshe lo siento, me perdí tus comentarios por alguna razón: / Pero tienes toda la razón.
Fabian Pijcke
2

Este debería darle el resultado correcto para su pregunta editada.

La subconsulta se asegura de encontrar solo filas de la última fecha, y el exterior GROUP BYse encargará de los vínculos. Cuando hay dos entradas para la misma fecha para el mismo usuario, devolverá la que tenga la más alta value.

SELECT t.username, t.date, MAX( t.value ) value
FROM your_table t
JOIN (
       SELECT username, MAX( date ) date
       FROM your_table
       GROUP BY username
) x ON ( x.username = t.username AND x.date = t.date )
GROUP BY t.username, t.date
Peter Lang
fuente
1

También puede usar la función de clasificación analítica

    with temp as 
(
select username, date, RANK() over (partition by username order by date desc) as rnk from t
)
select username, rnk from t where rnk = 1
imba22
fuente
0
SELECT Username, date, value
 from MyTable mt
 inner join (select username, max(date) date
              from MyTable
              group by username) sub
  on sub.username = mt.username
   and sub.date = mt.date

Abordaría el problema actualizado. Puede que no funcione tan bien en tablas grandes, incluso con una buena indexación.

Philip Kelley
fuente
0
SELECT *
FROM ReportStatus c
inner join ( SELECT 
  MAX(Date) AS MaxDate
  FROM ReportStatus ) m
on  c.date = m.maxdate
Narmadha
fuente
0

Para Oracle ordena el conjunto de resultados en orden descendente y toma el primer registro, por lo que obtendrá el último registro:

select * from mytable
where rownum = 1
order by date desc
usuario2014518
fuente
0
SELECT DISTINCT Username, Dates,value 
FROM TableName
WHERE  Dates IN (SELECT  MAX(Dates) FROM TableName GROUP BY Username)


Username    Dates       value
bob         2010-02-02  1.2       
brad        2010-01-02  1.1       
fred        2010-01-03  1.0       
wara
fuente
Esto probablemente no funcionaría si varios usuarios tuvieran pedidos en la misma fecha; ¿Qué pasaría si Brad y Bob tuvieran una orden el 2 de enero?
AHiggins
Estoy agrupando por nombre de usuario para que funcione y los resultados serán los siguientes: Nombre de usuario Fechas valor bob 2010-02-02 1.2 brad 2010-02-02 1.4 fred 2010-01-03 1.0
wara
0
SELECT t1.username, t1.date, value
FROM MyTable as t1
INNER JOIN (SELECT username, MAX(date)
            FROM MyTable
            GROUP BY username) as t2 ON  t2.username = t1.username AND t2.date = t1.date
David
fuente
44
Una o dos oraciones sobre implementación o explicación contribuyen en gran medida a crear una respuesta de calidad.
0

Select * from table1 where lastest_date=(select Max(latest_date) from table1 where user=yourUserName)

La consulta interna devolverá la última fecha para el usuario actual, la consulta externa extraerá todos los datos de acuerdo con el resultado de la consulta interna.

Dheeraj Kumar
fuente
0

Utilicé esta forma de tomar el último registro para cada usuario que tengo en mi mesa. Fue una consulta para obtener la última ubicación del vendedor según el tiempo reciente detectado en los dispositivos PDA.

CREATE FUNCTION dbo.UsersLocation()
RETURNS TABLE
AS
RETURN
Select GS.UserID, MAX(GS.UTCDateTime) 'LastDate'
From USERGPS GS
where year(GS.UTCDateTime) = YEAR(GETDATE()) 
Group By GS.UserID
GO
select  gs.UserID, sl.LastDate, gs.Latitude , gs.Longitude
        from USERGPS gs
        inner join USER s on gs.SalesManNo = s.SalesmanNo 
        inner join dbo.UsersLocation() sl on gs.UserID= sl.UserID and gs.UTCDateTime = sl.LastDate 
        order by LastDate desc
Mahmoud Hawa
fuente
0
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
AJAY
fuente
Bienvenido a StackOverflow y gracias por intentar ayudar. Las respuestas de solo código como la suya son menos apreciadas en comparación con las respuestas que explican la solución.
Yunnosch
Lea este tutorial para obtener una respuesta de calidad.
2017
y. no vuelve a MAX para cada nombre de usuario, solo a la última fila individual.
IrvineCAGuy
0

Mi pequeña compilación

  • uno mismo joinmejor que anidadoselect
  • pero group byno te da primary keycuál es preferible parajoin
  • esta clave se puede dar partition byjunto con first_value( docs )

Entonces, aquí hay una consulta:

Seleccione
 t. *
de 
 Tabla t unión interna (
  seleccione first_value (ID) distinto (partición por GroupColumn ordenado por DateColumn desc) como ID
  de la mesa
  donde FilterColumn = 'valor'
 ) j en t.ID = j.ID

Pros:

  • Filtrar datos con wheredeclaración utilizando cualquier columna
  • select cualquier columna de filas filtradas

Contras:

  • Necesita MS SQL Server a partir de 2012.
resnyanskiy
fuente
0

Hice algo para mi aplicación, ya que:

A continuación se muestra la consulta:

select distinct i.userId,i.statusCheck, l.userName from internetstatus 
as i inner join login as l on i.userID=l.userID 
where nowtime in((select max(nowtime) from InternetStatus group by userID));    
Sajee
fuente
0

Esto es similar a una de las respuestas anteriores, pero en mi opinión es mucho más simple y ordenado. Además, muestra un buen uso para la declaración de aplicación cruzada. Para SQL Server 2005 y superior ...

select
    a.username,
    a.date,
    a.value,
from yourtable a
cross apply (select max(date) 'maxdate' from yourtable a1 where a.username=a1.username) b
where a.date=b.maxdate
James Moore
fuente
0
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
Ashish Bindra
fuente
1
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación, y probablemente resultaría en más votos positivos. Recuerde que está respondiendo la pregunta para los lectores en el futuro, no solo la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos. De la opinión
doble pitido
-2

Esto también debería funcionar para obtener las últimas entradas para los usuarios.

SELECT username, MAX(date) as Date, value
FROM MyTable
GROUP BY username, value
Vipin Kohli
fuente
1
Hola, la columna de valor debe estar en el grupo por cláusula.
Juan Ruiz de Castilla
-4

Usaría la función agregada MAX y GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
Matthew Jones
fuente
77
Su edición solo elegirá una aleatoria value, no la asociada con la MAX(date)fila.
Alison R.
dará la fecha máxima, pero el nombre de usuario y el valor pueden no ser del mismo registro.
SKR