質問

テーブル:

UserId, Value, Date.

UserId、各UserIdのmax(Date)の値を取得したいと考えています。つまり、最新の日付を持つ各 UserId の値です。SQLでこれを簡単に行う方法はありますか?(できればOracle)

アップデート: 曖昧な点があることをお詫びします:すべての UserId を取得する必要があります。ただし、各 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 句がない場合、デフォルトではウィンドウ句が適用されず、明示的に指定することもできません。

コードは機能します。

他のヒント

多くの人がこれを行うためにサブクエリやベンダー固有の機能を使用しているのを見かけますが、私は次の方法でサブクエリを使用せずにこの種のクエリを実行することがよくあります。プレーンな標準 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 からの再コメント:

さらに詳しい説明は次のとおりです。

外部結合が結合を試行します t1t2. 。デフォルトでは、次のすべての結果が 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

正確な列名はわかりませんが、次のようなものになります。

    select userid, value
      from users u1
     where date = (select max(date)
                     from users u2
                    where 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

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 秒で実行されます。

Oracle を使用してテストすることはできませんが、最も効率的な解決策は分析クエリを使用することです。次のようになります。

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

外側のクエリを削除して内側にdistinctを置くことができるのではないかと思いますが、わかりません。その間、これが機能することがわかりました。

分析クエリについて知りたい場合は、以下を読むことをお勧めします。 http://www.orafaq.com/node/55 そして http://www.akadia.com/services/ora_analytic_functions.html. 。これが短い要約です。

内部では、分析クエリによってデータセット全体が並べ替えられ、順番に処理されます。処理するときに、特定の基準に従ってデータセットを分割し、行ごとにいくつかのウィンドウ (デフォルトは現在の行へのパーティション内の最初の値になります。このデフォルトが最も効率的です) を調べ、分析関数の数 (そのリストは集計関数とよく似ています)。

この場合、内部クエリの動作は次のとおりです。データセット全体は、UserId、次に日付 DESC で並べ替えられます。その後、それを 1 パスで処理します。行ごとに、UserId とその UserId に対して最初に表示された日付を返します (日付は DESC 順にソートされているため、これが最大日付になります)。これにより、重複した行を含む答えが得られます。次に、外側の DISTINCT によって重複が削除されます。

これは、分析クエリの特に素晴らしい例ではありません。より大きな利益を得るには、財務上の領収書の表を作成し、各ユーザーと領収書、つまり支払った金額の現在までの合計を計算することを検討してください。分析クエリはそれを効率的に解決します。他のソリューションは効率が低くなります。このため、これらは 2003 SQL 標準の一部となっています。(残念ながら、Postgres にはまだそれらがありません。うーん...)

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 行のみが必要な場合は、 rankrow_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; 

PostgreSQL 8.4 以降では、これを使用できます。

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

使用 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;
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  

仕事で「ライブ」サンプルを書く必要がありました:)

これは、UserId の複数の値をサポートします。 同じ 日付。

列:ユーザーID、値、日付

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 ヒントを使用した方がよいでしょう。

select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))

こんなことを思います。(構文上の間違いはご容赦ください。この時点では HQL の使用には慣れています!)

編集:質問も読み間違えてます!クエリを修正しました...

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

このバリアントを前のクエリに変更する必要があると思います。

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

(T-SQL) まず、すべてのユーザーとその最大日付を取得します。テーブルと結合して、最大日付のユーザーに対応する値を見つけます。

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 年に最も人気のある本、つまり 2008 年に最もよく借りられた本 (ISBN とタイトル) を教えてください。

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

Date が特定の UserID に対して一意であると仮定すると、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 

私はパーティーにかなり遅れてしまいましたが、次のハックは相関サブクエリと分析機能の両方よりも優れたパフォーマンスを発揮しますが、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

これはうまくいくはずだと思いますか?

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

まず、質問を読み間違えて、一番上の回答に従ってみてください。正しい結果が得られた完全な例は次のとおりです。

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)

これにより、重複も処理されます (user_id ごとに 1 行が返されます)。

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

これをテストしたところ、ロギングテーブルで動作するようです

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc

これは次のように単純である必要があります。

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

Postgres を使用している場合は、次を使用できます array_agg のように

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

オラクルについては詳しくありません。これが私が思いついたものです

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 を参照してください。

  1. 受け入れられた回答
  2. Postgres を使用した私のソリューション
  3. Oracle を使用した私のソリューション

(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;

パーティション 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-what-have-maxmin-value-in.html

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top