質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

Q&A

7回答

3456閲覧

postgresqlで日毎に通算のユニークユーザー数を算出したい

teratail_k

総合スコア0

PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

0グッド

1クリップ

投稿2021/09/17 05:20

編集2021/09/18 03:53

前提・実現したいこと

取引テーブル(tx)を元に毎日その日のユニークユーザー(UU)数とその日までの通算のUU数をpostgresqlで集計したいです。

元となるデータ

iduser_iddate
111970-01-01
211970-01-01
321970-01-01
431970-01-01
541970-01-02
651970-01-02
711970-01-02
821970-01-03
961970-01-03

期待する結果

年月日日別UU通算UU
1970-01-0133
1970-01-0235
1970-01-0326

通算UUでは、それぞれの日までに取引実績がある人のユニーク数を算出したいです。
1970-01-01までに3人(1,2,3)、1970-01-02までに5人(1,2,3,4,5)、1970-01-03までに6人(1,2,3,4,5,6)というのを求めたいです。

発生している問題・エラーメッセージ

Error running query: DISTINCT is not implemented for window functions

該当のソースコード

sql

1SELECT 2date 3, count(distinct user_id) over(ORDER BY date) 4FROM tx 5GROUP BY date 6ORDER BY date ASC

試したこと

上記コードはウィンドウ関数でdistinctが使えないため不可ということで色々調べたりしましたが、日毎に通算を算出する方法がわかりませんでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

surface_0

2021/09/17 06:48

期待する通算UUは3,5,6となっていますが、3,6,8ではないのですか?
teratail_k

2021/09/18 03:47

すみません。補足が遅くなりました。teratailで質問するのは初めてでこんなに早く回答をいただけるものとは思っていませんでした。すごいですね。 通算UUの期待する結果は3,5,6です。 それぞれの日までに取引実績がある人のユニーク数を算出したかったので。その説明をちゃんと書くべきでしたね。 1970-01-01までに3人(1,2,3)、1970-01-02までに5人(1,2,3,4,5)、1970-01-03までに6人(1,2,3,4,5,6)というのを求めたいです。 時間かかってしまいますがいただいている回答を1つずつ確認します。
guest

回答7

0

他の方の回答と内容的には同じでネストが少ないだけですが。

SQL

1select date 年月日 2 , count(distinct user_id) 日別UU 3 , (select count(distinct user_id) from tx where date<=t.date) 通算UU 4from tx t 5group by date

投稿2021/09/18 03:12

sazi

総合スコア25188

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teratail_k

2021/09/18 04:24 編集

簡潔なクエリながら理解が追いついていません。 where date<=t.date この箇所の解説をお願いしたいです。
sazi

2021/09/18 04:29

「相関副問合せ」と言われるものです。 ネットに詳しい解説が沢山あります。
sazi

2021/09/19 05:30

>ウィンドウ関数でdistinctが使えないため windouw関数はコストを下げる為にgroup byをしない事を目的としたものですから、distinctしたいという事は、Window関数の使用は目的に合っていません。
guest

0

結合や相関サブクエリを使わずにwindow関数のみで実現する方法を紹介します。

通算UU でuser_idの重複を除いてカウントするために、user_idが初めて登場した行に初登場のフラグを立てます

sql

1select 2 id 3, user_id 4, date 5, case when first_value(id) over(partition by user_id order by date, id) = id 6 then 1 else 0 end as 初登場 7from tx 8order by id

結果

iduser_iddate初登場
111970-01-011
211970-01-010
321970-01-011
431970-01-011
541970-01-021
651970-01-021
711970-01-020
821970-01-030
961970-01-031

後はwindow関数のsum()を使って初登場の列の値を累積すれば、通算UUの値を求めることが出来ます。

上記の手法を使って、やりたいことを実現するSQLは以下となります。

sql

1with sub1 as 2( 3select 4 user_id 5, date 6, case when first_value(id) over(partition by user_id order by date) = id 7 then 1 else 0 end as 初登場 8from tx 9), sub2 as 10( 11select 12 date 13, count(distinct user_id) as 日別UU 14, sum(初登場) as 通算UU 15from sub1 16group by date 17) 18 19select 20 date 21, 日別UU 22, sum(通算UU) over(order by date) as 通算UU 23from sub2 24order by date

投稿2021/09/17 14:19

編集2021/09/18 07:35
takanaweb5

総合スコア358

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mayu-

2021/09/17 14:54

このロジック、いいと思います。 私の記述は、日別UU, 通算UU 単体だと問題ないのですけど JOINに欠陥があるので。 ( 現存の user_id のみで新規の user_id が無い日はデータが欠ける ) ( それを防ぐ方法もあるにはありますけど、クエリが重くなるのがネック )
mayu-

2021/09/17 15:50

window関数は、集計関数の結果にかぶせることが可能ですので よりシンプルに記述できそうですね。少しチューニングしてみました。 with sub1(user_id, date, 初登場) as ( select user_id , date , case when first_value(id) over(partition by user_id order by date, id) = id then 1 else 0 end from tx ) select date , count(distinct user_id) as 日別UU , sum( sum(初登場) ) over ( order by date ) 通算UU from sub1 group by date
guest

0

概念的に一番分かりやすい形はこうじゃないのかな。
このSQLはパフォーマンス的にどうなんだ、って話はあるかもしれないけれど……

SQL

