it-swarm-eu.dev

Načíst řádek, který má hodnotu Max pro sloupec

Stůl: 

UserId, Value, Date.

Chci získat UserId, hodnotu pro max (datum) pro každého UserId. To znamená hodnota pro každý UserId, který má poslední datum. Existuje způsob, jak to udělat jednoduše v SQL? (Přednostně Oracle)

Aktualizace: Ospravedlňuje se za jakoukoli nejednoznačnost: Musím dostat VŠECHNY UŽIVATELE. Ale pro každý UserId, pouze ten řádek, kde má uživatel poslední datum.

521
Umang

Tím se načtou všechny řádky, pro které je hodnota sloupce my_date rovna maximální hodnotě my_date pro toto ID uživatele. To může načíst více řádků pro ID uživatele, kde maximální datum je na více řádcích.

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

"Analytické funkce rock"

Upravit: S ohledem na první komentář ...

"pomocí analytických dotazů a self-join porazí účel analytických dotazů"

V tomto kódu není žádné připojení. Tam je místo toho predikát umístěný na výsledku inline pohledu, který obsahuje analytickou funkci - velmi odlišná záležitost, a úplně standardní praxe.

"Výchozí okno v Oracle je od prvního řádku v oddílu k aktuálnímu"

Klauzule okna je použitelná pouze za přítomnosti objednávky podle klauzule. Bez příkazu podle klauzule není ve výchozím nastavení použita žádná klauzule okna a nelze ji explicitně zadat.

Kód funguje.

366
David Aldridge

Vidím, že mnoho lidí používá k tomuto účelu poddotazy nebo jiné funkce specifické pro dodavatele, ale často používám tento druh dotazu bez poddotazů následujícím způsobem. Používá prostý, standardní SQL, takže by měl fungovat v jakékoli značce RDBMS.

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;

Jinými slovy: načtěte řádek z t1, kde neexistuje žádný jiný řádek se stejným UserId a větším Date.

(Identifikátor "Datum" vložím do oddělovače, protože je to vyhrazené slovo SQL.)

V případě, že se objeví t1."Date" = t2."Date", objeví se zdvojení. Tabulky mají obvykle auto_inc(seq) klíč, např. id. K zamezení zdvojení lze použít následující:

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;

Komentovat uživatele @Farhan:

Zde je podrobnější vysvětlení:

Vnější spojení se pokouší připojit k t1 s t2. Ve výchozím nastavení jsou všechny výsledky t1 vráceny, a pokud existuje shoda v t2, je také vrácena. Pokud v daném řádku t2 neexistuje žádná shoda v t1, pak dotaz stále vrací řádek t1 a používá NULL jako zástupný symbol pro všechny sloupce t2 '. Takhle funguje vnější spojení obecně.

Trik v tomto dotazu spočívá v navržení odpovídající podmínky spojení tak, aby se t2 muselo shodovat s same userid a a large date. Myšlenka, zda existuje řádek v t2, který má větší date, pak řádek v t1 je porovnán s nemůže být největší date pro tuto userid. Pokud však neexistuje žádná shoda - tj. Pokud neexistuje žádný řádek v t2 s větším date než řádek v t1 - víme, že řádek v t1 byl řádek s největším date pro dané userid.

V těchto případech (když neexistuje žádná shoda) budou sloupce t2NULL - dokonce sloupce zadané ve podmínce spojení. Proto používáme WHERE t2.UserId IS NULL, protože hledáme případy, kdy nebyl nalezen žádný řádek s větším date pro dané userid.

418
Bill Karwin
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid
150
Dave Costa

Neznám vaše přesné názvy sloupců, ale bylo by to něco takového:

 vyberte userid, hodnotu 
 od uživatelů u1 
 kde date = (vyberte max (datum) 
 od uživatelů u2 
 kde u1.userid = u2 .uživatelské ID)
47
Steve K

Nejsem v práci, nemám Oracle k ruce, ale zdá se, že si vzpomínám, že Oracle umožňuje více sloupců, které mají být uzavřeny v klauzuli IN, která by měla alespoň vyhnout možnosti, které používají korelované poddotaz, který je zřídka dobrý myšlenka.

