DIYな業務システムのビューに Metabase はいかが

例えば勤怠管理
![Screenshot_2020-04-20 TOP · ダッシュボード · Metabase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81857/9b43babc-767d-1475-7605-16cec9731e7f.png)


# なぜ書いた？
* Metabase が気に入っているから
* 自宅で調べたことを職場で参照したい
* もしかすると参考になる人がいるかもしれない

# 元のデータを用意する

下記の SQL は MySQL(phpMyAdmin) からエクスポートしたものです。
冒頭のスクリーンショットはこのデータベースを元に作成しました。
※ SQL を貼ってはみましたが後の説明で使わなかったので読み飛ばしてください。

Metabase は Join や Group By に相当する機能があります。
しかし単純な数値意外の計算はできないようなので、
日時の差分を求めるような複雑な計算が必要な場合はDB側で View を作って処理しておきます。
Metabase 側で生の SQL 文を書いて処理させることも可能ではありますが、同じ SQL の結果をテーブルでもグラフでも表示する場合などに View の方が一元管理できて良さそうです。

```SQL
-- phpMyAdmin SQL Dump
-- version 5.0.1
-- https://www.phpmyadmin.net/
--
-- ホスト: mysql
-- 生成日時: 2020 年 4 月 19 日 14:50
-- サーバのバージョン： 5.7.29
-- PHP のバージョン: 7.4.3

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- データベース: `attendance`
--

-- --------------------------------------------------------

--
-- ビュー用の代替構造 `BasicView`
-- (実際のビューを参照するには下にあります)
--
CREATE TABLE `BasicView` (
`id` char(8)
,`name` tinytext
,`date` date
,`checkIn` time
,`checkOut` time
,`spent` bigint(21)
);

-- --------------------------------------------------------

--
-- テーブルの構造 `date`
--

CREATE TABLE `date` (
  `TDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `ENumber` char(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- テーブルのデータのダンプ `date`
--

INSERT INTO `date` (`TDate`, `ENumber`) VALUES
('2020-03-18 22:55:34', 'A1'),
('2020-03-19 09:55:34', 'A1'),
('2020-03-18 22:56:54', 'A2'),
('2020-03-19 07:56:54', 'A2'),
('2020-03-18 22:57:43', 'A3'),
('2020-03-19 03:57:43', 'A3'),
('2020-03-19 08:58:32', 'I9'),
('2020-03-19 12:58:49', 'I9'),
('2020-03-19 22:55:34', 'A1'),
('2020-03-20 09:55:34', 'A1'),
('2020-03-19 22:56:54', 'A2'),
('2020-03-20 07:56:54', 'A2'),
('2020-03-19 22:57:43', 'A3'),
('2020-03-20 03:57:43', 'A3'),
('2020-03-20 08:58:32', 'I9'),
('2020-03-20 12:58:49', 'I9'),
('2020-04-18 23:55:34', 'A1'),
('2020-04-19 09:55:34', 'A1'),
('2020-04-18 23:56:54', 'A2'),
('2020-04-19 07:56:54', 'A2'),
('2020-04-18 23:57:43', 'A3'),
('2020-04-19 03:57:43', 'A3'),
('2020-04-19 08:58:32', 'I9'),
('2020-04-19 12:58:49', 'I9'),
('2020-04-19 23:55:34', 'A1'),
('2020-04-20 09:55:34', 'A1'),
('2020-04-19 23:56:54', 'A2'),
('2020-04-20 07:56:54', 'A2'),
('2020-04-19 23:57:43', 'A3'),
('2020-04-20 03:57:43', 'A3'),
('2020-04-20 08:58:32', 'I9'),
('2020-04-20 12:58:49', 'I9');

-- --------------------------------------------------------

--
-- テーブルの構造 `member`
--

CREATE TABLE `member` (
  `ENumber` char(8) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `EName` tinytext CHARACTER SET utf16 NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- テーブルのデータのダンプ `member`
--

INSERT INTO `member` (`ENumber`, `EName`) VALUES
('A1', '栄一郎'),
('A2', '栄次郎'),
('A3', '栄三郎'),
('I9', '愛九郎');

-- --------------------------------------------------------

--
-- ビュー用の構造 `BasicView`
--
DROP TABLE IF EXISTS `BasicView`;

CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `BasicView`  AS  select `member`.`ENumber` AS `id`,`member`.`EName` AS `name`,cast(`date`.`TDate` as date) AS `date`,cast(min(`date`.`TDate`) as time) AS `checkIn`,cast(max(`date`.`TDate`) as time) AS `checkOut`,timestampdiff(MINUTE,min(`date`.`TDate`),max(`date`.`TDate`)) AS `spent` from (`date` join `member` on((`date`.`ENumber` = `member`.`ENumber`))) group by `date`,`member`.`ENumber` ;

--
-- ダンプしたテーブルのインデックス
--

--
-- テーブルのインデックス `date`
--
ALTER TABLE `date`
  ADD KEY `TDate` (`TDate`),
  ADD KEY `a member has dates` (`ENumber`);

--
-- テーブルのインデックス `member`
--
ALTER TABLE `member`
  ADD PRIMARY KEY (`ENumber`) USING BTREE;

--
-- ダンプしたテーブルの制約
--

--
-- テーブルの制約 `date`
--
ALTER TABLE `date`
  ADD CONSTRAINT `a member has dates` FOREIGN KEY (`ENumber`) REFERENCES `member` (`ENumber`);
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
```

# Metabase を起動する

Docker Compose なんかで起動するとオシャレですが今回は[公式サイト](https://www.metabase.com/start/jar.html)から .jar をダウンロードする方法とします。とりあえず動かすには下記のコマンドで起動します。

```sh
java -jar metabase.jar
```

デフォルト設定では Metabase 自体のアプリケーションDBに H2 を使うのですが、[公式サイト](https://www.metabase.com/docs/latest/operations-guide/configuring-application-database.html)によると本格稼働には非推奨の設定らしいです。起動時にも下記の警告が表示されます。

```
警告: 本番環境で、MetabaseをH2アプリケーションデータベースと共に使用することはお勧めできません。
本番環境の場合は、代わりにPostgres、MySQL、またはMariaDBを使用することを強くお勧めします。
H2の使用を継続する場合は、データベースファイルを定期的にバックアップしてください。
詳細はこちら
https://metabase.com/docs/latest/operations-guide/migrating-from-h2.html
```

下記のように環境変数で設定を与えることで MySQL や PostgreSQL を使うことができます。
Windows10で本格稼働させるなら[サービス化](https://qiita.com/moni-hide/items/fd26fb223f2d4736cf55)すると良さそうです。Windows なら export でなく set ですね。もちろん実在するデータベースと、そのデータベースを読み書きできるアカウントの設定を与えてください。[元のデータを用意する](#元のデータを用意する)のデータベースとは別のデータベースです。

```sh
# MySQL の場合
export MB_DB_TYPE=mysql
export MB_DB_DBNAME=XXXX(適宜)
export MB_DB_PORT=3306
export MB_DB_USER=XXXX(適宜)
export MB_DB_PASS=XXXX(適宜)
export MB_DB_HOST=localhost
java -jar metabase.jar
```

起動したら初回アクセス時の案内に従って管理者アカウントの作成と事前に用意したDBへの接続(データを追加する)を済ませます。特に設定していなければ下記のURLで Metabase にアクセスできます。
http://localhost:3000/

![Screenshot_2020-04-22 Metabase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81857/745d9fba-8865-3bdc-7f77-19463e6339d9.png)

DBへの接続では[元のデータを用意する](#元のデータを用意する)のデータベースを指定しますがスキップする(あとでデータを追加する)こともできます。サンプルのテーブルがいくつか用意されているので、それらを用いて Metabase の機能を体験してください。

# 照会する = 「質問」を作成する

![Screenshot_2020-04-22 新しい質問 · Metabase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81857/4104d82e-bef9-4fdb-32fd-523880e12b24.png)

ヘッダー右側の「照会する」ボタンを押して「質問」を作成しましょう。
「質問」はデータベースのViewのようなものですがグラフ化（ビジュアライゼーション）することができます。また作成した「質問」をいくつか組み合わせてダッシュボードを作ることができます。

「質問」はエディター設定→ビジュアライズ設定の順に設定して作成します。

## エディター設定
SQL の View の作成に相当します。
「ネイティブクエリ」から「質問」を作成する場合は生の SQL 文を入力します。
「簡単な質問」から「質問」を作成すると「エディター設定」がスキップされますが、
「ビジュアライズ設定」から「エディター設定」へはいつでも戻ることができるので「簡単な質問」と「カスタム質問」に実質的な差はありません。

「エディター設定」が完了したら「ビジュアライズ」ボタンを押して「ビジュアライズ設定」へ進みます。

## ビジュアライズ設定

![Screenshot_2020-04-22 質問 · Metabase.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81857/e118ca17-cecf-d57f-4103-0adf0533d9c4.png)

デフォルトでは可視化方法がテーブルになっています。別の可視化方法を選ぶ場合は左下の「ビジュアライゼーション」ボタンを、可視化方法の詳細を設定する場合は左下の「設定」ボタンを押します。

エディター設定へ戻るには右上のメニューアイコンのボタンを押します。マウスオーバーすると「エディターを表示する」と表示されるボタンです。
「フィルター」や「要約」のボタンを押すとエディター設定の機能を一部だけ使えます。
「質問」が完成したら「保存」ボタンを押してください。

# ダッシュボードを作成する

「質問」を保存するとダッシュボードへの追加を勧められます。
ダッシュボードをまだ作成していなくても、新規にダッシュボードを作成して追加することができます。

ダッシュボードだけを新規に作成するには右上の「＋」ボタンを押してください。
ダッシュボードの作成は直感的に行えますが使いこなしのポイントを一つ紹介します。

## フィルターを追加する

「質問」の作成時点であらかじめデータを選別しておくことができますが、フィルターを用いるとダッシュボードの閲覧時にデータを動的に選別することができます。ダッシュボードの編集画面でフィルターアイコンを押してフィルターを追加してみましょう。追加したフィルターに対して次の３つを設定できます。

* 対象の質問と項目
    * ダッシュボードに追加ずみの「質問」を１つ以上、各「質問」の項目を１つずつ選択します
* フィルターの名前
* フィルターのデフォルト値

# ダッシュボードを管理する

## 編集を制限する

ダッシュボードや「質問」は「コレクション」というディレクトリのようなものに入れて管理します。初期状態では「分析」という名前のルートディレクトリのような「コレクション」とユーザーごとの「個人のコレクション」だけがあります。管理者ユーザーは全ユーザーの「個人のコレクション」を閲覧できます。

コレクションごとに権限を設定することで編集を制限できます。「分析」に「質問」やダッシュボードを直接には置かないほうが良さそうです。新しいコレクションを作成してそこにアイテムを置きましょう。作成済みのアイテムを別の「コレクション」へ移動させることができます。

![Screenshot_2020-04-22 Metabase(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81857/47d62ca0-24ed-cfed-8f02-2b1ac58c0990.png)

## アイテムを削除する

ダッシュボードなどのアイテムに対して「アーカイブする」という操作をすると、アイテムは「アーカイブ」というゴミ箱のような場所へ送られます。「アーカイブ」にあるアイテムは復元することと、完全に削除することができます。

# まとめ

まずは触ってみた方が早いのですが、それでも気が付きにくそうな機能やポイントについて紹介しました。また何か気づいたことがあれば追記します。