web-dev-qa-db-ja.com

条件付きリード/ラグ関数PostgreSQL?

私はこのようなテーブルを持っています:

_Name   activity  time

user1  A1        12:00
user1  E3        12:01
user1  A2        12:02
user2  A1        10:05
user2  A2        10:06
user2  A3        10:07
user2  M6        10:07
user2  B1        10:08
user3  A1        14:15
user3  B2        14:20
user3  D1        14:25
user3  D2        14:30
_

今、私はこのような結果が必要です:

_Name   activity  next_activity

user1  A2        NULL
user2  A3        B1
user3  A1        B2
_

すべてのユーザーについて、グループAの最後のアクティビティと、グループBの次に発生したアクティビティの種類を確認したいと思います(グループBのアクティビティは、常にグループAのアクティビティの後に発生します)。他の種類の活動は私にとって面白くありません。 lead()関数を使おうとしましたが、うまくいきませんでした。

どうすれば問題を解決できますか?

6
KolM

テスト設定:

_CREATE TEMP TABLE t (name text, activity text, time time);
INSERT INTO t values
 ('user1', 'A1', '12:00')
,('user1', 'E3', '12:01')
,('user1', 'A2', '12:02')
,('user2', 'A1', '10:05')
,('user2', 'A2', '10:06')
,('user2', 'A3', '10:07')
,('user2', 'M6', '10:07')
,('user2', 'B1', '10:08')
,('user3', 'A1', '14:15')
,('user3', 'B2', '14:20')
,('user3', 'D1', '14:25')
,('user3', 'D2', '14:30');
_

あなたの定義:

グループBのアクティビティは、常にグループAのアクティビティの後に行われます。

..論理的には、ユーザーごとに、1つ以上のAアクティビティの後に0または1つのBアクティビティがあることを意味します。連続して1Bを超えるアクティビティはありません。

単一のウィンドウ関数_DISTINCT ON_およびCASEで機能させることができます。これは、ユーザーあたり少数行の最速の方法です(「未満):

_SELECT name
     , CASE WHEN a2 LIKE 'B%' THEN a1 ELSE a2 END AS activity
     , CASE WHEN a2 LIKE 'B%' THEN a2 END AS next_activity
FROM  (
   SELECT DISTINCT ON (name)
          name
        , lead(activity) OVER (PARTITION BY name ORDER BY time DESC) AS a1
        , activity AS a2
   FROM   t
   WHERE (activity LIKE 'A%' OR activity LIKE 'B%')
   ORDER  BY name, time DESC
   ) sub;
_

CASEブランチが追加されていない場合、SQL NULL式はデフォルトでELSEになるので、短くしました。

また、timeが定義されていると仮定します_NOT NULL_。それ以外の場合は、_NULLS LAST_を追加することをお勧めします。どうして?

_(activity LIKE 'A%' OR activity LIKE 'B%')_は_activity ~ '^[AB]'_よりも冗長ですが、通常、古いバージョンのPostgresでは高速です。パターンマッチングについて:

条件付きウィンドウ関数?

それは実際には可能です。集約FILTER句をウィンドウ関数のOVER句と組み合わせることができます。 ただし

  1. FILTER句自体は、現在の行の値でのみ機能します。

  2. さらに重要なことに、Postgres9.6のlead()lag()のような純粋なウィンドウ関数にはFILTERは実装されていません(まだ) 集計関数 のみ。

試してみると:

_lead(activity) FILTER (WHERE activity LIKE 'A%') OVER () AS activity
_

Postgresはあなたに言うでしょう:

_FILTER is not implemented for non-aggregate window functions
_

FILTERについて:

パフォーマンス

少数ユーザーの場合ユーザーあたりの行数が少ない、ほとんどanyクエリは、インデックスがなくても高速です。)

多くのユーザーおよびの場合ユーザーあたりの行数が少ないの場合、上記の最初のクエリが最も高速になります。インデックスとパフォーマンスについては、上記の リンクされた回答 を参照してください。

ユーザーあたり多くの行の場合、(潜在的に多くの)セットアップの他の詳細に応じて、より高速なテクニック:

9
select      distinct on(name) name,activity,next_activity

from       (select name,activity,time
                  ,lead(activity) over (partition by name order by time) as next_activity

            from   t

            where  left(activity,1) in ('A','B')
            ) t

where       left(activity,1) = 'A'

order by    name,time desc