Možná něco takového (možná si nepamatuje, zda by měl být seznam sloupců v závorkách nebo ne):

SELECT * 
FROM MyTable
WHERE (User, Date) IN
  ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)

EDIT: Prostě to zkusil:

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

Funguje to, i když některé nové věci, které jsou zmíněny jinde, mohou být výkonnější.

35
Mike Woodhouse

Vím, že jste požádali o Oracle, ale v SQL 2005 to nyní používáme:


-- 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
13
mancaus

Nemám Oracle otestovat, ale nejefektivnějším řešením je použití analytických dotazů. Mělo by to vypadat takto:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

Domnívám se, že se můžete zbavit vnějšího dotazu a odlišit vnitřní, ale nejsem si jistý. Mezitím vím, že tohle funguje.

Pokud se chcete dozvědět více o analytických dotazech, doporučuji vám přečíst http://www.orafaq.com/node/55http://www.akadia.com/services/ora_analytic_functions.html. Zde je stručné shrnutí.

Pod analytickými dotazy kapoty třídíme celou datovou sadu a pak je zpracováváme postupně. Při zpracování rozdělujete datovou množinu podle určitých kritérií a pak se pro každý řádek dívá na nějaké okno (výchozí hodnota na první hodnotu v oddílu na aktuální řádek - tato výchozí hodnota je také nejefektivnější) a hodnoty lze vypočítat pomocí počet analytických funkcí (jejichž seznam je velmi podobný agregačním funkcím).

V tomto případě se jedná o to, co dělá vnitřní dotaz. Celá sada dat je řazena podle UserId a Date DESC. Pak to zpracovává v jednom průchodu. Pro každý řádek vrátíte UserId a první datum, které je pro toto UserId zobrazeno (od data jsou seřazeny DESC, to je maximální datum). To vám dává odpověď s duplikovanými řádky. Pak vnější DISTINCT rozmačká duplikáty.

Nejedná se o mimořádný příklad analytických dotazů. Za mnohem větší výhru uvažujte o zaplacení tabulky finančních příjmů a výpočtu pro každého uživatele a přijetí, běžící součet toho, co zaplatili. Analytické dotazy to řeší efektivně. Jiná řešení jsou méně efektivní. Proto jsou součástí standardu SQL 2003. (Bohužel Postgres ještě nemá. Grrr ...)

6
user11318

Nebyla by klauzule QUALIFY nejjednodušší a nejlepší?

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

Pro kontext, na Teradata tady slušné velikostní test tohoto běží v 17s s touto verzí QUALIFY a ve 23s s 'inline pohled'/Aldridge řešení # 1.

6
wcw

S PostgreSQL 8.4 nebo novějším můžete použít toto:

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
5
Cito

VOracle 12c+, můžete použít Top n dotazy spolu s analytickou funkcí rank k dosažení tohoto velmi stručně bez subqueries:

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

Výše uvedené vrací všechny řádky s maximem my_date na uživatele. 

Chcete-li pouze jeden řádek s maximálním datem, nahraďte rank znakem 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; 
5
Gurwinder Singh
select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
3
nouky

Použijte ROW_NUMBER() pro přiřazení jedinečného pořadí pro sestupné Date pro každé UserId, pak filtrujte do prvního řádku pro každý UserId (tj. 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;
3
markusk

Prostě musel napsat "živý" příklad při práci :)

Tento podporuje více hodnot pro UserId v stejný datum.

Sloupce: 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
)

Namísto MAX můžete použít FIRST_VALUE a podívat se do plánu vysvětlování. Neměl jsem čas si s ním hrát.

Samozřejmě, pokud prohledáváte obrovské tabulky, je to pravděpodobně lepší, pokud ve svém dotazu použijete plné rady.

3
Truper
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  
3
Aheho

Odpověď zde je pouze Oracle. Zde je trochu sofistikovanější odpověď ve všech SQL:

Kdo má nejlepší celkový domácí výsledek (maximální součet domácích úkolů)?

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)