1SELECT 2 date AS 年月日 3 , cnt AS 日別UU 4 , (SELECT count(distinct user_id) FROM hogehoge b WHERE b.date <= a.date) AS 通算UU 5FROM 6 (SELECT 7 date 8 , count(distinct user_id) AS cnt 9 FROM hogehoge 10 GROUP BY 11 date 12 ) a

投稿2021/09/17 13:49

ishina_yum

総合スコア509

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mayu-

2021/09/17 15:19

私の記述が今のところ、分かりやすさでは断トツ最下位なので ishina_yumさんのロジック、分かりやすくて良いと思います。
guest

0

もしかすると

count(distinct user_id) over(ORDER BY date)

じゃなく

count(user_id) over(partition by "date")

だったりしないかな? 日付ごとのuser_idをカウントしたいなら。

投稿2021/09/17 08:55

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

その日までの通算のUU数

通算」が、どのような演算や集計方法で
ご提示の値になるのか解釈に悩みましたけど
推理すると以下のようなことでしょうか。

マスタではなく、トランザクションテーブルにおいて
固有のuser_idが初めて登場した年月( サンプルデータではですが )を
user_idの「登録日」と見なして
( 過去月を含む )当日までの新規user_idの総数( 累計 )を計算したい。

カウントしたいのはあくまで新規なので
過去日に登場したuser_idは当日に再度現れてもカウントしない。
# 実績のあるuser_idの個体数を算出したいのではないか、と推測

また、初回登録された日にuser_idがユニークな値にならないのは
実際は、同日に複数のアクション( 商品購入など )が発生していたりするのでしょう。
ひょっとするとdateも「時刻」が切り捨てられていて
元のdateは別の値なのかもしれませんね。

##更に追記:
シンプルにできたかも。※ver9.4以降

SQL

1SELECT ymd 年月日 2 , count( distinct user_id ) 日別UU 3 , sum( count( distinct user_id ) filter( where ymd = f ) ) 4 over( order by ymd ) 通算UU 5FROM 6( 7 SELECT tx."date" ymd 8 , user_id 9 , min( tx."date" ) over( partition by user_id ) f 10 FROM tx 11) q 12GROUP BY ymd ;

##追記:
新規登録のuser_idが存在しない日でも
日別UU と 通算UU が両方とも表示されるよう外部結合を適用。

SQL

1CREATE TABLE tx 2( 3 id serial primary key 4 , user_id int not null 5 , "date" date not null 6); 7 8INSERT INTO tx 9 ( user_id, "date" ) 10VALUES 11 ( 1, '1970-01-01' ) 12 , ( 1, '1970-01-01' ) 13 , ( 2, '1970-01-01' ) 14 , ( 3, '1970-01-01' ) 15 , ( 4, '1970-01-02' ) 16 , ( 5, '1970-01-02' ) 17 , ( 1, '1970-01-02' ) 18 , ( 2, '1970-01-03' ) 19 , ( 6, '1970-01-03' ) 20 , ( 2, '1970-01-04' ) 21 , ( 2, '1970-01-04' ) 22 , ( 3, '1970-01-05' ) 23 , ( 7, '1970-01-06' ) 24; 25 26SELECT x.ymd 年月日 27 , x.duu 日別UU 28 , max( y.tuu ) over( order by x.ymd ) 通算UU 29FROM 30( 31 SELECT tx."date" ymd 32 , count( distinct user_id ) duu 33 FROM tx 34 GROUP BY tx."date" 35) x 36LEFT JOIN 37( 38 SELECT DISTINCT 39 ymd 40 , count(1) over( order by ymd ) tuu 41 FROM 42 ( 43 SELECT min( tx."date" ) ymd 44 FROM tx 45 GROUP BY user_id 46 ) q 47) y 48USING ( ymd ) 49;

推理が当たっていた場合に限りますけど
通算UUをわかりやすく計算するのなら ←そんなことは無かった
以下のような3段のクエリになりそうです。

SQL

1SELECT 年月 2 , sum( 新規uid数 ) 3 over( order by 年月 4 rows between unbounded preceding and current row 5 ) 通算UU 6FROM 7( 8 SELECT 年月 9 , count(1) 新規uid数 10 FROM 11 ( 12 SELECT user_id 13 , min( t."date" ) 年月 14 FROM テーブル名 t 15 GROUP BY user_id 16 ) q2 17 GROUP BY 年月 18) q1 19;

投稿2021/09/17 08:49

編集2021/09/18 13:37
mayu-

総合スコア335

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

通算UUは3,6,8でよければサブクエリを使った形でいけます。

sql

1SELECT 2 date AS 年月日, 3 dau AS 日別UU, 4 SUM(dau) OVER(ORDER BY date) AS 通算UU 5FROM ( 6 SELECT date, COUNT(DISTINCT user_id) AS dau 7 FROM tx 8 GROUP BY date 9) S 10ORDER BY date ASC

投稿2021/09/17 07:20

surface_0

総合スコア497

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

postgresはよく知らないけど通算なら質問にもある通り3,6,8じゃないですか?
あとcountをcountする意味はないのでsumじゃないですか?
通るかはわかりませんがこんな感じじゃないですか

sql

1select X.date,X.通算UU 2 , sum(X.通算UU) over(ORDER BY date) 3from ( 4 SELECT date 5 , count(distinct user_id) as 通算UU 6 FROM tx 7 GROUP BY date 8) as X 9ORDER BY X.date ASC

投稿2021/09/17 07:02

sousuke

総合スコア3828

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問