Table:
UserId, Value, Date.
Je veux obtenir le UserId, valeur pour le max (Date) pour chaque UserId. C'est-à-dire, la valeur pour chaque ID utilisateur ayant la dernière date. Y at-il un moyen de faire cela simplement en SQL? (Oracle de préférence)
Mise à jour: / Toutes mes excuses pour toute ambiguïté: je dois obtenir TOUS les UserIds. Mais pour chaque ID utilisateur, seule cette ligne où cet utilisateur a la date la plus récente.
Cela permettra d'extraire 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 lorsque 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
"Rock fonctions analytiques"
Edit: En ce qui concerne le premier commentaire ...
"utiliser des requêtes analytiques et une auto-jointure annule le but des requêtes analytiques"
Il n'y a pas d'auto-jointure dans ce code. Il y a plutôt un prédicat placé sur le résultat de la vue en ligne qui contient la fonction analytique - un sujet très différent et une pratique tout à fait standard.
"La fenêtre par défaut dans Oracle va de la première ligne de la partition à celle en cours"
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 spécifiée explicitement.
Le code fonctionne.
Je vois que beaucoup de gens utilisent des sous-requêtes ou des fonctionnalités spécifiques à un fournisseur, mais je fais souvent ce type de requête sans sous-requêtes de la manière suivante. Il utilise SQL simple et standard, il devrait donc fonctionner dans toutes les marques 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
où aucune autre ligne n'existe avec la même UserId
et une date supérieure.
(J'ai mis l'identifiant "Date" dans les délimiteurs car c'est un mot réservé SQL.)
Dans le cas où t1."Date" = t2."Date"
, le doublage apparaît. Les tables ont généralement la touche auto_inc(seq)
, par exemple id
. Pour éviter de doubler, vous pouvez utiliser les méthodes suivantes:
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;
Re commentaire de @Farhan:
Voici une explication plus détaillée:
Une jointure externe tente de rejoindre 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
comme espace réservé pour toutes les colonnes de t2
. C'est comme ça que les jointures externes fonctionnent 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 userid
et à un supérieur date
. L'idée est que si une ligne existe dans t2
qui a une plus grande date
, alors la ligne dans t1
est comparée à ne peut pas être la plus grande date
pour cette userid
. Mais s'il n'y a pas de correspondance - c'est-à-dire s'il n'y a pas de ligne dans t2
avec une plus grande date
que la ligne dans t1
- nous savons que la ligne dans t1
était la rangée avec le plus grand date
pour la userid
donnée.
Dans ces cas (lorsqu'il n'y a pas 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 plus grande date
pour la userid
donnée.
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:
Sélectionnez ID utilisateur, valeur des utilisateurs u1 où date = (sélectionnez max (date) parmi les utilisateurs u2 où 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, ce qui est rarement un bon idée.
Quelque chose comme ça, peut-être (je ne me souviens plus si la liste des colonnes doit être entre parenthèses ou non):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
EDIT: 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, bien que certains des nouveaux trucs mentionnés ailleurs puissent être plus performants.
Je sais que vous avez demandé Oracle, mais dans SQL 2005, nous utilisons maintenant ceci:
-- 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
Je n'ai pas Oracle pour le tester, mais la solution la plus efficace consiste à utiliser des requêtes analytiques. Ça devrait ressembler a quelque chose comme ca:
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
Je suppose que vous pouvez vous débarrasser de la requête externe et mettre distincte sur la requête 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, je vous suggère 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 agrégées).
Dans ce cas, voici ce que fait la requête interne. L'ensemble de données entier 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, l’extérieur de DISTINCT é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 ...)
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'exécute en 17 avec cette version de QUALIFY et en 23 avec la solution 'inline view'/Aldridge # 1.
Avec PostgreSQL 8.4 ou ultérieur, 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
DansOracle 12c+
, vous pouvez utiliser les requêtes Top n avec la fonction analytique rank
pour y parvenir très précisément 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 la rank
par row_number
:
select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
select VALUE from TABLE1 where TIME =
(select max(TIME) from TABLE1 where DATE=
(select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
Utilisez ROW_NUMBER()
pour attribuer un classement unique sur Date
décroissant pour chaque UserId
, puis filtrez vers la première ligne pour chaque UserId
(c'est-à-dire, ROW_NUMBER
= 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;
Il me suffisait d'écrire un exemple "en direct" au travail :)
Celui-ci supporte plusieurs valeurs pour UserId le même date.
Colonnes: UserId, Value, 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
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
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 le plus emprunté 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
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
)
(T-SQL) Obtenez d’abord tous les utilisateurs et leur date maximale. Joindre avec la table pour trouver les valeurs correspondantes pour les utilisateurs sur les maxdates.
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
je pense que vous devriez faire cette variante à la requête précédente:
SELECT UserId, Value FROM Users U1 WHERE
Date = ( SELECT MAX(Date) FROM Users where UserId = U1.UserId)
En supposant que 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. Cela fonctionne donc pour les dates, les nombres et les 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 la table 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.
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:
Cela devrait être aussi simple que:
SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Essayez 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)
Cela s’occupera aussi des doublons (retourne une ligne pour chaque 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 cela semble fonctionner sur une table de journalisation
select ColumnNames, max(DateColumn) from log group by ColumnNames order by 1 desc
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
vérifier ce lien si vos questions semblent similaires à cette page, je vous suggérerais la requête suivante qui vous donnera la solution
select distinct sno,item_name,max(start_date) over(partition by sno),max(end_date) over(partition by sno),max(creation_date) over(partition by sno),
max(last_modified_date) over(partition by sno)
from uniq_select_records
order by sno,item_name asc;
sera donné des résultats précis liés à ce lien
SELECT a.userid,a.values1,b.mm
FROM table_name a,(SELECT userid,Max(date1)AS mm FROM table_name GROUP BY userid) b
WHERE a.userid=b.userid AND a.DATE1=b.mm;
Si (UserID, Date) est unique, c’est-à-dire qu’aucune date ne s'affiche deux fois pour le même utilisateur, puis:
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;
Utilisez le code:
select T.UserId,T.dt from (select UserId,max(dt)
over (partition by UserId) as dt from t_users)T where T.dt=dt;
Cela va récupérer les résultats, indépendamment des valeurs en double pour UserId. Si votre UserId est unique, il devient plus simple:
select UserId,max(dt) from t_users group by UserId;
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