Help us understand the problem. What is going on with this article?

GoogleAnalyticsで出る数値をBigQueryでやってみる。

More than 3 years have passed since last update.

GoogleAnalyticsのデータって?

GoogleAnalyticsの生データはPremium(以下GAP)サービスに申し込むことで、BigQuery(以下BQ)にJSONデータでエクスポートすることができます。去年も書いたのですが、このデータって非常によくできている。やり方によってはローカルのデータと組み合わせて、かなり精度の高い分析が可能です。
でも、非常によく出来ている反面、JSONが使いづらいという声をよく聞くのも事実だったりします。このJSONフォーマットのデータがGAPデータをBQで活用させる壁になっていると言っても過言じゃないかなと思います。
(※GAP使ってるのにBQ使ってるのって1割ぐらいだと思います)
ということで、実際に使っているクエリなども書いて行きたいと思います。

流入元を見たい。

GAPのデータではtrafficSource.mediumというところに流入元の種類が入ってきます。
例えばディスプレイ広告なら『display』、アドワーズやリスティング広告の場合は『cpc』、twitterやfacebookなどは『social』と入っています。そして、そこで絞ってからtrafficSource.sourceを見るとその流入元がわかります。例のSQLの場合、yahooとかgoogleとかがわかります。(他にもbingとかも入ってます。)
このようにGAが自動的に判断して入れる場合もありますが、スマホアプリやlineなどリファラが取れないものについてはリンクのURLにクエリストリングスのutm_medium=*****で自分の好きな値で設定することもできます。(※詳細は こちら
例えば流入元の種類をディスプレイ広告として、流入元別のPVUUを過去8日間履歴で取るにはこんなSQLとなります。

display.sql
SELECT
  date,
  SUM(CASE WHEN display_source CONTAINS 'yahoo' THEN pv ELSE 0 END) yahoo_disp_pv,
  SUM(CASE WHEN display_source CONTAINS 'yahoo' THEN uu ELSE 0 END) yahoo_disp_uu,
  SUM(CASE WHEN display_source CONTAINS 'google' THEN pv ELSE 0 END) google_disp_pv,
  SUM(CASE WHEN display_source CONTAINS 'google' THEN uu ELSE 0 END) google_disp_uu,
  sum(case when display_source not like '%yahoo%' and display_source not like '%google%' then pv else 0 end) other_disp_pv,
  sum(case when display_source not like '%yahoo%' and display_source not like '%google%' then uu else 0 end) other_disp_uu
FROM (
  SELECT
    date,
    trafficSource.source display_source,
    SUM(totals.pageviews) pv,
    INTEGER(EXACT_COUNT_DISTINCT(fullVisitorId)) uu
  FROM (TABLE_DATE_RANGE( [*******.ga_sessions_], TIMESTAMP(DATE(DATE_ADD(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour'),-8,'day'))), TIMESTAMP(DATE(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour')))) )
  WHERE
    trafficSource.medium = 'display'
  GROUP BY
    date,
    display_source)
GROUP BY
  date
ORDER BY
  date;

では次に流入元の種類をsocialとして、その流入元別にPVUUも見てみましょう。trafficSource.mediumにsocialと入れ替えるだけです。

sns.sql
SELECT
  date,
  SUM(CASE WHEN sns_source CONTAINS 'facebook' THEN pv ELSE 0 END) facebook_sns_pv,
  SUM(CASE WHEN sns_source CONTAINS 'facebook' THEN uu ELSE 0 END) facebook_sns_uu,
  SUM(CASE WHEN sns_source CONTAINS 'twitter' THEN pv ELSE 0 END) twitter_sns_pv,
  SUM(CASE WHEN sns_source CONTAINS 'twitter' THEN uu ELSE 0 END) twitter_sns_uu,
  sum(case when sns_source not like '%facebook%' and sns_source not like '%twitter%' then pv else 0 end) other_sns_pv,
  sum(case when sns_source not like '%facebook%' and sns_source not like '%twitter%' then uu else 0 end) other_sns_uu
FROM (
  SELECT
    date,
    trafficSource.source sns_source,
    SUM(totals.pageviews) pv,
    INTEGER(EXACT_COUNT_DISTINCT(fullVisitorId)) uu
  FROM (TABLE_DATE_RANGE( [*******.ga_sessions_], TIMESTAMP(DATE(DATE_ADD(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour'),-8,'day'))), TIMESTAMP(DATE(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour')))) )
  WHERE
    trafficSource.medium contains 'social'
  GROUP BY
    date,
    sns_source)
GROUP BY
  date
ORDER BY
  date desc;

さて、これで個々のSQLができました。trafficSource.mediumの値を変えることで流入元を色んな角度で見ることが出来ます。
この2つのSQLの数値を帳票などで一回で見たいとなれば、1つのSQLで書くことも出来ます。
でも、これは好みの問題かもしれませんが、私の場合は1つのSQLでは書きません。SQLが長くなるとメンテナンスが面倒なので。
じゃ、どうするか?このSQLを個々のviewにしちゃって、この場合だとdateでjoinします。
例えばdisplay.sql→display_view、sns.sql→sns_viewとします。

join.sql
select
 a.date,
 a.facebook_sns_pv,
 a.facebook_sns_uu,
 a.twitter_sns_pv,
 a.twitter_sns_uu,
 a.other_sns_pv,
 a.other_sns_uu,
 b.yahoo_disp_pv,
 b.yahoo_disp_uu,
 b.google_disp_pv,
 b.google_disp_uu,
 b.other_disp_pv,
 b.other_disp_uu
from [*******.sns_view] a
join [*******.display_view] b
on a.date = b.date

ここで一つ注目して欲しいのが、今回の場合に限ってだけかもしれませんが見てるカラムが両方とも同じということです。
これはどういうことを意味しているかというと、個々でSQLを実行するとそれぞれに課金されますが、今回のこの場合だと1回分の課金になるということです。もちろん1つのSQLで書けば結果は同じなのですが、viewで分けたからと言ってそれをjoinしてしまえば同じカラムを見ている場合は費用は同じとなります。
ですから、trafficSource.mediumを色んな角度で取るために10個ぐらいのviewにしたところで、最後にjoinしちゃえば課金は1回のSQLと同じになります。これって何気にすごいなぁと思うんです。

dropページを見てみる。

サイトではユーザーがどこで離脱したのかが非常に気になるわけです。どこのページが一番離脱されてるの?それはなぜ?使い勝手が悪いのか?それとも、目的が達成できなかったのか?
それを調査する優先順位をつけるために、どのページで一番多く人が離脱しているのかを調査してみます。
ここではmax(hits.hitnumber)でユーザー毎に最後のページ番号を取ってきて、そのページ番号のhits.page.pagePathでURI部分を取ってきて、ユーザーの数を取ってきています。
(※でもこれはGAの画面で見ることが出来ますが、このSQLを少し改造することで調査がしやすくなると思いますよ。)

drop.sql
SELECT
  a.date,
  a.hits.page.pagePath,
  INTEGER(EXACT_COUNT_DISTINCT(a.fullVisitorId)) cnt
FROM
  FLATTEN((
    SELECT
      fullVisitorId,
      hits.page.pagePath,
      hits.hitnumber,
      date
    FROM (TABLE_DATE_RANGE( [*******.ga_sessions_], TIMESTAMP(DATE(DATE_ADD(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour'),-8,'day'))), TIMESTAMP(DATE(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour')))) )),hits.page.pagePath) a
JOIN EACH (
  SELECT
    date,
    fullVisitorId,
    MAX(hits.hitNumber) last_page_num
  FROM (TABLE_DATE_RANGE( [*******.ga_sessions_], TIMESTAMP(DATE(DATE_ADD(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour'),-8,'day'))), TIMESTAMP(DATE(DATE_ADD(CURRENT_TIMESTAMP(),+9,'hour')))) )
  GROUP BY
    date,
    fullVisitorId) b
ON
  a.date = b.date
  AND a.fullVisitorId = b.fullVisitorId
  AND a.hits.hitnumber = b.last_page_num
GROUP BY
  a.date,
  a.hits.page.pagePath
ORDER BY
  a.date DESC,
  cnt DESC

これでdropしたページがわかります。
もちろん、これだけではただページがわかるだけで『なんでdropしたの?』まではわかりません。
でも、例えばdropした前のページは何だったのか?はlast_page_num - 1などで見ることが出来ます。直帰しているページもlast_page_num = 1だけで絞ればいいとなります。

ということで。

簡単なSQLだけを掲載してみました。が、実際に使っているのはこんなものです。
コツは一つのSQLで最初からなんとかしようとするのではなく、こういう簡単なSQLを作って肉付けしていくのが手っ取り早いです。
table_date_rangeも二日か三日分ぐらいしか指定しなければそんな膨大なデータ量でもないですしね。
で、SQLは極力長くしない方が良いと思っています。出来たらさっさとviewに保存する。で、最後にjoinするって感じです。
ローカルデータとjoinするときも同じかと。最初にGAPのデータをBQであらかたviewで作ってから、ローカルのデータとぶつけるという感じです。すいません、このあたりは大人の事情で詳しく書けないのですがw

こんなのGAの画面で見れるじゃん!と思われる方もいらっしゃるかと。でも、この中の値やカラムを変えてトライ&エラーでやってみてください。
きっと痒いところにだけ手が届くようなデータが出てくると思いますよ。

急いで書いたので間違えているところとか、もっとこうやれば良いという意見を頂ければうれしいです。

satoru_mag
社内なんでも屋さん。 BigQueryとGoogleAnalytics360が大好物。 最近はがんばってpython勉強中。 GoogleDevelopersExpert(GCP)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした