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

Cloud FunctionsからBigQueryへ自分で作成したテーブルにデータを追加する

3行で

  • Firebase Extensionsや、Cloud SchedulerでバックアップしたFirestoreのデータではなく、特定のリクエストをトリガーにBigQueryへデータを追加したかった
  • 自分で作成したデータセット、テーブル、スキーマにデータを追加できるので分析がしやすくなりました
  • BigQueryのドキュメントがなかなか見つからず辛かったので記事にしました

実装のきっかけ

今年の9月に月額課金をもつサービスをリリースしました1
このサービスのWebの解約画面に「解約理由」を必須にして欲しいと要望がありました。

「解約理由」は次の要件でした。

  • チェックボックス形式で解約理由の項目を選択できる(必須かつ複数選択可)
  • テキスト形式で自由に解約の理由について書くことが出来る(任意)
  • それぞれの解約理由の合計を知りたい
実際に開発した解約理由の画面
image1.png

Cloud FunctionsからBigQueryへデータを送信する手順

1. Cloud Functionsのコード

BigQueryを準備する前に、先にコードを見せた方が理解しやすいと思うのでコードを載せました。
解約処理は、本記事には関係ないので「解約理由をBigQueryに送信する」コードを中心にして書き換えてます。

余談ですが、 BigQueryのドキュメントを探すのが一番苦労したのでコードのコメントにも追記しています。
探しにくいと感じるのは僕だけでしょうか 😭

cancelPayment.ts
import * as functions from 'firebase-functions';
import { BigQuery } from '@google-cloud/bigquery';

interface CancelReason {
  id: number;
  title: string;
}

/*
 *  月額プランを解約する
 */
export = functions
  .region('asia-northeast1')
  .https.onCall(async (data, context) => {
    if (!context.auth) {
      throw new functions.https.HttpsError(
        'unauthenticated',
        '認証エラー',
        data
      );
    }

    if (!data.reasons || data.reasons.length === 0) {
      throw new functions.https.HttpsError(
        'invalid-argument',
        '解約の理由を選択してください',
        data
      );
    }

    try {
      // ここで解約(処理は省略)
      await sendBigQuery(context.auth.uid, data);
    } catch (error) {
      throw new functions.https.HttpsError(error.code, error.message, data);
    }
  });

/*
 * 解約理由をBigQueryに送信する
 * doc: https://cloud.google.com/nodejs/docs/reference/bigquery/3.0.x/Table#insert
 */
async function sendBigQuery(
  uid: string,
  data: { reasons: CancelReason[]; comment: string }
) {
  try {
    const bigQuery = new BigQuery({ projectId: process.env.GCLOUD_PROJECT });
    const table = bigQuery.dataset('データセットID').table('cancel_payment');
    const isExists = await table.exists();

    if (!isExists[0]) {
      console.error(`🧨 sendBigQuery: cancel_paymentテーブルがない`);
      return;
    }

    const ids: number[] = data.reasons.map(v => v.id);
    await table.insert({
      TIMESTAMP: bigQuery.timestamp(new Date()),
      UID: uid,
      DATA: JSON.stringify(data),
      COMMENT: data.comment,
      REASON_1: ids.includes(1) ? 1 : 0,
      REASON_2: ids.includes(2) ? 1 : 0,
      REASON_3: ids.includes(3) ? 1 : 0,
      REASON_4: ids.includes(4) ? 1 : 0,
      REASON_5: ids.includes(5) ? 1 : 0,
      REASON_6: ids.includes(6) ? 1 : 0,
    });
  } catch (e) {
    console.error(`🧨 sendBigQuery: ${JSON.stringify(e)}`);
    throw new functions.https.HttpsError(
      'internal',
      '解約処理中にエラーが発生しました'
    );
  }
}

2. BigQueryでデータセット、テーブルを作成する

const bigQuery = new BigQuery({ projectId: process.env.GCLOUD_PROJECT });
const table = bigQuery.dataset('データセットID').table('cancel_payment');

上記のとおり、連携したFirebaseのプロジェクトのBigQueryにデータセットとテーブルを作成します。
要件どおりにスキーマの型や説明を加えて分析しやすくします。

BigQueryにデータセットとテーブルを作成 スキーマ
imag2.png image3.png

データセット、テーブル、スキーマの各ドキュメントは下記のページにあります。

3. クエリで分析する

1.のコードをデプロイして解約理由がBigQueryに送信されてるかクエリを実行して確かめます。

解約理由の集計クエリ
SELECT
  count(uid) AS cancel_count,
  COUNT(CASE
      WHEN reason_1=1 THEN reason_1
    ELSE
    NULL
  END
    ) AS reason_1,
  COUNT(CASE
      WHEN reason_2=1 THEN reason_2
    ELSE
    NULL
  END
    ) AS reason_2,
  COUNT(CASE
      WHEN reason_3=1 THEN reason_3
    ELSE
    NULL
  END
    ) AS reason_3,
  COUNT(CASE
      WHEN reason_4=1 THEN reason_4
    ELSE
    NULL
  END
    ) AS reason_4,
  COUNT(CASE
      WHEN reason_5=1 THEN reason_5
    ELSE
    NULL
  END
    ) AS reason_5,
  COUNT(CASE
      WHEN reason_6=1 THEN reason_6
    ELSE
    NULL
  END
    ) AS reason_6
FROM
  `プロジェクト名.データセット名.cancel_payment`
クエリの結果
imag4.png

実装時の注意点

BigQueryのデータセットやテーブルを作成し直したり、スキーマを変更しても、即座に変更が反映されるわけではないようです。

最大でも1分ぐらい待ってから、Cloud Functionsの関数にリクエストを送った方が期待した結果が返ってきます。

BigQuery側のデータを変更したとき、たまに正常に動かない時があったので知っておくと良いかもしれません。

おわりに

やってることはドキュメントを見れば理解できるので簡単な実装です。
しかし、それ故に自分と同じことを考える記事を見かけませんでした。
僕の検索力が低いだけかもしれないですが。

ただ、解約理由の要件が複数選択ではなく一つだけ選択の場合、Google Analyticsにデータを送信する方が圧倒的に楽です。
もし、このような複数選択のデータでも、Google Analyticsで簡単に分析できる方法を知っている人がいれば、記事にしてほしいです 🥺

コードを書く前に、最小工数で要件を満たせるよう、FirebaseやBigQueryを使い倒していきたいですね 😄

masashi-sutou
👨‍💻最近は、Swift・Flutter・Firebase・Vue・Nuxtとかやってます
http://keeping.hatenablog.jp/
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
ユーザーは見つかりませんでした