テーブル:
UserId, Value, Date.
UserId、各UserIdのmax(Date)の値を取得したい。つまり、最新の日付を持つ各UserIdの値です。これをSQLで簡単に行う方法はありますか?(できればOracle)
更新:あいまいさをお詫びします:すべてのユーザーIDを取得する必要があります。ただし、各UserIdについては、そのユーザーが最新の日付を持っている行のみ。
テーブル:
UserId, Value, Date.
UserId、各UserIdのmax(Date)の値を取得したい。つまり、最新の日付を持つ各UserIdの値です。これをSQLで簡単に行う方法はありますか?(できればOracle)
更新:あいまいさをお詫びします:すべてのユーザーIDを取得する必要があります。ただし、各UserIdについては、そのユーザーが最新の日付を持っている行のみ。
回答:
これにより、my_date列の値がそのユーザーIDのmy_dateの最大値と等しいすべての行が取得されます。これにより、最大日付が複数の行にあるユーザーIDの複数の行が取得される場合があります。
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
「分析機能は素晴らしい」
編集:最初のコメントに関して...
「分析クエリと自己結合を使用すると、分析クエリの目的が無効になります」
このコードには自己結合はありません。代わりに、分析関数を含むインラインビューの結果に述語が配置されます。これは、非常に異なる問題であり、完全に標準的な方法です。
「Oracleのデフォルトウィンドウは、パーティションの最初の行から現在の行までです。」
ウィンドウ句は、order by句が存在する場合にのみ適用されます。order by句がない場合、デフォルトではウィンドウ句は適用されず、明示的に指定することはできません。
コードは機能します。
MAX(...) OVER (...)
使用することもできます。ROW_NUMBER() OVER (...)
RANK() OVER (...)
多くの人がサブクエリやベンダー固有の機能を使用してこれを実行しているようですが、サブクエリなしでこの種のクエリを次のように実行することがよくあります。プレーンな標準SQLを使用しているため、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;
つまりt1
、同じUserId
でより大きい日付を持つ他の行が存在しない場所から行をフェッチします。
(識別子 "Date"はSQL予約語であるため、区切り文字で囲みます。)
の場合t1."Date" = t2."Date"
、倍増が表示されます。通常、テーブルにはauto_inc(seq)
キーがありますid
。重複を回避するには、次のように使用できます。
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;
@Farhanからの再コメント:
詳細は次のとおりです。
外部結合はとの結合t1
を試みt2
ます。デフォルトでは、すべての結果がt1
返され、そして場合試合はでありt2
、それも返されます。のt2
特定の行に一致するものがない場合t1
でも、クエリはの行を返し、すべてののプレースホルダーとしてt1
使用NULL
します。t2
の列のます。これが、一般的に外部結合が機能する方法です。
このクエリの秘訣はt2
、同じ userid
とより大きいに 一致するように結合の一致条件を設計することdate
です。その中に行が存在する場合、その行がt2
より大きいdate
場合、t1
比較される行はその中で最大になることはできません。しかし、一致がない場合-つまりは何の行が中に存在しない場合は大きいとして、行よりも、我々は内の行があることを知っている- 最大で行だった与えられたため。date
userid
t2
date
t1
t1
date
userid
それらの場合(一致がない場合)の列は、結合条件で指定された列であってt2
もNULL
-になります。そのWHERE t2.UserId IS NULL
ため、を使用するのはそのためです。date
指定されたでより大きい行が見つからなかった場合を検索しているからですuserid
。
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
FROM table
GROUP BY userid
正確な列名はわかりませんが、次のようになります。
ユーザーID、値を選択 ユーザーu1から where date =(select max(date) ユーザーu2から ここで、u1.userid = u2.userid)
仕事をしていないので、手元にOracleはありませんが、OracleではIN句で複数の列を照合できるため、少なくとも相関サブクエリを使用するオプションを回避する必要があることを思い出しているようです。考え。
次のようなものかもしれません(列リストを括弧で囲む必要があるかどうか覚えていません):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
編集:実際に試してみました:
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
そのため、他の場所で言及された新しいおかしなもののいくつかはよりパフォーマンスが高いかもしれませんが、それは機能します。
あなたがOracleを要求したのは知っていますが、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
テストするOracleはありませんが、最も効率的なソリューションは分析クエリを使用することです。次のようになります。
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
私はあなたが外側のクエリを取り除き、内側に区別を付けることができると思いますが、私にはわかりません。その間、私はこれが機能することを知っています。
分析クエリについて知りたい場合は、 、http: //www.orafaq.com/node/55およびhttp://www.akadia.com/services/ora_analytic_functions.htmlをします。ここに短い要約があります。
分析クエリでは、データセット全体を並べ替えてから、順次処理します。それを処理するときに、特定の基準に従ってデータセットを分割し、各行についてウィンドウを確認し(デフォルトは現在の行への分割の最初の値です-このデフォルトも最も効率的です)、分析関数の数(そのリストは集計関数と非常に似ています)。
この場合、内部クエリは次のようになります。データセット全体は、UserId、次にDate DESCでソートされます。その後、1つのパスで処理します。各行について、UserIdとそのUserIdで最初に確認された日付を返します(日付がDESCでソートされているため、これが最大日付です)。これにより、重複した行で答えが得られます。次に、外側のDISTINCTが重複を押しつぶします。
これは、分析クエリの特に素晴らしい例ではありません。はるかに大きな勝利を収めるには、財務領収書の表を取り、各ユーザーと領収書について、彼らが支払った金額の現在の合計を計算することを検討してください。分析クエリはそれを効率的に解決します。他のソリューションは効率が良くありません。これが2003 SQL標準の一部である理由です。(残念ながらPostgresにはまだありません。Grrr...)
QUALIFY句は最も単純であり、最良でもありませんか?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
コンテキストについては、ここのTeradataでは、この適度なサイズテストが、このQUALIFYバージョンで17秒、「インラインビュー」/ Aldridgeソリューション#1で23秒で実行されています。
rank()
つながりがある場合は注意してください。最終的には複数になる可能性がありrank=1
ます。row_number()
本当に1つのレコードだけが返されるようにしたい場合は、使用した方がよいでしょう。
QUALIFY
条項はTeradataに固有のものであることに注意してください。Oracleでは(少なくとも)クエリをネストWHERE
し、ラッピングselectステートメントの句を使用してフィルター処理する必要があります(おそらく、パフォーマンスに少し影響があると思います)。
ではOracle 12c+
、上位nクエリと分析関数rank
を使用して、サブクエリなしで非常に簡潔にこれを実現できます。
select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
上記は、ユーザーごとに最大my_dateを持つすべての行を返します。
あなたが最大の日付を持つ唯一の1行をしたい場合は、交換するrank
と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;
を使用ROW_NUMBER()
してDate
、各の降順で一意のランキングを割り当て、それぞれUserId
の最初の行にフィルターをかけますUserId
(つまり、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;
仕事で「ライブ」の例を書く必要がありました:)
これは、同じ日付のUserIdの複数の値をサポートします。
列: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
)
MAXの代わりにFIRST_VALUEを使用して、Explain Planでルックアップできます。遊ぶ時間はありませんでした。
もちろん、巨大なテーブルを検索する場合は、クエリでFULLヒントを使用する方が良いでしょう。
このようなものだと思います。(構文の誤りを許してください。この時点でHQLを使用することに慣れています!)
編集:質問も誤解してください!クエリを修正しました...
SELECT UserId, Value
FROM Users AS user
WHERE Date = (
SELECT MAX(Date)
FROM Users AS maxtest
WHERE maxtest.UserId = user.UserId
)
(T-SQL)最初にすべてのユーザーとその最大日付を取得します。テーブルと結合して、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
結果:
userid value date
----------- ----------- --------------------------
2 3 2003-01-01 00:00:00.000
1 2 2002-01-01 00:00:00.000
ここでの答えはOracleのみです。これは、すべてのSQLにおけるもう少し高度な回答です。
誰が全体の宿題の最高の結果(宿題ポイントの最大合計)を持っていますか?
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)
そして、いくつかの説明が必要な、もっと時間のかかる例を示します。
2008年に最も人気のある本(ISBNとタイトル)、つまり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);
これが(誰でも)役立つことを願っています.. :)
よろしく、Guus
私はパーティーにかなり遅れましたが、次のハックは相関サブクエリとすべての分析関数の両方を上回りますが、制限が1つあります。値を文字列に変換する必要があります。したがって、日付、数値、その他の文字列に対して機能します。コードは良く見えませんが、実行プロファイルは素晴らしいです。
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
このコードがうまく機能する理由は、テーブルを1回スキャンするだけでよいためです。インデックスを必要とせず、最も重要なのは、ほとんどの分析機能が行うように、テーブルをソートする必要がないことです。ただし、単一のユーザーIDの結果をフィルタリングする必要がある場合は、インデックスが役立ちます。
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
私はこれが機能します。HTH
最初に私が質問を読み間違えて、上の答えに続いて、正しい結果の完全な例を示します。
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)
パーティションKEEP、DENSE_RANKの概念を持たないMySQLのソリューション。
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
リファレンス:http : //benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html
Postgresを使用してarray_agg
いる場合は、次のように使用できます
SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid
私はOracleに慣れていません。これは私が思いついたものです
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
どちらのクエリも、受け入れられた回答と同じ結果を返します。SQLFiddlesを参照してください。
(UserID、Date)が一意である場合、つまり同じユーザーに対して2度日付が表示されない場合:
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;