LoginSignup
3
1

SQL:ユーザーごとに最新レコードが欲しい

Last updated at Posted at 2021-01-20

背景と状況

データを履歴で持っているテーブルで最新レコードだけが欲しい場合があります。例えば、体重を管理しているテーブルなら最後に入力した体重を表示したい。
※IBM Db2 V11.5 Windowsで確認していますが他のDBMSでもできるはずです。

やりたいこと

ユーザーごとに最新のレコードだけを取得したい。SQLでグループごとに最新レコードを出力します。例では最新の体重一覧をユーザー単位で出力します。

体重管理テーブル wt_tbl
<-------------主キー----------->
USR_ID      WT_DATE    WT_TIME  WT_KG
----------- ---------- -------- -------
          1 2020-09-12 08:23:14    53.3
          1 2020-09-13 08:11:19    53.6
          1 2020-09-14 08:03:11    53.5  <-- 最新レコード
          2 2020-09-13 08:45:01    73.3
          2 2020-09-14 08:42:31    73.6
          2 2020-09-14 15:55:21    73.9  <-- 最新レコード
          3 2020-09-07 09:32:19    63.7
          3 2020-09-08 08:37:39    63.3
          3 2020-09-10 08:12:28    64.1  <-- 最新レコード

制約条件

  • テーブル設計は変更できない
  • レコードが多いため、パフォーマンスを考慮したい

副照会を使う方法

副照会のなかでgroup byを使ってユーザーごとに最新レコードをMax関数で特定、主キーでジョインします。

latestSubQuery.sql
SELECT w.* 
FROM   wt_tbl w 
       INNER JOIN(SELECT usr_id, 
                         Max(TIMESTAMP(wt_date, wt_time)) AS max_timestamp 
                  FROM   wt_tbl 
                  GROUP  BY usr_id) m 
               ON w.usr_id = m.usr_id 
                  AND w.wt_date = DATE(m.max_timestamp) 
                  AND w.wt_time = TIME(m.max_timestamp) 
ORDER  BY usr_id; 

ユーザーごとに最新レコードのみが出力されます。

実行結果
F:\>db2 -tvf latestSubQuery.sql
SELECT w.* FROM   wt_tbl w INNER JOIN(SELECT usr_id, Max(TIMESTAMP(wt_date, wt_time)) AS max_timestamp FROM   wt_tbl GROUP  BY usr_id) m ON w.usr_id = m.usr_id AND w.wt_date = DATE(m.max_timestamp) AND w.wt_time = TIME(m.max_timestamp) ORDER  BY usr_id

USR_ID      WT_DATE    WT_TIME  WT_KG
----------- ---------- -------- -------
          1 2020-09-14 08:03:11    53.5
          2 2020-09-14 15:55:21    73.9
          3 2020-09-10 08:12:28    64.1

  3 レコードが選択されました。

Rank関数を使用する方法

ユーザー単位に日時でランキングをつけて、Where句で1位だけをとります。

latestRank.sql
SELECT DISTINCT usr_id, wt_date, wt_time, wt_kg 
FROM   (SELECT *, 
               RANK() 
                 OVER( 
                   PARTITION BY usr_id 
                   ORDER BY wt_date desc, wt_time desc) AS RANK 
        FROM   wt_tbl) 
WHERE  RANK = 1 
ORDER  BY usr_id; 

副照会と同じ結果になります。

実行結果
F:\>db2 -tvf latestRank.sql
SELECT DISTINCT usr_id, wt_date, wt_time, wt_kg FROM   (SELECT *, RANK() OVER( PARTITION BY usr_id ORDER BY wt_date desc, wt_time desc) AS RANK FROM   wt_tbl) WHERE  RANK = 1 ORDER  BY usr_id

USR_ID      WT_DATE    WT_TIME  WT_KG
----------- ---------- -------- -------
          1 2020-09-14 08:03:11    53.5
          2 2020-09-14 15:55:21    73.9
          3 2020-09-10 08:12:28    64.1

  3 レコードが選択されました。

First_value関数を使用する方法

ユーザー単位に日時順で最初の値をとります。

