[Looker]Refimentsを駆使して1ランク上のLookMLを書いていく
大阪オフィス所属だが奈良県でリモートワークしている玉井です。
今回は、Lookerのバージョン7.6から出たRefinementsという機能をご紹介します。
公式情報
Refinementsとは
既存のViewやExploreをいじることなく、それらに追記・編集することができるものです。
…っていっても、おそらくピンとこないのではないでしょうか。というわけで、以降の「実際にやってみた」を見ていただくのが手っ取り早いと思います。
ちなみに、この時点で「Extendsと同じじゃないの?」と思った方、目の付け所がなかなかいいですね。Extendsとの違いは後で言及します。
とりあえずやってみた
超簡単な使い方をやってみる
下記のようなViewがあったとします。見ればわかりますが、dimentionが3つ定義されています。
view: test_refinements { sql_table_name: xxxxxxx ;; dimension: author_id { label: "著者ID" type: string sql: ${TABLE}.author_id ;; } dimension: facebook { label: "SNS(Facebook)" type: number sql: ${TABLE}.facebook ;; } dimension: hatena { label: "SNS(はてブ)" type: number sql: ${TABLE}.hatena ;; } }
で、ここからもう実際にRefinementsを使うのですが、別途下記のViewを作成します。
include: "/test_refinements/test_A.view" view: +test_refinements { dimension_group: post { type: time timeframes: [ raw, time, time_of_day, hour, hour_of_day, date, week, day_of_week, month, month_num, month_name, day_of_month, quarter, year ] sql: ${TABLE}.post_date ;; } }
詳細は後で説明しますが、この状態でtest_refinements
をExploreとして定義して、実際に画面を見ると、下記の通りになります。
4つのフィールドが表示されていますが、post
はtest_refinements
には記述していません。post
があるのは、後に作成した+test_refinements
です。この時点で大体わかったかと思いますが、+test_refinements
に定義しているものが、test_refinements
側に追記されている形になっています。
注目するのは下記2点。
test_refinements
をインクルードしている- Viewの名前が
+test_refinements
Refinementsとは、元々のViewやExploreの名前にプラス記号をつけて、別途記述することで、元々のファイルに追記・編集する機能となります。元のView等をいじることなく、そのViewを元に追記していくことができます。
ちなみに、Refinements側で、元ファイルと競合する記述をした場合(同じ名前のdimentionを書いた時など)は、Refinements側が優先されます(要するに上書き)。
複数記述してみる
Refinementsは複数書くことができます。先程のRefinementsに、さらに追記するとこんな感じになります。
include: "/test_refinements/test_A.view" view: +test_refinements { dimension_group: post { type: time timeframes: [ raw, year ] sql: ${TABLE}.post_date ;; } } view: +test_refinements { measure: count { label: "投稿本数" type: count drill_fields: [] } }
投稿本数というmeasureが追加されています。
Refinementsの適用順
適用(Refinementsが追記)される順番ですが、上から順番に適用されます。例えば、下記の場合、measureのcount
に付与されるlabelは「投稿されたブログの本数だぜ」になります。
view: +test_refinements { measure: count { label: "投稿本数" type: count } } view: +test_refinements { measure: count { label: "投稿されたブログの本数だぜ" type: count } }
また、同一オブジェクトに対するRefinementsを、複数ファイルにまたがって記述している場合、Modelファイルにそれらのファイルをインクルードしますが、それも「上からインクルードしている順」に適用されていきます。
下記の場合だと、test_A.view
が後に適用されます。上書がかれる記述があった場合は、test_A.view
のものが適用されます。
include: "/test_refinements/test_Z.view" include: "/test_refinements/test_A.view"
ですので、Refinementsを使っているViewファイルをインクルードする場合、ワイルドカードでまとめて指定するのは避けるべきです(順番がわからなくなる)。ちょっと面倒ですが、ファイル毎に記述しましょう。
Exploreにも使える
Viewファイルの例ばかり書いてきましたが、ExploreもRefinementsの対象です。下記の場合、test_refinements
のlabelは「Refinements Test」となります。
explore: test_refinements {} explore: +test_refinements { label: "Refinements Test" }
ちょっと混乱しそうなのですが、上記の場合、実際に定義されているExploreは1つだけです。test_refinements
というExploreに追記しているだけ…というところに注意です(+test_refinements
という新しいExploreが誕生しているわけではない)。
Refinementsの疑問
Extendsとの違いは?
ここまでくると、ますます「Extendsと一緒やん」ってなる方も多いと思います。しかし、両者は似てるようで実は違う機能です。
大きな違いをざっくりいうと下記の通り。
- Extendsは元ファイルを(内部的に)コピーして、Extends先とマージする(新しいLookMLオブジェクトを生成する)
- Refinementsは元ファイルに追記するだけ(新しいLookMLオブジェクトは生成しない)
Refinementsの方が動作としてはシンプルですね。公式ドキュメントにも言及されていますが、「今までExtendsでやってきたけど、Refinementsで代用できるかも。」というケースもあるかもしれません(ただし、まるまるExtendsの上位互換というものではないところには注意しましょう)。
ちなみに、ExtendsとRefinementsは併用できます(具体例は別ブログで書きたい所存…)。
いつ使うの?(ユースケース)
例えば、下記のようなものが考えられます。
- 流用したいLookMLがあるが、元ファイルに手を入れたくない場合
- 他Projectからインポートしたファイルを流用したい場合
- 既存のLooker Blockを追記していきたい場合
- dimentionとかmeasureがめちゃくちゃ多くて超長いViewを分割して管理したい場合
他にも色々あると思いますが、使い方の例の一つして、次はRefinementsの応用編をご紹介します。
Refinements応用編
自分の環境でいい例が浮かばなかったので、公式ドキュメントのサンプルコードを例に、Refinementsを使いこなす例を解説します。
Projectを階層化(レイヤー化)する
例えば、「FAA」というProjectを作成するとします。最初のLookMLはDBのスキーマから自動生成するとします。テーブル毎にViewファイルが生成されて、それらにダダーっとカラムがdimentionとして自動定義されますよね。この自動生成されたままの状態のViewをfaa_raw.lkml
として残しておきます。
そして、それはそのままにしておきつつ、faa_basic.lkml
というファイルを作成します。下記はその例です。
include: "faa_raw.lkml" explore: +flights { join: carriers { sql_on: ${flights.carrier} = ${carriers.name} ;; } } view: +flights { measure: total_seats { type: sum sql: ${aircraft_models.seats} ;; } }
上記はあくまで例なので、ExploreもViewも一緒くたに書いてますけど、実際は、命名規則を決めた上で、ModelファイルやViewファイルに分割することになると思います。
重要なのは、この時点で、FAAというProject下のLookMLファイルには「DBから自動生成しただけの層」と「そこに独自の記述をRefinementsしている層」の2つが誕生しているというところです。後者の層(LookML)は、あくまでRefinementsなので元ファイルはそのままです。こうやって、Projectにレイヤーをもたせることで、LookMLの管理をわかりやすくできます。
分析担当のレイヤーを用意する
自動生成のLookML→ベースとなるLookML、ときたら、次はもうちょい高度な分析をするレイヤーを用意します。ここでもRefinementsが活躍します。
※公式のサンプルの順番を説明しやすいように変えています。
include: "faa_basic.lkml" view: +flights { measure: distance_avg { type: average sql: ${distance} ;; } measure: distance_stddev { type: number sql: STDDEV(${distance}) ;; } dimension: distance_tiered2 { type: tier sql: ${distance} ;; tiers: [500,1300] } } view: distance_stats { derived_table: { explore_source: flights { bind_all_filters: yes column: distance_avg {field:flights.distance_avg} column: distance_stddev {field:flights.distance_stddev} } } dimension: avg { type:number sql: CAST(${TABLE}.distance_avg as INT64) ;; } dimension: stddev { type:number sql: CAST(${TABLE}.distance_stddev as INT64) ;; } dimension: distance_auto_tier { sql: CASE WHEN ${flights.distance} < ${avg} + ${stddev} * -2 THEN CONCAT('T00 (-inf,', CAST(${avg} + ${stddev} * -2 AS STRING),')') WHEN ${flights.distance} < ${avg} + ${stddev} * -1 THEN CONCAT('T01 [', CAST(${avg} + ${stddev} * -2 AS STRING),',', CAST(${avg} + ${stddev} * -1 AS STRING),')') WHEN ${flights.distance} < ${avg} + ${stddev} * 0 THEN CONCAT('T02 [', CAST(${avg} + ${stddev} * -1 AS STRING),',', CAST(${avg} + ${stddev} * 0 AS STRING),')') WHEN ${flights.distance} < ${avg} + ${stddev} * 1 THEN CONCAT('T03 [', CAST(${avg} + ${stddev} * 0 AS STRING),',', CAST(${avg} + ${stddev} * 1 AS STRING),')') WHEN ${flights.distance} < ${avg} + ${stddev} * 2 THEN CONCAT('T04 [', CAST(${avg} + ${stddev} * 1 AS STRING),',', CAST(${avg} + ${stddev} * 2 AS STRING),')') WHEN ${flights.distance} >= ${avg} + ${stddev} * 2 THEN CONCAT('T05 [', CAST(${avg} + ${stddev} * 2 AS STRING),',', 'inf',')') ELSE 'yea' END ;; } } explore: +flights { join: distance_stats { relationship: one_to_one type: cross } }
上から順に見ていきます。
view: +flights
ここでflights
というViewに、いくつか新しいdimentionを足しています。
view: distance_stats
ネイティブ派生テーブルを定義していますが、このNDTの元となっているExploreはflights
です。このExploreのベースのViewはflights
です。ここがミソなのですが、NDTで定義している新しいカラムは、上記の+flights
でRefinementsとして追加されたカラムが使われています。
explore: +flights
上記のNDTが定義されたdistance_stats
がjoinされています。名前を見ればわかる通り、このExploreもRefinementsですので、(単独のJOINではなく)flights
というExploreに新しいNDTを追加でJOINしている形となります。
まとめると…
既存ViewにRefinementsで追記→追記されたdimentionを使ってNDTを定義→既存ExploreにそのNDTをRefinementsで追加JOIN。…という流れです。
おわりに
機能単体としては簡単ですが、使いこなすのはなかなかムズそうです。