公開日: 2024/08/25
最終更新: 2024/08/25
この記事では#WOW2024 W34で出題した問題について、少し背景を補足しながら解説してみます。
閲覧者ごとに見たいデータが重複する場合に、表示制御をどのように実装するかについて述べます。
今回の記事で使用したデータとワークブックは、基本的には上記出題で使用したものに則ります。記事中で使用したものと内容ほとんど同一です。
ワークブックはこちら
データはこちら:
「データの出し分け」で困るとき
Tableauでは様々な方法で閲覧者に応じたデータの出し分けを行えます。
Tableau の行レベルのセキュリティ オプションの概要 (最後の「データベース内の RLS」は、厳密にはTableauでの話ではないですが)
方法は色々とあるのですが、基本的には「データ1行ごとに閲覧できる1ユーザーを指定する」ことを念頭に使用されます。
Superstoreのデータで例えれば「各サブカテゴリにはそれぞれ1名のサブカテゴリ担当者が割り当てられている」ことを前提に「ダッシュボードを閲覧者している担当者には、その担当者に割り当てられたサブカテゴリのデータだけ表示する」ような場合です。
ただし多くの場合、例えば1サブカテゴリを複数名で担当することがあります。
このように「特定の情報について、閲覧できるユーザーを複数名指定したい」要請があった場合、少し実装方法を考えなければいけません。
本記事ではこの問題について考えます。
まずは基本的な方法を見ていきましょう。
ユーザーグループを使用する方法
Tableau Server/Cloudには「ユーザーグループ」があり、Tableauワークブックやデータソース等では、このユーザーグループ情報を使用できます。
ユーザー関数 (特にISMEMBEROF関数を参照ください)
【TableauTips】ユーザーフィルタでセキュリティ強化 (特に「ユーザーフィルタの種類」項を参照ください)
例えば「Accessories」サブカテゴリを担当するユーザーを登録したユーザーグループを事前作成しておけば、そのユーザーグループ情報を使用したフィルターを作成できます。
具体的には、以下のような計算フィールドを作成するなどです。 ここでは「Accessories Users」という名前のユーザーグループが事前作成されているとします。
サブカテゴリごと閲覧制御フィルター
IF ISMEMBEROF('Accessories Users')
THEN
[Sub-Category] = 'Accessories'
ELSEIF ....
...
END
もしくは以下のように、ユーザーグループを使用したユーザーフィルターを作成することもできます。
どちらの方法にしても、ユーザーグループ情報を使用した実装には以下の問題点があります。
ユーザーグループを管理できるのはサイト管理者またはサーバー管理者のため、付与されているサイトロールによっては、自分たちで管理運用できない情報に基づいて実装をしなければいけない。
特に「ユーザーグループ名」を使用するので、何かの拍子でユーザーグループ名が変更されると破綻する。
そもそもディメンションの値に対してユーザーグループを作成するため、必要なユーザーグループ数が多くなる場合に運用が辛くなる可能性が高い。
仮に10個のユーザーグループを要すれば、10個分の初期設定(ISMEMBEROFで指定する、ユーザーフィルターを設定する)が必要になる。
要は、運用面の辛さが想定されます。ということで、個人的には最低限以下の条件が満たされないのであれば、基本的には避けた方が良いアプローチだと思っています。
作成者がサイト管理者である/サイト管理者ユーザーが管理運用するダッシュボードで使用される。
必要なユーザーグループ数が運用上妥当な範囲に収まっている。
データ割り当てマスタを用意し、リレーションや結合を使用する方法
上記では「ユーザーグループ」という、Tableau側で提供されている機能を使用した実装について紹介しました。
ただし上述のように運用上の懸念点が多くあるように思いますので、個人的には推奨をしません。
そもそもデータ可視化上の要請のために
ユーザーグループという、サイト全体で使用されうるものを(項目ごとに複数)用意する
ユーザーグループ管理のためにサイト管理者の協力を要する
ことが必要になるのは運用上/実装上の複雑さを増加させるように思います。
この状況を回避するために、可視化の作成者側で用意し管理できる形で実装するために、以下のような割り当てマスタを作成することを考えます。
以下のようにSuperstoreデータと結合します。
その後、以下のデータソースフィルターを追加します。
注:
ここでは都合によりFULLNAME()を使用していますが、基本的にはUSERNAME()を使用した方が良いです。FULLNAME()はユーザーが任意に設定できる表示名(つまりユーザー間で重複する可能性がある)を取得しますが、USERNAME()はメールアドレス等の一意な情報を取得します。
この設定により、閲覧しているユーザーに割り当てのあるサブカテゴリに関するデータのみ表示されるようになります。
ちなみに上記ビューを作成する際のクエリを見てみると、先に"t1"サブクエリで閲覧ユーザーが割り当てられているサブカテゴリの一覧を取得し、その結果を"t2"サブクエリでOrdersテーブルと結合しています。
割り当てマスタのデータを必要分に絞ってから結合しているのでTableauは賢いですね(他のコネクタ使用時でも同様かは分かりませんが...)。
SELECT
"t0"."Sub-Category" AS "Sub-Category",
SUM("t0"."$temp0_agg") AS "sum:Sales:ok"
FROM (
SELECT
"Orders"."Sub-Category" AS "Sub-Category",
SUM("Orders"."Sales") AS "$temp0_agg"
FROM "db1001"."TableauTemp"."Orders$" "Orders"
GROUP BY 1
) "t0"
WHERE EXISTS (
SELECT
1 AS "one"
FROM (
SELECT
"Orders"."Sub-Category" AS "Sub-Category"
FROM "db1001"."TableauTemp"."Orders$" "Orders"
INNER JOIN (
SELECT
"Multi-rows"."Assigned Subcategory" AS "Assigned Subcategory",
"Multi-rows"."Assignee" AS "Assignee"
FROM "db1002"."TableauTemp"."'Multi-rows$'" "Multi-rows"
WHERE ("Multi-rows"."Assignee" = 'Yoshitaka Arakawa' COLLATE "ja")
) "t1"
ON ("Orders"."Sub-Category" = "t1"."Assigned Subcategory")
GROUP BY 1
) "t2"
WHERE ("t0"."Sub-Category" = "t2"."Sub-Category")
)
GROUP BY 1
このように
自分たちが管理運用できる、ユーザーごとの割り当てマスタを用意する
そのマスタをデータと結合する
Tableau Cloud/Serverのユーザー情報を使用したデータソースフィルタを作成する
ことにより、運用面の懸念点を解消したデータの出し分けを実現できます。
同様の実装はもちろん仮想接続でも実現できます。
ただしこの方法は、あくまでもデータソースまたは仮想接続を作成する際に使用できる手法であることにご留意ください。
言い換えれば、Tableau Prep等でTableau Cloud/Server上に作成されるような、パブリッシュされたデータソースに対しては使用できません。
この観点からもTC24で発表のあったComposable Data Sources機能のリリースが待ち遠しいですね。
WOW2024 W34での出題の背景もそうなのですが、データベース側でSQLやETLで完全に整備されたデータに接続するのではなく、Tableau Prepで作成し更新されるパブリッシュされたデータソースについて可視化を作成することは、よくある場合だと思います。
また上記の方法はデータソースまたは仮想接続自体の編集を要しますが、特定の可視化のためだけにデータソースを編集しマスタを埋め込むことがためらわれる、そもそも対象のデータソースを編集する権限が無い場合も想定されます。
このような場合の代替策として、セカンダリデータソースを使用した方法を紹介します。
データ割り当てマスタをセカンダリデータソースとして用意する方法
ここで紹介する内容が #WOW2024 W34 で扱った内容なのですが、大きく3つの方法があります。
ユーザー x 割り当て対象のマスタを作成し、ブレンドリレーションシップを設定したブレンドを使用してデータをフィルターする。
ユーザーごとの割り当て対象リストを作成し、ブレンドリレーションシップを設定しないブレンドを使用してデータをフィルターする。
ユーザーごとの割り当て対象リストを作成し、閲覧ユーザーのリストをパラメーターに入れ、パラメーター情報を使用してデータをフィルターする。
以下、順に解説していきます。
1.ブレンドリレーションシップを使用したブレンド
再掲ですが、以下のようなデータをワークブック上で読み込みます。
ワークブックを開いているユーザーに割り当てられているサブカテゴリに絞って表示するために、以下の計算フィールドを、上記のデータを読み込んでいるデータソースで作成します。
Viewer's Sub-Categories
IF [Assignee] = FULLNAME() // or USERNAME()
THEN
[Assigned Subcategory]
END
作成した計算フィールドを使用し、以下のようにブレンドリレーションシップを定義します。
最後に作成した計算フィールドを以下のようにフィルターに追加し、NULLを除外します。簡単な実装ですね。
一方、この方法にはブレンド共通の懸念点があります:結合が複雑であったり、結合パターンが多い場合に計算パフォーマンスが低下します。
例えば1人あたり数十~数百の割り当てがある場合などは、ブレンドリレーションを使用した実装が妥当か検討、検証する必要があるかもしれません。
(データサイズ等にもよるので、一概には言えませんが)
この問題を回避するために、次の実装を考えます。
2.ブレンドリレーションシップを使用しないブレンド
この方法を使用するために、1ユーザー1行となるマスタを用意します。
例えば以下のようなデータです。このデータをワークブックに読み込みます。
ワークブックを開いているユーザーに割り当てられているサブカテゴリに絞って表示するために、以下の計算フィールドを、上記のデータを読み込んでいるデータソースで作成します。
Viewer's Sub-Category List
IF [Assignee] = FULLNAME() // or USERNAME()
THEN
[Assigned Subcategories List]
END
この計算フィールドを使用した計算フィールドを、プライマリデータソースで作成します。
このとき、ブレンドリレーションシップは設定する必要がありません。
Viewer's Assigned Sub-Cats? (Blend)
CONTAINS(
ATTR([(Secondary Datasource Name)].[Viewer's Sub-Category List])
, ATTR([Sub-Category])
)
最後に作成した計算フィールドを以下のようにフィルターに追加し、TRUEのみ選択します。
この方法ではブレンドリレーションシップが指定されていないので、結合の複雑さによる懸念点はありません。
一意の値をセカンダリデータソースから取得して使用するイメージです。
ただし、この方法は以下の課題があります。
集計値を使用してCONTAINS関数で比較するので、今回で言えば、最低でもSub-Categoryディメンションがビューで使用されるなどにより、ビューのマーク粒度にSub-Categoryが含まれる必要がある。
ブレンドの仕様上、使用できる関数に限りがある。そのためCONTAINS関数を使用した実装としたが、この関数は例えば数字IDに対する判定に対して不適切なのでREGEXP_MATCH関数等で完全一致で判定できるようにしたした方が良いと思うが、正規表現関数はブレンドでサポートされていない。
例えば(1,10,100)というIDのリストがあり、閲覧者はIDが1のデータのみ見せたい場合、文字'1'は文字'1', '10', '100'全てに含まれてしまいます。
したがって、この方法は実のところ汎用性に欠けます。
ただし「一意の値をセカンダリデータソースから取得する」アイデアは採用したいため、次の方法を考えます。
3.パラメーターを使用する
この方法で重要なのは「動的パラメーター」機能です。
まずは以下の計算フィールドを作成します。この計算フィールドは、ワークブック閲覧者に割り当てられたサブカテゴリのリストを、一意の値として出力します。
(For Dynamic Parameter) Viewer's Sub-Cat List
{FIXED: MIN(
IF [Assignee] = FULLNAME() // or USERNAME()
THEN
// 以下、正規表現で使いやすくするための文字列置換
REPLACE(
REPLACE([Assigned Subcategories List], '"', '')
, ', '
, '|'
)
END
)}
以下のパラメーターを作成します。
ワークブックを開いた時の値として、上記の計算フィールドを指定します。
ちなみに - 許可される値をリストにし、リスト値も動的にする
画像のように許可する値をリストにし、ワークブックを開いた時のリストの値も計算フィールドからの値に設定しておけば、ビューのURLパラメーター等でパラメーターの値を指定することが出来なくなります。
URLパラメーターでパラメーターの値を適当に指定したとしても、ワークブックを開いたときのパラメーターで許可される値の一覧に含まれないので、結果として無入力として処理されます。
ご興味あればぜひ実験してみて下さい。
参考: ビュー URL の構築方法
このパラメーターを使用し、プライマリデータソースに以下の計算フィールドを作成します。
Viewer's Assigned Sub-Cats? (Param)
REGEXP_MATCH(
[Sub-Category]
, [Viewer's Sub-Cat List]
)
最後に作成した計算フィールドを以下のようにフィルターに追加し、TRUEのみ選択します。
この方法であれば、1の方法にあった結合に関する懸念も、2の方法にあった利用可能な関数に関する懸念もありません。
またブレンドをそもそも使用しないので、ブレンド起因のパフォーマンス低下に関する懸念もありません。
個人的には気に入っているアプローチなのですが、強いて言えばパラメータに入力される項目数が多くなったときに、パラメーターの文字数上限に抵触するかもしれないことは懸念しても良いかもしれません。
ただし以下理由から多くの場合は大丈夫だろうとは思っていますが...
明確な上限数は探しても見つからないが、経験上わりと長い文字数も入力できた覚えがある。
コミュニティ掲示板では「接続先に依存する」ような旨の記載が散見されました。ただし自分は検証したことはありません。
整数IDのリストであれば、文字数は高が知れている
今回はサブカテゴリ文字列のリストを作成しましたが、実務上は整数IDを使用することが多いと想定しています。
セカンダリデータソースを使用する場合の懸念点
ここまでセカンダリデータソースを使用した実装について解説しました。
先に述べたように元データソースの編集を要さず、ワークブック側で表示制御を実現できるので、データソース作成や管理を担わないユーザーにとっては良い方法のように見えます。
ただしこの方法はワークブック側でデータ表示制御を実装することから、データガバナンスについて考慮する必要があります。
まず初めに、もし表示されている以上のデータを閲覧者に見せたくない場合は、最低でも以下2点を担保する必要があります
(他にも考慮するべき点はあるのですが、この記事では最低ラインに絞ります)
プライマリデータソースに表示制御を実装したワークブック以外から接続できないようにする
ワークブックを編集、ダウンロードできないようにする。
再度強調したいのは、セカンダリデータソースを使用した表示制御は、そのワークブックだけに適用される実装であることです。
当然ですが、他のワークブックから大元のデータに接続できるのであれば、この方法による表示制御にガバナンス上の意味はありません。
また、ワークブックを編集またはダウンロードできるのであれば、自由に表示制御のためのフィルターを外してデータを閲覧することが出来ます。
したがって、セカンダリデータソースを使用した表示制御を実装する場合はTabelau Cloud/Server上のパーミッション設定を考慮する必要が生じるかもしれません。
この点がデータソースまたは仮想接続上での表示制御に対しての、大きなデメリットだと考えます。
たとえば「データ割り当てマスタを用意し、リレーションや結合を使用する方法」で紹介した実装であれば、これはデータソースフィルターでユーザー情報を見ているので、パーミッションは考慮する必要がありません。
(強いて言えば、データソースの編集やダウンロードは許可しない必要はありますが)
方法が色々とある中で、要件と制約条件の中で適切な方法を取っていただければと思います。
そして他にいい方法があれば、ぜひ教えてください。
最後に
今回の記事では、閲覧者ごとに見たいデータが重複する場合に、表示制御をどのように実装するかについて解説しました。
色々と書きましたが、おそらくパーミッション周りの懸念点はTC24で発表のあったComposable Data Sources機能がリリースされれば解決されると思いますので、Tableauの今後のリリースに期待ですね。
質問などありましたら、XかLinkedinまでお願いします。