Récupère la ligne qui a la valeur Max pour une colonne
-
02-07-2019 - |
Question
Tableau:
UserId, Value, Date.
Je souhaite obtenir l’ID utilisateur, valeur pour le max (Date) pour chaque ID utilisateur. C'est-à-dire, la valeur pour chaque ID utilisateur ayant la date la plus récente. Y at-il un moyen de faire cela simplement en SQL? (Oracle de préférence)
Mise à jour : toutes les excuses sont excuses: je dois obtenir TOUS les UserIds. Mais pour chaque ID utilisateur, seule cette ligne où cet utilisateur a la date la plus récente.
La solution
Ceci récupérera toutes les lignes pour lesquelles la valeur de la colonne my_date est égale à la valeur maximale de my_date pour cet ID utilisateur. Cela peut récupérer plusieurs lignes pour l'ID utilisateur dont la date maximale est sur plusieurs lignes.
select userid,
my_date,
...
from
(
select userid,
my_date,
...
max(my_date) over (partition by userid) max_my_date
from users
)
where my_date = max_my_date
"Fonctions analytiques rock"
Modifier: en ce qui concerne le premier commentaire ...
"L'utilisation de requêtes analytiques et d'une auto-jointure annule l'objectif des requêtes analytiques"
Il n'y a pas de jointure automatique dans ce code. Il y a plutôt un prédicat placé sur le résultat de la vue intégrée qui contient la fonction analytique - un sujet très différent et une pratique tout à fait standard.
"La fenêtre par défaut d'Oracle va de la première ligne de la partition à la fenêtre courante" "
.La clause de fenêtrage n’est applicable qu’en présence de la clause order by. En l'absence de clause order by, aucune clause de fenêtrage n'est appliquée par défaut et aucune ne peut être explicitement spécifiée.
Le code fonctionne.
Autres conseils
Je vois que beaucoup de gens utilisent des sous-requêtes ou des fonctionnalités propres à un fournisseur, mais je fais souvent ce type de requête sans sous-requêtes de la manière suivante. Il utilise un langage SQL simple et standard, il devrait donc fonctionner dans n’importe quelle marque de SGBDR.
SELECT t1.*
FROM mytable t1
LEFT OUTER JOIN mytable t2
ON (t1.UserId = t2.UserId AND t1."Date" < t2."Date")
WHERE t2.UserId IS NULL;
En d'autres termes: récupérez la ligne à partir de t1
lorsqu'aucune autre ligne n'existe avec le même UserId
et une date supérieure.
(J'ai mis l'identifiant "Date" dans des délimiteurs car il s'agit d'un mot réservé SQL.)
Si t1. "Date" = t2. "Date"
, le double apparaît. Habituellement, les tables ont la clé auto_inc (seq)
, par exemple. id
.
Pour éviter de doubler, vous pouvez utiliser les éléments suivants:
SELECT t1.*
FROM mytable t1
LEFT OUTER JOIN mytable t2
ON t1.UserId = t2.UserId AND ((t1."Date" < t2."Date")
OR (t1."Date" = t2."Date" AND t1.id < t2.id))
WHERE t2.UserId IS NULL;
Commentaire de @Farhan:
Voici une explication plus détaillée:
Une jointure externe tente de joindre t1
avec t2
. Par défaut, tous les résultats de t1
sont renvoyés et si il existe une correspondance dans t2
, elle est également renvoyée. S'il n'y a pas de correspondance dans t2
pour une ligne donnée de t1
, la requête renvoie toujours la ligne de t1
et utilise . NULL
en tant qu'espace réservé pour toutes les colonnes de t2
. C’est ainsi que fonctionnent les jointures externes en général.
Le truc dans cette requête est de concevoir la condition de correspondance de la jointure de telle sorte que t2
doit correspondre au même ID utilisateur
et à un . date
supérieur. L'idée étant qu'une ligne existe dans t2
qui a une plus grande date
, la ligne dans t1
est comparée à ne peut pas soit la date
la plus grande pour ce ID utilisateur
. Mais s'il n'y a pas de correspondance - c'est-à-dire s'il n'existe aucune ligne dans t2
avec une date
supérieure à la ligne dans t1
, nous savons que la ligne dans t1
était la ligne avec la plus grande date
pour l'ID donné
.
Dans ces cas (en l'absence de correspondance), les colonnes de t2
seront NULL
- même les colonnes spécifiées dans la condition de jointure. C'est pourquoi nous utilisons WHERE t2.UserId IS NULL
, car nous recherchons les cas où aucune ligne n'a été trouvée avec une date
supérieure pour l'ID utilisateur donné.
.
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
FROM table
GROUP BY userid
Je ne connais pas vos noms de colonnes exacts, mais ce serait quelque chose comme ceci:
select userid, value from users u1 where date = (select max(date) from users u2 where u1.userid = u2.userid)
N'étant pas au travail, je n'ai pas Oracle à portée de main, mais il me semble rappeler qu'Oracle autorise la correspondance de plusieurs colonnes dans une clause IN, ce qui devrait au moins éviter les options utilisant une sous-requête corrélée, à savoir rarement une bonne idée.
Quelque chose comme ça, peut-être (je ne me souviens plus si la liste des colonnes doit être mise entre parenthèses ou non):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
EDIT: Je l'ai juste essayé pour de vrai:
SQL> create table MyTable (usr char(1), dt date);
SQL> insert into mytable values ('A','01-JAN-2009');
SQL> insert into mytable values ('B','01-JAN-2009');
SQL> insert into mytable values ('A', '31-DEC-2008');
SQL> insert into mytable values ('B', '31-DEC-2008');
SQL> select usr, dt from mytable
2 where (usr, dt) in
3 ( select usr, max(dt) from mytable group by usr)
4 /
U DT
- ---------
A 01-JAN-09
B 01-JAN-09
Cela fonctionne donc, même si certains des nouveaux trucs mentionnés ailleurs peuvent être plus performants.
Je sais que vous avez demandé Oracle, mais nous l'utilisons maintenant dans SQL 2005:
-- Single Value
;WITH ByDate
AS (
SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE RowNum = 1
-- Multiple values where dates match
;WITH ByDate
AS (
SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE Rnk = 1
Une clause QUALIFY ne serait-elle pas à la fois la plus simple et la meilleure?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
Pour le contexte, ici, sur Teradata, un test de taille décente s’effectue par 17 avec cette version de QUALIFY et par 23 avec la solution 'inline view' / Aldridge.
Je n'ai pas Oracle pour le tester, mais la solution la plus efficace consiste à utiliser des requêtes analytiques. Cela devrait ressembler à ceci:
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
Je pense que vous pouvez vous débarrasser de la requête externe et mettre la requête distincte en interne, mais je ne suis pas sûr. En attendant, je sais que celui-ci fonctionne.
Si vous souhaitez en savoir plus sur les requêtes analytiques, nous vous conseillons de lire http: //www.orafaq .com / node / 55 et http: //www.akadia. com / services / ora_analytic_functions.html . Voici le court résumé.
Sous le capot, les requêtes analytiques trient l’ensemble du jeu de données, puis le traitent de manière séquentielle. Au fur et à mesure que vous le traitez, vous partitionnez le jeu de données en fonction de certains critères, puis, pour chaque ligne, examine une fenêtre (la valeur par défaut de la première valeur de la partition correspond à la ligne actuelle - cette valeur est également la plus efficace) et vous pouvez calculer des valeurs à l'aide d'un nombre de fonctions analytiques (dont la liste est très similaire aux fonctions d'agrégation).
Dans ce cas, voici ce que fait la requête interne. L'ensemble de données complet est trié par UserId puis Date DESC. Ensuite, il le traite en un seul passage. Pour chaque ligne, vous renvoyez l'identifiant utilisateur et la première date vue pour cet identifiant utilisateur (puisque les dates sont triées par DESC, c'est la date maximale). Cela vous donne votre réponse avec des lignes dupliquées. Ensuite, DISTINCT externe écrase les doublons.
Ce n’est pas un exemple particulièrement spectaculaire de requêtes analytiques. Pour gagner beaucoup plus, envisagez de dresser un tableau des recettes financières et de calculer, pour chaque utilisateur et chaque reçu, le total cumulé de leurs paiements. Les requêtes analytiques résolvent cela efficacement. D'autres solutions sont moins efficaces. C'est pourquoi ils font partie du standard SQL 2003. (Malheureusement, Postgres ne les a pas encore. Grrr ...)
Dans Oracle 12c +
, vous pouvez utiliser les requêtes Top n avec la fonction analytique rank
pour atteindre cet objectif même. sans sous-requêtes:
select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Ce qui précède renvoie toutes les lignes avec un maximum de my_date par utilisateur.
Si vous souhaitez une seule ligne avec une date maximale, remplacez le rank
par numéro_rille
:
select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Avec PostgreSQL 8.4 ou version ultérieure, vous pouvez utiliser ceci:
select user_id, user_value_1, user_value_2
from (select user_id, user_value_1, user_value_2, row_number()
over (partition by user_id order by user_date desc)
from users) as r
where r.row_number=1
Utilisez ROW_NUMBER ()
attribuer un classement unique sur Date
décroissant pour chaque ID utilisateur
, puis filtrer sur la première ligne de chaque ID utilisateur
(c'est-à-dire, NOMBRE ROW
= 1).
SELECT UserId, Value, Date
FROM (SELECT UserId, Value, Date,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
FROM users) u
WHERE rn = 1;
Select
UserID,
Value,
Date
From
Table,
(
Select
UserID,
Max(Date) as MDate
From
Table
Group by
UserID
) as subQuery
Where
Table.UserID = subQuery.UserID and
Table.Date = subQuery.mDate
Il suffisait d'écrire un "live". exemple au travail:)
Celui-ci prend en charge plusieurs valeurs pour UserId à la même date.
Colonnes: ID utilisateur, valeur, date
SELECT
DISTINCT UserId,
MAX(Date) OVER (PARTITION BY UserId ORDER BY Date DESC),
MAX(Values) OVER (PARTITION BY UserId ORDER BY Date DESC)
FROM
(
SELECT UserId, Date, SUM(Value) As Values
FROM <<table_name>>
GROUP BY UserId, Date
)
Vous pouvez utiliser FIRST_VALUE au lieu de MAX et le rechercher dans le plan Expliquer. Je n’ai pas eu le temps de jouer avec.
Bien sûr, si vous effectuez une recherche dans d'énormes tables, il est probablement préférable d'utiliser des indications complètes dans votre requête.
select VALUE from TABLE1 where TIME =
(select max(TIME) from TABLE1 where DATE=
(select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
Je pense quelque chose comme ça. (Pardonnez-moi les erreurs de syntaxe; je suis habitué à utiliser HQL à ce stade!)
EDIT: Également mal interprété la question! Corrigé la requête ...
SELECT UserId, Value
FROM Users AS user
WHERE Date = (
SELECT MAX(Date)
FROM Users AS maxtest
WHERE maxtest.UserId = user.UserId
)
Je pense que vous devriez transformer cette variante en requête précédente:
SELECT UserId, Value FROM Users U1 WHERE
Date = ( SELECT MAX(Date) FROM Users where UserId = U1.UserId)
(T-SQL) Obtenez d’abord tous les utilisateurs et leur maxdate. Joignez-vous à la table pour trouver les valeurs correspondantes pour les utilisateurs du nombre maximal de dates.
create table users (userid int , value int , date datetime)
insert into users values (1, 1, '20010101')
insert into users values (1, 2, '20020101')
insert into users values (2, 1, '20010101')
insert into users values (2, 3, '20030101')
select T1.userid, T1.value, T1.date
from users T1,
(select max(date) as maxdate, userid from users group by userid) T2
where T1.userid= T2.userid and T1.date = T2.maxdate
résultats:
userid value date
----------- ----------- --------------------------
2 3 2003-01-01 00:00:00.000
1 2 2002-01-01 00:00:00.000
La réponse ici est Oracle uniquement. Voici une réponse un peu plus sophistiquée dans tout le langage SQL:
Qui a le meilleur résultat global pour les devoirs (somme maximale de points de devoirs)?
SELECT FIRST, LAST, SUM(POINTS) AS TOTAL
FROM STUDENTS S, RESULTS R
WHERE S.SID = R.SID AND R.CAT = 'H'
GROUP BY S.SID, FIRST, LAST
HAVING SUM(POINTS) >= ALL (SELECT SUM (POINTS)
FROM RESULTS
WHERE CAT = 'H'
GROUP BY SID)
Et un exemple plus difficile, qui nécessite quelques explications, pour lequel je n'ai pas le temps nécessaire:
Donnez le livre (ISBN et titre) le plus populaire en 2008, c’est-à-dire qu’il emprunte le plus souvent en 2008.
SELECT X.ISBN, X.title, X.loans
FROM (SELECT Book.ISBN, Book.title, count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan
ON Copy.copyId = Loan.copyId
GROUP BY Book.title) X
HAVING loans >= ALL (SELECT count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan
ON Copy.copyId = Loan.copyId
GROUP BY Book.title);
J'espère que cela aide (n'importe qui) ..:)
Cordialement, Guus
En supposant que la date est unique pour un ID utilisateur donné, voici quelques TSQL:
SELECT
UserTest.UserID, UserTest.Value
FROM UserTest
INNER JOIN
(
SELECT UserID, MAX(Date) MaxDate
FROM UserTest
GROUP BY UserID
) Dates
ON UserTest.UserID = Dates.UserID
AND UserTest.Date = Dates.MaxDate
Je suis assez en retard pour le parti, mais le hack suivant surperformera les sous-requêtes corrélées et toute fonction d'analyse, mais comporte une restriction: les valeurs doivent être converties en chaînes. Donc, cela fonctionne pour les dates, les nombres et autres chaînes. Le code n'a pas l'air bien mais le profil d'exécution est génial.
select
userid,
to_number(substr(max(to_char(date,'yyyymmdd') || to_char(value)), 9)) as value,
max(date) as date
from
users
group by
userid
La raison pour laquelle ce code fonctionne si bien est qu’il n’a besoin que d’analyser le tableau une fois. Il ne nécessite aucun index et, plus important encore, il ne nécessite pas de trier la table, contrairement à la plupart des fonctions d'analyse. Les index vous seront utiles si vous devez filtrer le résultat pour un seul ID utilisateur.
select userid, value, date
from thetable t1 ,
( select t2.userid, max(t2.date) date2
from thetable t2
group by t2.userid ) t3
where t3.userid t1.userid and
t3.date2 = t1.date
IMHO cela fonctionne. HTH
Je pense que cela devrait fonctionner?
Select
T1.UserId,
(Select Top 1 T2.Value From Table T2 Where T2.UserId = T1.UserId Order By Date Desc) As 'Value'
From
Table T1
Group By
T1.UserId
Order By
T1.UserId
Tout d’abord, j’ai mal interprété la question, en suivant la première réponse, voici un exemple complet avec des résultats corrects:
CREATE TABLE table_name (id int, the_value varchar(2), the_date datetime);
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'a','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'b','2/2/2002');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'c','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'d','3/3/2003');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'e','3/3/2003');
-
select id, the_value
from table_name u1
where the_date = (select max(the_date)
from table_name u2
where u1.id = u2.id)
-
id the_value
----------- ---------
2 d
2 e
1 b
(3 row(s) affected)
Ceci s’occupera également des doublons (retourne une ligne pour chaque ID utilisateur):
SELECT *
FROM (
SELECT u.*, FIRST_VALUE(u.rowid) OVER(PARTITION BY u.user_id ORDER BY u.date DESC) AS last_rowid
FROM users u
) u2
WHERE u2.rowid = u2.last_rowid
Je viens de tester cela et il semble fonctionner sur une table de journalisation
select ColumnNames, max(DateColumn) from log group by ColumnNames order by 1 desc
Cela devrait être aussi simple que:
SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Si vous utilisez Postgres, vous pouvez utiliser array_agg
comme
SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid
Je ne connais pas Oracle. C'est ce que je suis venu avec
SELECT
userid,
MAX(adate),
SUBSTR(
(LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)),
0,
INSTR((LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)), ',')-1
) as value
FROM YOURTABLE
GROUP BY userid
Les deux requêtes renvoient les mêmes résultats que la réponse acceptée. Voir SQLFiddles:
Si (UserID, Date) est unique, si aucune date n'apparaît deux fois pour le même utilisateur, alors:
select TheTable.UserID, TheTable.Value
from TheTable inner join (select UserID, max([Date]) MaxDate
from TheTable
group by UserID) UserMaxDate
on TheTable.UserID = UserMaxDate.UserID
TheTable.[Date] = UserMaxDate.MaxDate;
select UserId,max(Date) over (partition by UserId) value from users;
Solution pour MySQL qui n’a pas les concepts de partition KEEP, DENSE_RANK.
select userid,
my_date,
...
from
(
select @sno:= case when @pid<>userid then 0
else @sno+1
end as serialnumber,
@pid:=userid,
my_Date,
...
from users order by userid, my_date
) a
where a.serialnumber=0
Référence: http: // benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html