1

各顧客の最後の3つのログイン日を取得し、最後のログインの前(login3)と最後のログイン(login1)の間に4日以上ある顧客を見つけたいと思います。

「アクティビティ」テーブルには次のものが含まれます。

  • ユーザーID
  • login_dateはDATETIME形式ですが、時刻は常に00:00:00です。
  • (および問題フィールドに関連しない他のいくつか)

いくつかのクエリを試しましたが、どれも正しく機能していません。

4

3 に答える 3

3

以下は、配列を使用して 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;
于 2012-09-07T18:48:25.813 に答える
2

@ 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%高速でした。

  • エントリが 1 つしかないユーザーは、資格を得ることはありません。
  • エントリが 2 つしかないユーザーの場合、最後から 3 番目のエントリではなく、最後から 2 番目のエントリが使用されます。

それはさておき、タイムスタンプの時刻部分が常に である場合は、代わりに列00:00:00を使用することを検討してください。datetimestamp

于 2012-09-07T20:13:09.803 に答える
0

完全を期すために: 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
        ;
于 2012-09-10T21:53:04.530 に答える