この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
大阪オフィス所属だが奈良県でリモートワークしている玉井です。
今回は、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。…という流れです。
おわりに
機能単体としては簡単ですが、使いこなすのはなかなかムズそうです。