A obtížnější příklad, který potřebuje nějaké vysvětlení, pro které nemám čas atm:

Uveďte knihu (ISBN a titul), která je nejoblíbenější v roce 2008, tj. Která je nejčastěji vypůjčena v roce 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);

Doufám, že to pomůže (někdo) .. :)

Pozdravy, Guus

2
Guus

Myslím, že něco takového. (Odpusťte mi všechny chyby syntaxe; v tomto bodě jsem zvyklý používat HQL!)

EDIT: Také nesprávně vyložil otázku! Oprava dotazu ...

SELECT UserId, Value
FROM Users AS user
WHERE Date = (
    SELECT MAX(Date)
    FROM Users AS maxtest
    WHERE maxtest.UserId = user.UserId
)
2
jdmichal

(T-SQL) Nejdříve si všichni uživatelé a jejich maxdate. Spojte se s tabulkou a vyhledejte odpovídající hodnoty pro uživatele na maxdatech.

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

výsledek:

userid      value       date                                    
----------- ----------- -------------------------- 
2           3           2003-01-01 00:00:00.000
1           2           2002-01-01 00:00:00.000
2
boes

měli byste tuto variantu provést na předchozí dotaz:

SELECT UserId, Value FROM Users U1 WHERE 
Date = ( SELECT MAX(Date)    FROM Users where UserId = U1.UserId)
2
stefano m

Za předpokladu, že datum je jedinečné pro dané ID uživatele, zde je několik 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 
2
marc

Jsem docela pozdě na párty, ale následující hack překoná jak korelované poddotazy, tak všechny analytické funkce, ale má jedno omezení: hodnoty musí převést na řetězce. Tak to funguje pro data, čísla a další řetězce. Kód nevypadá dobře, ale profil provádění je skvělý. 

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

Důvodem, proč tento kód funguje tak dobře, je to, že stačí pouze jednou prohlédnout tabulku. Nevyžaduje žádné indexy, a co je nejdůležitější, není třeba třídit tabulku, kterou dělá většina analytických funkcí. Indexy vám pomohou, pokud je třeba filtrovat výsledek pro jediné ID uživatele.

2

Pokud používáte Postgres, můžete použít array_agg jako

SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid

Nejsem obeznámen s Oracle. To je to, s čím jsem přišel

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 

Oba dotazy vrátí stejné výsledky jako přijatá odpověď. Viz SQLFiddles:

  1. Přijatá odpověď
  2. Moje řešení s Postgresem
  3. Moje řešení s Oracle
1
Bruno Calza

Mělo by to být tak jednoduché jako:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
1
Valerion

Nejprve zkuste chybně odpovědět na otázku, následujíc top odpověď, zde je kompletní příklad se správnými výsledky:

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)
1
KyleLanser

Také se postará o duplikáty (vrátí se jeden řádek pro každé jméno uživatele):

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
1
na43251

Jen to otestovalo a zdá se, že to funguje na logovací tabulce

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc
1
Mauro
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 to funguje. HTH 

1
Zsolt Botykai

Myslím, že by to mělo fungovat?

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
1
GateKiller

check tento odkaz pokud vaše dotazy vypadají podobně jako na této stránce, pak bych vám doporučil následující dotaz, který poskytne řešení pro tento odkaz

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;

poskytne přesné výsledky týkající se tohoto odkazu

0
Smart003
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;
0
praveen

Je-li (UserID, Date) jedinečné, tj. Pro stejného uživatele se dvakrát nezobrazí žádné datum, pak:

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;
0
finnw

Použijte kód:

select T.UserId,T.dt from (select UserId,max(dt) 
over (partition by UserId) as dt from t_users)T where T.dt=dt;

To načte výsledky bez ohledu na duplicitní hodnoty pro UserId. Pokud je vaše UserId jedinečné, je to jednodušší:

select UserId,max(dt) from t_users group by UserId;
0
Natty
select   UserId,max(Date) over (partition by UserId) value from users;
0
Amitābha

Řešení pro MySQL, které nemá koncepty oddílu 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

Odkaz: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html

0
Ben Lin