latestFirstValue.sql
SELECT DISTINCT 
    usr_id, 
    FIRST_VALUE(wt_date) OVER(PARTITION BY usr_id ORDER BY wt_date desc, wt_time desc) AS wt_date,
    FIRST_VALUE(wt_time) OVER(PARTITION BY usr_id ORDER BY wt_date desc, wt_time desc) AS wt_time,
    FIRST_VALUE(wt_kg)   OVER(PARTITION BY usr_id ORDER BY wt_date desc, wt_time desc) AS wt_kg
FROM   wt_tbl
ORDER  BY usr_id; 

副照会と同じ結果になります。

実行結果
F:\>db2 -tf latestFirstValue.sql

USR_ID      WT_DATE    WT_TIME  WT_KG
----------- ---------- -------- -------
          1 2020-09-14 08:03:11    53.5
          2 2020-09-14 15:55:21    73.9
          3 2020-09-10 08:12:28    64.1

  3 レコードが選択されました。

結果は同じですがパフォーマンスはどうでしょうか。

注意:NULLがorder byの列にある場合

  • NULLを最小値とみなすか最大値とみなすかはDBMSによって異なる
  • Db2はNULLを最大値とみなす
  • Db2でNULLを最小値として扱いたい場合、ORDER BY wt_date DESC NULLS LAST と記述

アクセスプラン

テーブルにダミーレコードを1万件ほどインサートして、SQLのアクセスプラン(実行計画)を比較しましょう。アクセスプランはDb2のエクスプレインで出力しています。

副照会のアクセスプラン latestSubQuery.sql
Access Plan:
-----------
	Total Cost: 		118.921
	Query Degree:		1


            Rows 
           RETURN
           (   1)
            Cost 
             I/O 
             |
            1120 
           TBSCAN
           (   2)
           118.921 
             122 
             |
            1120 
           SORT  
           (   3)
           118.899 
             122 
             |
            1120 
           ^HSJOIN
           (   4)
           118.733 
             122 
         /---+----\
      10009        10000 
     TBSCAN       GRPBY 
     (   5)       (   6)
     57.6302      59.823 
       61           61 
       |            |
      10009        10009 
 TABLE: DB2ADMIN  TBSCAN
     WT_TBL       (   7)
       Q5         59.6181 
                    61 
                    |
                   10009 
                  SORT  
                  (   8)
                  59.421 
                    61 
                    |
                   10009 
                  TBSCAN
                  (   9)
                  57.6302 
                    61 
                    |
                   10009 
              TABLE: DB2ADMIN
                  WT_TBL
                    Q1

副照会のコストは約118です。

Rank関数のアクセスプラン latestRank.sql
Access Plan:
-----------
	Total Cost: 		61.7188
	Query Degree:		1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
     400.36 
     FILTER
     (   2)
     61.7188 
       61 
       |
      10009 
     TBSCAN
     (   3)
     60.9085 
       61 
       |
      10009 
     SORT  
     (   4)
     60.7114 
       61 
       |
      10009 
     TBSCAN
     (   5)
     57.6302 
       61 
       |
      10009 
 TABLE: DB2ADMIN
     WT_TBL
       Q1

Rank関数のコストは約61です。

Fitst_value関数のアクセスプラン latestFirstValue.sql
Access Plan:
-----------
	Total Cost: 		66.3212
	Query Degree:		1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
      10009 
     TBSCAN
     (   2)
     66.3212 
       61 
       |
      10009 
     SORT  
     (   3)
     66.124 
       61 
       |
      10009 
     TBSCAN
     (   4)
     60.9424 
       61 
       |
      10009 
     SORT  
     (   5)
     60.7452 
       61 
       |
      10009 
     TBSCAN
     (   6)
     57.6302 
       61 
       |
      10009 
 TABLE: DB2ADMIN
     WT_TBL
       Q1

Fitst_value関数のコストは約66です。

パフォーマンス比較

SQL コスト
副照会 118
Rank関数 61
First_value関数 66
  • Rank関数とFirst_value関数のコストは副照会に比べ約1/2
  • 副照会はジョインが発生する分、パフォーマンスが悪化

まとめ

  • グループごとに最新レコードを取る方法:副照会、Rank関数、Fitst_value関数
  • Rank関数とFitst_value関数がパフォーマンス的に優位
  • Fitst_value関数の記述がシンプル

参考

以下の書籍を参考にしています。
翔泳社 SQLパズル

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1