各顧客の最後の3つのログイン日を取得し、最後のログインの前(login3)と最後のログイン(login1)の間に4日以上ある顧客を見つけたいと思います。
「アクティビティ」テーブルには次のものが含まれます。
- ユーザーID
- login_dateはDATETIME形式ですが、時刻は常に00:00:00です。
- (および問題フィールドに関連しない他のいくつか)
いくつかのクエリを試しましたが、どれも正しく機能していません。
各顧客の最後の3つのログイン日を取得し、最後のログインの前(login3)と最後のログイン(login1)の間に4日以上ある顧客を見つけたいと思います。
「アクティビティ」テーブルには次のものが含まれます。
いくつかのクエリを試しましたが、どれも正しく機能していません。
以下は、配列を使用して PostgreSQL 8.3 以降で機能するソリューションの 1 つです。
テスト データを生成します。generate_series()
アクティビティ レコードを追加するには、 の 2 番目のパラメーターを変更します。
create table activity (id serial primary key, user_id integer, login_date timestamp);
insert into activity (user_id, login_date)
select * from
(
select round(random()*10)::integer as user_id, ('2012-01-01'::date + (round(random()*300))* '1 day'::interval) as login_date
from
(select generate_series(1,1000)) foo
) fooger order by login_date;
select * from activity;
目的のデータを照会します。
--show last three login dates per user:
select user_id, login[1] as login1, login[2] as login2, login[3] as login3
from
(
select user_id, array_agg(login_date) as login from
(select * from activity order by user_id,login_date desc) foo
group by user_id
) foo;
--shake out those who haven't been visiting frequently enough
select user_id, login[1] as login1, login[2] as login2, login[3] as login3, (login[1] - coalesce(login[3],login[2],login[1]))::interval as diff
from
(
select user_id, array_agg(login_date) as login from
(select * from activity order by user_id,login_date desc) foo
group by user_id
) foo
where login[1] - coalesce(login[3],login[2],login[1]) > '4 days'::interval;
@ Joshua が提供するセットアップを使用して簡略化しました。
CREATE TEMP TABLE activity (id serial primary key, user_id integer
, login_date timestamp);
INSERT INTO activity (user_id, login_date)
SELECT * FROM (
SELECT round(random()*10)::int AS user_id
, ('2012-01-01 0:0'::timestamp + random() * interval '365 days') AS ts
FROM generate_series(1,1000)
) g
ORDER BY ts;
PostgreSQL 8.4 以降で利用可能なウィンドウ関数を使用できます。
SELECT user_id, login1, login3, (login1 - login3) AS time_span
FROM (
SELECT user_id, login_date
,first_value(login_date) OVER w AS login1
,COALESCE(lead(login_date, 2) OVER w
,lead(login_date) OVER w) AS login3
FROM activity
WINDOW w AS (PARTITION BY user_id ORDER BY login_date DESC)
) x
WHERE login_date = login1
AND (login1 - login3) > interval '4d';
IMO を読む方が簡単ですが、簡単なテストでは、@Joshua のクエリは ~ 30%高速でした。
それはさておき、タイムスタンプの時刻部分が常に である場合は、代わりに列00:00:00
を使用することを検討してください。date
timestamp
完全を期すために: NAIVE バージョン (クエリ プランは 3 つの CTE に対して 3 つの個別のサブプランを示します。これは悪いことです) (再帰的な CTE も可能である必要があります ;-)
WITH l3 AS (
SELECT a3.id, a3.user_id, a3.login_date
FROM activity a3
WHERE NOT EXISTS ( SELECT *
FROM activity nx
WHERE nx.user_id = a3.user_id
AND nx.login_date > a3.login_date
)
)
, l2 AS (
SELECT a2.id, a2.user_id, a2.login_date
FROM activity a2
JOIN l3 ON l3.user_id = a2.user_id AND l3.login_date > a2.login_date
WHERE NOT EXISTS ( SELECT *
FROM activity nx
WHERE nx.user_id = a2.user_id
AND nx.login_date > a2.login_date
AND nx.login_date < l3.login_date
)
)
, l1 AS (
SELECT a1.id, a1.user_id, a1.login_date
FROM activity a1
JOIN l2 ON l2.user_id = a1.user_id AND l2.login_date > a1.login_date
WHERE NOT EXISTS ( SELECT *
FROM activity nx
WHERE nx.user_id = a1.user_id
AND nx.login_date > a1.login_date
AND nx.login_date < l2.login_date
)
)
SELECT l1.user_id
,l1.id AS ii1, l1.login_date AS d1
,l2.id AS ii2, l2.login_date AS d2
,l3.id AS ii2, l3.login_date AS d3
FROM l1
JOIN l2 ON l2.user_id = l1.user_id
JOIN l3 ON l3.user_id = l1.user_id
WHERE l3.login_date - l1.login_date > '4 days'::INTERVAL
;