Nuxt.js+LaravelでStripeのCheckoutの実装をやってみた【Stripe決済ページ遷移編】

この記事は個人ブログと同じ内容です

Nuxt.js+LaravelでStripeのCheckoutの実装をやってみた【Stripe決済ページ遷移編】


概要

LaravelとNuxt.jsでStripeの決済処理を実装しました。 Stripeにはいくつか決済方法があるのですが今回はCheckoutと呼ばれる、Stripeが用意してくれた決済フォームを使用しました。 また、Checkoutにも2種類存在し、「決済時にStripeの決済ページに遷移する方法」と「自前のページに決済フォームを埋め込む方法」があるのですが、今回は「決済時にStripeの決済ページに遷移する方法」の紹介になります。

https://stripe.com/docs/payments/checkout

実装したもの

顧客が1つの商品を購入して完了するまでの実装をしました

https://youtu.be/ovSFHPwcZGY

事前準備

StripeのAPIキーの取得

以下を参考にAPIキーを取得してください。

https://stripe.com/docs/keys?locale=ja-JP#reveal-an-api-secret-key-for-test-mode

公開可能キーはフロントエンド側で使用するキー。 シークレットキーはバックエンド側で使用するAPIキー。

商品の追加

ダッシュボードの商品カタログから、購入してもらいたい商品を追加し、そのAPI IDをメモしておく。(バックエンドで使用する)

顧客アカウントの作成

誰が購入したかがわかるように、Stripeの顧客アカウントを作成する。 今回は記載していないが、Stripe APIを使用して顧客アカウントを作成も可能。

ダッシュボードの顧客より作成し、顧客IDをメモしておく。(バックエンドで使用する)

決済の簡単な流れ

  1. フロントがバックエンドのCheckoutセッション作成APIを叩く(フロント)
  2. バックエンドがstripeのセッション作成APIを叩き、その結果をフロントに返す(バックエンド)
  3. フロントがレスポンスのsessionIdを使用し、stripeの決済ページにリダイレクトする(フロント)
  4. 決済をし、任意のページにリダイレクト(フロント)

詳しくは以下をチェック↓

https://stripe.com/docs/payments/checkout/how-checkout-works?locale=ja-JP#lifecycle

バックエンドの実装(Laravel)

ライブラリを使用し、StripeのCheckoutセッション作成APIを叩き、その結果を返すAPIの作成をしました。

https://stripe.com/docs/api/checkout/sessions/create

ライブラリについて

stripe-phpという、 Stripe公式のphp用のライブラリを使用して実装しました。 採用理由としては、公式で信頼できるという点とドキュメントや使用例がたくさんあった点です。

https://github.com/stripe/stripe-php

Laravel CahierというLaravelが提供している別のライブラリも存在したのですが、こちらはドキュメントや使用例が少なく、サブスク支払がメイン機能そうだったので、今回は使用しませんでした。

https://readouble.com/laravel/9.x/ja/billing.html

重要なところの解説

$stripe->checkout->sessions->create()でStripeのCheckoutセッション作成APIを叩いています。以下にパラメーターについて解説します。

パラメーター 説明
line_items->price 顧客に購入してほしい商品のID。事前準備の商品IDを記載する
line_items->quantity 顧客に購入してもらう商品の個数
mode 今支払うか、後払いにするか、サブスクにするかを選択できる
customer Stripeの顧客アカウントID。誰が購入したかの把握ができる。事前準備の顧客IDを記載する
success_url 支払いに成功した時のリダイレクト先
cancel_url Stripeの決済ページの戻りボタンを押した時のリダイレクト先
automatic_tax->enable 税金の自動徴収をするかどうか。オンにすると価格1万円に設定した商品が、 税込の1万1千円で決済される
payment_method_options->card->setup_future_usage クレジットカードの情報を保存する。off_sessionにするとずっと保存してくれる
payment_method_types 支払い方法をどうするか。カード決済、ApplePayでの支払いなど選択できる
idempotency_key 多重決済を防ぐためのキー。 ユニークである必要がある。顧客が商品を購入する時の初回に作成し、それ以降はDBに保存されたこのキーを参照する

ソースコード

public function createCheckoutSession(): JsonResponse
    {
        /** @var string $config */
        $config = config('define.stripe.token');
        $stripe = new StripeClient($config);

        $checkout = $stripe->checkout->sessions->create([
            // 商品
            'line_items'             => [[
                'price'    => 'price_id',//商品ID
                'quantity' => 1,//個数
            ],
            ],
            'mode'                   => 'payment', // 支払いモード
            'customer'               => 'customer_id',//顧客ID 
            'success_url'            => 'http://localhost:8080/success,
            'cancel_url'             => 'http://localhost:8080/cancel', 
            // 税金を自動徴収するかどうか
            'automatic_tax'          => [
                'enabled' => true,
            ],
            // 支払い方法を保存するかどうか
            'payment_method_options' => [
                'card' => [
                    'setup_future_usage' => 'on_session',
                ],
            ],
            // 支払い方法
            'payment_method_types'   => ['card'],
        ],[
            'idempotency_key' => 'G5pobWIO8Q0ODYK1313333333',
        ]);
        return response()->json($checkout);
    }

フロントエンドの実装

ボタンを押したら、バックエンドのセッション作成のAPIを叩き、レスポンスのsessionIdを使用し、Stripeの決済ページにリダイレクトさせるような実装になっています。

stripe-jsでの実装

stripe-jsという、Stripeの公式のJavaScript用のライブラリを使用した実装方法の紹介です。

https://github.com/stripe/stripe-js

重要なところの解説

バックエンドからもらったclientSecretを使用し、ライブラリのredirectToCheckout()を呼べば、 Stripeの決済ページにリダイレクトします。

// Stripeのページにリダイレクトする
await stripe?.redirectToCheckout({
  sessionId: res.data.id,
})

ソースコード

<template>
  <div>
    <div>
      <button @click="submit">支払いをする</button>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import { StripeEmbeddedCheckout, loadStripe } from '@stripe/stripe-js'
import axios from 'axios'

export default Vue.extend({
  name: 'StripeTest',
  head: () => ({
    title: 'StripeTest | back check',
  }),
  components: {},
  data() {
    return {
      publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
      loading: false,
      sessionId: '',
      checkout: undefined as StripeEmbeddedCheckout | undefined,
    }
  },

  methods: {
    async submit() {
      const stripe = await loadStripe(
        process.env.STRIPE_PUBLISHABLE_KEY
          ? process.env.STRIPE_PUBLISHABLE_KEY
          : ''
      )

      await axios
        .post('http://localhost:8080/api/create_session')
        .then(async (res) => {
          // Stripeのページにリダイレクトする
          await stripe?.redirectToCheckout({
            sessionId: res.data.id,
          })
        })
    },
})
</script>

vue-stripeでの実装

vue-stripeとは、Stripeの認定パートナーのライブラリです。 ドキュメントが充実している。Checkoutで、Stripeの決済ページに遷移する方は実装できるが、自前のページに決済フォームを埋め込む方法は実装できません。 stripe-jsの方がコード量少ないので、個人的にはstripe-jsをお勧めします。

https://vuestripe.com

重要なところの解説

以下のように、ライブラリのコンポーネントを使用し、バックエンドからのsessionIdをコンポーネントに設定し、this.$refs.checkoutRef.redirectToCheckout();を呼び出すだけで、Stripeの決済ページに遷移する。

<stripe-checkout
      ref="checkoutRef"
      mode="payment"
      :pk="publishableKey"
      :session-id="sessionId"
      @loading="(v) => (loading = v)"
/>

ソースコード

<template>
  <div>
    <stripe-checkout
      ref="checkoutRef"
      mode="payment"
      :pk="publishableKey"
      :session-id="sessionId"
      @loading="(v) => (loading = v)"
    />
    <div>
      <button @click="submit">支払いをする</button>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import { StripeCheckout } from '@vue-stripe/vue-stripe'
import axios from 'axios'

export default Vue.extend({
  name: 'StripeTest',
  head: () => ({
    title: 'StripeTest | back check',
  }),
  components: {
    StripeCheckout,
  },
  data() {
    return {
      publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
      loading: false,
      sessionId: '',
    }
  },

  methods: {
    async submit() {
      await axios
        .post('http://localhost:8080/create_checkout_session')
        .then((res) => {
          this.sessionId = res.data.id
        })
        .then(() => {
          ;(this.$refs.checkoutRef as any).redirectToCheckout()
        })
    },
  },
})
</script>

終わりに

今回はStripeを使用し、決済ページに遷移するCheckoutの実装をやってみました。 Stripeの使用は初めてだったのですが、実装もそこまで難しなく、簡単に決済処理が作れて驚きでした。 ダッシュボードも使いやすかったので、決済の導入にはStripeをお勧めします。


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers

Node.js サーバーアプリケーションにおけるトークンバケットを用いたレートリミットの実現

この記事は個人ブログと同じ内容です

Node.js サーバーアプリケーションにおけるトークンバケットを用いたレートリミットの実現

はじめに

こんにちは、株式会社 ROXX で back check というサービスを開発しているぐっきー(@Area029S)です。

リクエストを受け付けるごとに重い処理が実行される機能の実装などにおいては一定の時間内に大量に処理を実行するとシステムのリソースを圧迫してしまう可能性があるため注意が必要です。 このような処理をリソースを圧迫させずに処理する方法の1つとして、レートリミット(時間あたりの実行数の制御)の導入があげられます。

今回はネットワークのトラフィックシェービングなどに利用されることの多いトークバケットアルゴリズムを導入することで、サーバー側のアプリケーションでレートリミットを実現しようと思います。

レートリミットとは?

レートリミットとは主に時間あたりのネットワークトラフィックを制御するために用いられる戦略です。

一般的には下記のようなことに役立ちます。

  • アプリケーションリソースの枯渇を防ぐ
    • 特定のエンドポイントへのリクエスト数を制限する
  • 運用コストの超過を防ぐ
    • ChatGPT など利用ごとに課金される外部 API を利用したサービスのリクエスト数を制限することにより出費を管理する
  • 悪意のあるユーザーから API を保護する

参照: The Fundamentals of Rate Limiting: How it Works and Why You Need it

外部 API を利用したサービスのリクエスト数を制限する

今回のやりたいことであるリクエスト毎に重い処理を実行したい要件においては、上記の文脈でレートリミットを適用することでシステムリソースの負荷を分散させる用途に利用できそうです。

レートリミットを実装するための一般的なアルゴリズムには下記のようなものが存在します。

  • token bucket
  • leaky bucket
  • fixed window counter
  • sliding window log
  • sliding window counter

参照:様々な rate limit アルゴリズム

今回は高負荷の掛かるリクエストが大量に来たときは処理量を制限しつつ、制限の範囲内においては短期間に多数のリクエストを処理(バースト)することの可能な方式を利用したいため token bucket がよさそうです。そこで token bucket の特徴について説明します。

token bucketトークバケットアルゴリズム)とは?

token bucket

画像引用:様々な rate limit アルゴリズム より

概要

トークバケットアルゴリズムは、リクエストのレートを一定の制限内に保ちつつ、短期的なトラフィックの増加(バースト)も許容するための手法です。このアルゴリズムは、トークンの生成速度とバケットのサイズを利用してリクエストの平均処理量に制限をかけます。柔軟かつ効率的なレートリミットの手段として、ネットワーク環境からアプリケーションまで幅広く採用されています。

主要用語

トークバケットアルゴリズムを理解する上で重要な用語は以下の通りです。

  1. トーク
  2. バケット
  3. トークンの生成速度
  4. トークンの消費
  5. バースト
    • バーストは、短期間に多数のリクエストを許可する能力です。バケットが満杯の場合、短時間で多数のリクエストを処理できますが、その後トークンが不足するとトークンの再生成を待たなければなりません。

仕組み

トークバケットアルゴリズムでは、定められた速度でトークンがバケットに追加されます。バケットはこれらのトークンを保持し、そのサイズには上限があります。バケットが満杯の場合、新たに生成されるトークンは失われます。

リクエストが行われると、バケットからトークンが消費されます。処理するためには、必要な数のトークンがバケット内に存在する必要があります。十分なトークンがない場合、リクエストは待機状態に入ります。

このアルゴリズムによるレートリミットは、トークンの生成速度とバケットのサイズによって決定されます。これにより、長期的には一定のレートが保たれる一方で、バケットが満杯の状態では短期間のバーストが可能になります。

もっとわかりやすく説明してほしい!という方には下記のブログをおすすめします。

トークンバケットアルゴリズムではない、MP アルゴリズムと呼べ

Node.jsにおける実装例

Node.js での実装例を簡単にまとめます。

ライブラリの活用

トークバケットアルゴリズムを自前で実装するとそこそこの工数はかかりそうだということで、ライブラリがないか探してみます。 npm trends をみてみるとレートリミットを実装するものがいくつか見つかり、 limiter というライブラリが最も利用されてることがわかりました。 (比較的レートリミット系ライブラリ自体あまり出回っていないようです)

参照: npm trends - bottleneck vs leaky-bucket vs limiter vs tokenbucket

そこで今回は limiter を利用します。

実装例

コードの概要としては、リクエストを受け付けるごとに重い処理が非同期に実行される機能を想定しています。 そのため express でサーバーをたて、 bull というライブラリを用いて local 環境で動作するキューワーカーを立ち上げます。

キューに追加されたジョブを処理する箇所でレートリミットを設定することで、クライアントからのリクエストは受け取りつつ、ミッションクリティカルな処理の実行はトークバケットを利用したレートリミット制限下で実行できる実装となっています。

github.com

const express = require("express");
const Queue = require("bull");
const { TokenBucket } = require("limiter");

// トークンバケットによるレートリミッターを設定
const bucket = new TokenBucket({
  bucketSize: 2, // トークンの最大保持可能数
  tokensPerInterval: 1, // 1分間に生成されるトークン数
  interval: "minute", // トークン生成の間隔
});

// キューの作成
const myQueue = new Queue("myQueue");

const addJobToQueue = (data) => {
  myQueue.add(data);
}

const someProcess = () => {
  const currentTime = new Date().toLocaleString();
  console.log(`Processing at: ${currentTime}`);
};

// キューでのジョブの処理
myQueue.process(async function(job, done) {
  console.log("Processing job");
  await bucket.removeTokens(1);
  someProcess();
  done();
});

const app = express();
app.use(express.json());

app.post("/job", (req, res) => {
  addJobToQueue(req.body);
  res.send("Job added to queue");
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

それでは具体的に下記の設定でトークバケットを動かしてみます。

バケットサイズ: 2 トークンの生成速度: 1 個/分 処理実行時のトークンの消費数: 1 個

下記の振る舞いが確認できれば機能していることがわかります。

  1. 3分間待機してバケットトークンが溜まるのを待つ。バケットトークンを保持できる最大数(バケットサイズ)を2に設定しているため、3つめに生成されたトークンは破棄される。
  2. curlにて連続で3回リクエストを送信する。2個は即座に処理され、1分後にバケットに空きがある状態でトークンが生成されるため、生成されたトークンを使い残りの処理が実行される。

実行結果:

output

想定した動作をすることが確認できました。

おわりに

今回は受け付けたリクエストに対して非同期に処理を行う Node.js サーバーアプリケーションにおいてトークバケットを用いたレートリミットを実装してみました。 レートリミットのアルゴリズムはとっつきづらいイメージを持ちがちですが、1度理解できると便利なので読んでいただいた方の学習の促進に少しでも繋がると幸いです。 また、本記事内でリンクを貼らせていただいている関連の記事もわかりやすいのでぜひご覧ください。

BigQuery やさしいはじめの一歩〜実際に触って理解するデータ操作ワークショップ〜

この記事は個人ブログと同じ内容です

www.ritolab.com


BigQuery

BigQueryは、Google Cloud Platform(GCP)が提供するフルマネージドのサーバーレスデータウェアハウスです。膨大な量のデータを高速に分析することが可能で、データ分析やビジネスインテリジェンス(BI)の分野で広く利用されています。

BigQuery では、データに対してクエリを実行することで、ほしいデータに絞って取得したり、集計したりなど、データを分析することができます。

BigQuery - Google Cloud

対象

本記事は、BigQuery を用いてデータの操作を行いたい人が対象です。

  • 本記事はワークショップ(ハンズオン)形式で構成しており、実際に手を動かしながら、記事の通りにデータ操作を行っていくことで、BigQuery に関する基礎的な理解を深められます。
  • 非エンジニアで、データ操作が初めてのひとを対象にしています。

BigQuery の料金体系と無料枠

まずはじめに大切なお金のことから。BigQuery は、全てを無料で利用できるものではありません。まずは料金体系について知っておきましょう。(本記事は無料枠内で実施します)

BigQuery の料金体系は、大きく分けて以下の2つがあります。

  • ストレージ料金
    • データの保存量に対して課金されます。
  • 分析料金
    • クエリでスキャンされたデータに対して課金されます。

また、BigQuery には無料枠が用意されています。

  • ストレージ料金
    • 10GB まで無料
  • 分析料金
    • 毎月 1TB まで無料

つまり、10GB 以内のデータを保存し、合計 1TB 以内のクエリを実行する場合、無料枠の範囲内であれば、料金は発生しません。

(BigQuery は、デフォルトでオンデマンド分析料金が適用されます。オンデマンド分析料金は、クエリでスキャンされたデータに対して課金されます。)

BigQuery の料金 - Google Cloud

BigQuery サンドボックスで利用を開始する

Google アカウントにログインした状態で以下の URL にアクセスすると、サンドボックスモードで BigQuery を利用できます。(クレジットカードの登録不要)

https://console.cloud.google.com/bigquery

アクセス後の BigQuery コンソール画面です。

BigQuery でのデータ管理

BigQuery には、「プロジェクト」「データセット」「テーブル」という分類があり、それぞれが階層になりリソース(データ)を分類します。

  • プロジェクト(大分類)
    • データセット(小分類)
      • テーブル(データ)

これらの分類を以てリソースを管理します。プロジェクトとは、BigQuery の最も最上階の分類です。

例えば、以下のように分類を分けて、データの管理を分かりやすくしましょうというお話です。

  • 練習用(プロジェクト)
    • アイスクリームの売上(データセット
      • 店舗売上データ(テーブル)
      • 地域天気データ(テーブル)
      • 人件費データ(テーブル)
    • 模試の成績(データセット
      • 学校データ(テーブル)
      • 生徒データ(テーブル)
      • 模試結果データ(テーブル)

プロジェクト・データセット・テーブルは複数作成できます。

なお、プロジェクトやデータセットは必須のため、データを操作する前に、これから以下の作業を行います。

  1. プロジェクトを作成する
  2. データセットを作成する
  3. テーブルを作成する(データのインポート)

プロジェクトを作成する

プロジェクトを作成します。[プロジェクトを作成] を押下します。

プロジェクト名には任意の名前を入力し、[作成] ボタンを押下します。

プロジェクトの作成に成功すると、BigQuery API の有効化画面に遷移するので、[有効にする] ボタンを押下して有効にします。

しばらくするとコンソール画面に戻ってきます。これでプロジェクトの作成は完了です。作成したプロジェクト名が表示されています。

データセットを作成する

次に、データセットを作成します。

データセットとは、これからプロジェクトの中に作成していく、いくつかのテーブル(表データ)をグループ分けするものだと考えてください。

左メニューに表示されているデータセット名の左側に、三角のアイコンがあるので押下してみてください。

現在はまだデータセットを作成していない状態のため、プロジェクト以下を見ても「外部接続」という表示しかありません。

データセットを作成すると、ここに新しいグループが作成されます。

それでは、データセットを作成します。プロジェクト名の右側にある三点リーダーを押下し、表示されるメニューから「データセットを作成」を押下します。

データセット作成フォームが表示されるので、それぞれ以下のフォームに入力を行います。

  • データセット ID
    • データセットの名前です。英数字とアンダースコアを使って任意の名前をつけましょう。ここでは sample_ice_cream としています。
  • ロケーション タイプ
    • リージョン を選択します。
    • リージョン: あなたがもし日本で作業しているのならば asia-northeast1(東京) を選択します。

入力が完了したら [データセットを作成] ボタンを押下します。

作成が完了すると、プロジェクト配下にデータセットが表示され、新たなデータセットが作成されたことを確認できます。

これでデータセットの作成は完了です。

テーブルを作成する(データのインポート)

分析する対象となるデータをインポート(読み込み)し、テーブルを作成してみましょう。テーブルとは、例えばスプレッドシートやエクセルでいうところの表データです。

データを読み込みテーブルを作成すると、上のキャプチャ画像のスプレッドシートのように、行と列で構成されたデータがそのまま BigQuery のデータベース上に入るとイメージしてください。

練習用データ

今回使用するのは、練習用のデータとして作成した「とある店舗でのアイスクリーム販売履歴」です。

データ概要

  • データ日時範囲:
    • 2023/01/01 〜 2024/12/31 までの 2 年間
  • データ件数:
    • 10000 件
  • カラム(列)
    • purchased_at: 購入日時
    • store: 店舗
      • 店舗 A, B, C, D の 4 店舗
    • item_name: 品名
    • amount: 金額
    • member_id: ポイントメンバー会員 ID

練習用データは公開しているので誰でもアクセスできます。
BigQueryはじめの一歩サンプルデータ - 1. アイスクリーム販売履歴(スプレッドシート)

データの読み込みは CSV ファイルで行いますので、練習用データのスプレッドシートCSV ファイルとしてダウンロードしてください。

ファイル > ダウンロード > カンマ区切り形式(.csv

テーブル作成

それではデータをインポートしテーブルを作成します。

コンソール画面「独自データの追加」から「ローカル ファイル」にある [データを追加する] を押下します。

テーブル作成フォームが表示されるので、それぞれ以下のフォームに入力を行います。少し入力が多いですが頑張ってください。

  • ファイルを選択
    • ダウンロードした練習用 CSV ファイルを選択します。
  • ファイル形式
    • ファイルを選択すると自動的に CSV になります。CSV にならない場合は選択してください。
  • データセット
    • 先程作成したデータセット名を入力します。ここでは sample_ice_cream としています。
  • テーブル
    • 読み込むデータの名前です。英数字とアンダースコアを用いて任意の名前をつけましょう。アイスクリームの販売履歴なので、ここでは ice_cream_sales としています。
  • スキーマ
    • 以下を入力しましょう。モードと説明はデフォルトのままで構いません。
      • [1] フィールド名: purchased_at, タイプ: DATETIME,
      • [2] フィールド名: store, タイプ: STRING,
      • [3] フィールド名: item_name, タイプ: STRING,
      • [4] フィールド名: amount, タイプ: INTEGER,
      • [5] フィールド名: member_id, タイプ: INTEGER,
  • 詳細オプション
    • スキップするヘッター行
      • 練習用データの先頭行にはカラム(列)名が入っているため、1 を入力します。

全て入力できたら、[テーブを作成] ボタンを押下します。

テーブルの作成が完了すると、BigQuery コンソール画面にテーブルが表示されます。sample_ice_cream データセット配下に、ice_cream_sales テーブルが作成されたことが確認できます。

プレビューというタブを押下すると、ice_cream_sales テーブルに入っているデータを確認できます。

これでテーブルの作成(データのインポート)は完了です。

次の章から、実際にクエリを実行してみましょう。

データベースと会話する

これは先程作成した、アイスクリームの販売履歴のテーブルのデータです。

こういった、スプレッドシートやエクセルのように、行と列で構成されるテーブルを持つデータベースを「リレーショナルデータベース」と呼びます。

リレーショナルデータベースの詳細についてはここでは重要ではないので割愛します。

そして文字が長いので、本記事では「リレーショナルデータベース」を「データベース」と呼ぶことにします。

データベースの種別はリレーショナルデータベース以外にも存在しますが、本記事で「データベース」と言ったらリレーショナルデータベースのことだと思ってください。

さて。

BigQuery に限らず、データベースからほしいデータに絞ってを取得したり、データを集計したりなど加工したりするには、SQL という言語を使います。

日本人と会話するなら日本語、英語圏の人と会話するなら英語。のように、データベースと対話するなら SQL といったイメージです。

SQL を使えばデータベースと会話ができて、データベースが応答してくれます。


あなた: 「あのデータをちょうだい(SELECT id, name FROM my_table ORDER BY id DESC LIMIT 100)」

データベース: 「はい、これですよ(id, mane, 1, xxx, 2 ,xxx ,...)」

あなた: 「このデータをあのテーブルにしまっておいて(INSERT my_table (id, name) VALUES (123, xxx))」

データベース: 「はい、テーブルにレコードを追加しました(Query OK, 1 row affected)」


SQL

SQL とは、Structured Query Language(構造化クエリ言語)の略称で、データベースを操作するための言語です。

SQL を使えば、データベースにデータを追加したり、データを変更したり、データを検索したりすることができます。

データベースには様々な製品があり、BigQuery もその 1 つです。代表的なものだと他には、MySQL, PostgreSQL, Oracle Database, Microsoft SQL Server などがありますが、これらを操作するための言語のベースは全て SQL です。

データベースを操作する上で SQL は共通言語であり、その上で、各データベース製品によって若干文法が違ったりします。

つまり、SQL を覚えれば、あとは各データベース製品ごとの書き方のお約束を知るだけで、BigQuery でも MySQL でも操作できるようになります。

SQL を「言語」とするならば、各データベース製品ごとの文法の違いは「方言」のようなものだと思ってください。

SQL の基本

SQL の文法はシンプルです。データを取得する時の基本的な文法は以下になります。

SELECT
  見たい列の名前 1,
  見たい列の名前 2,
  見たい列の名前 3,
FROM テーブル名
;

SELECT 句は、データを取得する際の構文です。見たいカラム(行)を指定して、それらをどのテーブルから取得するのかを FROM 句で指定してあげるだけです。

SELECT
  purchased_at,
  store
FROM ice_cream_sales
;

上記のように、見やすいように改行しても良いし、1 行で書いても同じことです。

SELECT purchased_at, store FROM ice_cream_sales;

この構文をベースとして、あとは並び順を変えたり、条件によってデータを絞ったり、集計したりできます。この辺りは実際に BigQuery でクエリ(こういった SQL で作成した文)を実行していきながら見ていきましょう。

データを取得する(基本)

それでは実際に、BigQuery でクエリを実行しデータを取得してみましょう。

タブの右側に [+] のボタンがあるので押下してください。

「無題」というタブが作成されます。ここにクエリを書いて実行していきます。

データの確認

クエリを書く前に、再度、アイスクリームの販売履歴のテーブルのデータを確認をしましょう。

  • データセット名: sample_ice_cream
  • テーブル名: ice_cream_sales

それぞれのカラム(列)の意味するものは以下でしたね。

  • purchased_at: 購入日時
  • store: 店舗
  • item_name: 品名
  • amount: 金額
  • member_id: ポイントメンバー会員 ID

これからこのデータを使って、見たい情報だけに絞ったり、並び順を変えたり、集計したりをしていきます。

基本の取得

まずは、条件など無しに、シンプルにデータを取得してみましょう。BigQuery での取得のお作法は以下になります。

SELECT
  見たい列の名前,
FROM `プロジェクト名.データセット名.テーブル名`

FROM 句は、以下のどちらかで記述しテーブルを指定する必要があります。

プロジェクト名まで書くと長いので、`データセット名.テーブル名` で FROM 句を書く書き方でクエリを作成してみましょう。

SELECT
  purchased_at,
  store,
  item_name,
  amount,
  member_id,
FROM `sample_ice_cream.ice_cream_sales`
;

これを、先程の「無題」のタブにあるエディターに記述します。

記述できたら、上にある [実行] ボタンを押下してクエリを実行してみましょう。

上手くいけば、クエリ結果としてデータが返ってくるはずです。

おめでとうございます。あなたは今、BigQuery でクエリを実行してデータを取得することができました。

覚えておきたいお金のこと

さて、無事に最初のクエリを実行できました。ここで一度、BigQuery での課金についておさらいしておきましょう。

BigQuery の料金体系は、大きく分けて以下の2つがあります。

- ストレージ料金
  - データの保存量に対して課金されます。
- 分析料金
  - クエリでスキャンされたデータに対して課金されます。

また、BigQuery には無料枠が用意されています。

- ストレージ料金
  - 10GB まで無料
- 分析料金
  - 毎月 1TB まで無料

つまり、10GB 以内のデータを保存し、合計 1TB 以内のクエリを実行する場合、無料枠の範囲内であれば、料金は発生しません。

(BigQuery は、デフォルトでオンデマンド分析料金が適用されます。オンデマンド分析料金は、クエリでスキャンされたデータに対して課金されます。)

先ほど行った「クエリを実行する」という行為は、上記の「分析料金: クエリでスキャンされたデータに対して課金されます。」に該当します。

そしてそれは、毎月 1TB まで無料ということになっています。

ここで、先程のエディターに記述したクエリをカットアンドペーストして貼り付け直してみてください。エディターの右上にこんな表示が出てきます。

このクエリを実行すると、461.8 KB が処理されます。

この、461.8 KB が、今回のクエリでスキャンされたデータ量です。

1TB は 1024 GBですから、1 回の実行で 461.8KB を消費する場合、このクエリを約 2200 回程度実行出来ることになります。

そしてこれは、テーブルに収録されているデータ量が増えれば、その分スキャンするデータ量も増えていくことになります。

本記事はサンドボックスモードにて、課金されない範疇で実施するので基本的には問題はありませんが、例えば自身の所属企業の BigQuery などで大量のデータを取り扱っている場合は、特にスキャン量と課金額には注意してください。(不安な場合は管理者に聞いてみましょう)

費用の見積もりと管理 - BigQuery, Google Cloud

データを取得する(色々な取得)

ではここから、アイスクリーム販売履歴データについて、色々な側面からクエリを実行しデータを取得してみましょう。

なお、ここから紹介する文法は BigQuery に限らず、他のデータベース製品でも共通で使えるものです。(BigQuery 独自の記法は随時お知らせします)

絞り込み(WHERE)

このデータは、店舗 A, B, C, D の 4 店舗分のデータが入っています。店舗 A の売上のみを見てみたい場合は WHERE 句を使ってデータを絞り込みます。

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
WHERE store = '店舗 A' -- 店舗 A のデータのみ
;

BigQuery コンソール画面のエディターに入力して実行してみましょう。店舗 A のデータのみが取得できます。

絞り込みは複数指定できます。これに、商品名「ハーゲンダッツ」も追加してみましょう。WHERE 句に AND をつけてつけて追加します。

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
-- 店舗 A かつ ハーゲンダッツのデータのみ
WHERE store = '店舗 A'
  AND item_name = 'ハーゲンダッツ'
;

「商品は PARM とハーゲンダッツ」といった、1 つのカラムに対する複数の絞り込みには IN が使えます。

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
-- 店舗 A かつ PARM とハーゲンダッツのデータのみ
WHERE store = '店舗 A'
  AND item_name IN ('PARM', 'ハーゲンダッツ')
;

最後は、店舗 A の、単価が 100 円未満の売上データを出してみます。

「店舗 A である」は等号(=)を使うのに対して、「100 円未満である」は不等号(>, <)を用います。

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
-- 店舗 A かつ 単価が 100 円未満の売上データのみ
WHERE store = '店舗 A'
  AND amount < 100
;

このように、WHERE 句を使うと様々な絞り込みができます。(他にも WHERE を使った絞り込み方法があるので気になる場合は調べてみましょう)

並び替え(ORDER BY)

明示的に並び順を指定することで、意図した順番で結果が返ってきます。

「売り上げた日時で並べたい」「売上金額順に並べたい」などです。

店舗 A のデータに対して、売り上げた日時で並べてみましょう。並び順の指定には、ORDER BY 句を用います。

昇順(過去->未来)で並べる場合: ASC

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
WHERE store = '店舗 A'
ORDER BY purchased_at ASC -- 結果を売上日時の昇順に並べる
;

クエリ結果が売上日時の昇順で並びました。

降順(未来->過去)で並べる場合: DESC

SELECT
  purchased_at,
  store,
  item_name,
  amount,
FROM `sample_ice_cream.ice_cream_sales`
WHERE store = '店舗 A'
ORDER BY purchased_at DESC -- 結果を売上日時の降順に並べる
;

クエリ結果が売上日時の降順で並びました。

テーブル同士をつなげる

さて、データベースでは、以下のように、関連する別のデータが別のテーブルになっている状況を良く見かけます。

アイスクリームの販売履歴テーブル

  • purchased_at: 購入日時
  • store: 店舗
  • item_name: 品名
  • amount: 金額
  • member_id: ポイントメンバー会員 ID

ポイントメンバー会員テーブル

  • id: ポイントメンバー会員 ID
  • name: 氏名
  • birth_date: 生年月日
  • gender: 性別

アイスクリームの販売履歴テーブルには、ポイントメンバー会員の ID があり、ポイントメンバー会員の情報はポイントメンバー会員テーブルで持っているような、いわゆる「リレーション(関係・関連)」の構造です。 (冒頭で出てきた「リレーショナルデータベース」の醍醐味でもあります)

これらを繋げてデータを取得してみましょう。

テーブル作成

以下の練習用データをダウンロードし、テーブル作成と同じ要領で BigQuery にインポートしテーブルを作成してください。

BigQueryはじめの一歩サンプルデータ - 2. ポイントメンバー会員(スプレッドシート)

  • データセット: sample_ice_cream(アイスクリームの販売履歴テーブルと同じ)
  • テーブル名: members
  • スキーマ:
    • id: INTEGER
    • name: STRING
    • birth_date: DATE
    • gender: STRING
  • スキップするヘッダー行: 1

ポイントメンバー会員テーブルが作成できたら、テーブル同士をつなげてみましょう。

テーブルを結合する(JOIN)

テーブルとテーブルを繋げることを「結合」といい、クエリでは JOIN 句を用います。

SELECT
  sales.purchased_at, -- テーブル名.カラム名 の形式にする
  sales.store,
  sales.item_name,
  sales.amount,
  sales.member_id,
  members.name,
  members.birth_date,
  members.gender,
FROM `sample_ice_cream.ice_cream_sales` sales
INNER JOIN `sample_ice_cream.members` members ON sales.member_id = members.id -- members テーブルをつなげる
;

JOIN 句の文法は以下になっています

SELECT
  テーブル名.カラム名,
FROM 結合元テーブル名 (エイリアス)
INNER JOIN 結合したいテーブル名 (エイリアス) ON 結合元テーブル名.結合元のメンバーID = 結合したいテーブル名.メンバーID

もう一つ違う点は、「エイリアス」という短縮名を定義して使っている点です。

FROM `sample_ice_cream.ice_cream_sales` sales -- `sample_ice_cream.ice_cream_sales` を sales という短縮名にします 
INNER JOIN `sample_ice_cream.members` members -- `sample_ice_cream.members` を members という短縮名にします
        ON sales.member_id = members.id

エイリアスを定義すると、その後のテーブル名の表記を、短くしたり分かりやすくできます。(任意の名前をつけられます)

もしもエイリアスを使わないとすると、以下のようなクエリになります。

SELECT
  `sample_ice_cream.ice_cream_sales`.purchased_at,
  `sample_ice_cream.ice_cream_sales`.store,
  `sample_ice_cream.ice_cream_sales`.item_name,
  `sample_ice_cream.ice_cream_sales`.amount,
  `sample_ice_cream.ice_cream_sales`.member_id,
  `sample_ice_cream.members`.name,
  `sample_ice_cream.members`.birth_date,
  `sample_ice_cream.members`.gender,
FROM `sample_ice_cream.ice_cream_sales`
INNER JOIN `sample_ice_cream.members` ON `sample_ice_cream.ice_cream_sales`.member_id = `sample_ice_cream.members`.id
;

冗長で、パッと見てもわかりにくいですよね。エイリアスを定義するとスッキリと書けるのでオススメです。

覚えておきたい INNER JOIN と LEFT JOIN

さて、JOIN 句にはいくつか種類があります。最低限覚えておきたいのは INNER JOINLEFT JOIN です。

  • INNER JOIN
    • 結合元テーブルのレコード(行)に対して、結合したいテーブルのレコードがマッチングしたものだけを結果として返す
  • LEFT JOIN
    • 結合元テーブルのレコードに対して、結合したいテーブルのレコードがマッチングしないものでも結果として返す

アイスクリームの販売履歴のデータには member_id というカラム(列)がありますが、必ずしも会員 ID が入っているわけではありません。

ポイントメンバー会員ではない人の購入データもあるということですね。会員 ID の無い場合は null と表示されています。

この時に、INNER JOINLEFT JOIN の違いを見てみましょう。

INNER JOIN

INNER JOIN を用いると、結合元テーブルのレコード(行)に対して、結合したいテーブルのレコードがマッチングしたものだけを結果として返します。

つまり、sales.member_id に値が入っていて、なおかつ members.id と合致したレコードのみが結果として返ることになります。

SELECT
  sales.purchased_at,
  sales.store,
  sales.member_id,
  members.name,
  members.birth_date,
  members.gender,
FROM `sample_ice_cream.ice_cream_sales` sales
INNER JOIN `sample_ice_cream.members` members ON sales.member_id = members.id
ORDER BY sales.purchased_at
;

LEFT JOIN

LEFT JOIN を用いると、結合元テーブルのレコード(行)に対して、結合したいテーブルのレコードがマッチングしなかったものも結果として返します。

つまり、sales.member_id が NULL のレコードや、members.id と一致しない sales のレコードも結果として返ることになります。

SELECT
  sales.purchased_at,
  sales.store,
  sales.member_id,
  members.name,
  members.birth_date,
  members.gender,
FROM `sample_ice_cream.ice_cream_sales` sales
LEFT JOIN `sample_ice_cream.members` members ON sales.member_id = members.id
ORDER BY sales.purchased_at
;

INNER JOIN と LEFT JOIN のどちらを使うかは、データの特徴や得たい結果を考慮して選択しましょう。そのために、クエリを書く前にデータをしっかり目視で眺めるというのも大切です。

データを集計する

最後に、データを集計してみましょう。集計は、値を合計したり、平均を出したりなどすることです。

データをまとめて、集計する(GROUP BY)

データを集計するには GROUP BY 句を用います。

SELECT
  store,
  SUM(amount) as total_sales,    -- 総売上
  COUNT(amount) as number_sales, -- 販売数
  AVG(amount) as average_sales,  -- 平均売上
  MIN(amount) as max_unit_price, -- 最大単価 
  MAX(amount) as min_unit_privc, -- 最小単価
FROM `sample_ice_cream.ice_cream_sales`
GROUP BY store -- 店舗ごとに集計します
ORDER BY store
;

GROUP BY store とすることで、「店舗ごとに集計する」という意味になります。

GROUP BY を指定したら、SELECT 句で「合計」や「平均」など、どんな計算をしたいかを指定すれば、店舗ごとに集計することができます。

このクエリでは、店舗ごとの全期間の売上や販売数などを集計しています。

結果を見てみると、店舗 B は店舗 A よりも売上は多いですが、平均売上は低いです。つまり、店舗 B は店舗 A よりもお安めのアイスが売れていて、単価よりも販売数で稼いでいる。というのが相対的にわかります。

こんなふうに、集計するとこれまではただの履歴でしかなかったデータが意味を持ち、そこから発見が生まれます。データを分析することの醍醐味ですね。

GROUP BY についての、BigQuery における SUM や AVG などの集計関数は以下に一覧がありますので参考にしてください。

集計関数 / レガシー SQL 関数と演算子 - BigQuery, Google Cloud

データから意味や価値を引き出す

データ分析とは、データから意味や価値を引き出すことです。たくさんの情報を整理して意味を見つけ出すプロセスと言えるでしょう。

大量のパズルピースが散らばっている状態で、それらを組み合わせて全体の絵を作り上げるようなものです。

データ分析を行うことで、ビジネスや研究などの分野で重要な判断材料を得ることができます。例えば、商品の売れ行きや広告の効果を分析して、より効果的な戦略を立てることができます。

BigQuery を用いれば、大量のデータを蓄積できること、そしてそれらを分析することが可能です。

BigQuery でやれることはまだまだ沢山あり、今回紹介したことは一部に過ぎませんが、これを期に実際に生きたデータに触れることで、より理解も深められると思います。

ここまで実際に手を動かしてみて、なんとなくの使い方が理解いただけたら幸いです。


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers

Looker Studio やさしいはじめの一歩〜実際に触って理解するレポート作成ワークショップ〜

この記事は個人ブログと同じ内容です

www.ritolab.com


蓄積した大量のデータを収集・分析・加工し、事業戦略の意思決定を支援する。いわゆる「ビジネス・インテリジェンス(BI)」という言葉が当たり前に聞かれるようになって数十年。これまでたくさんのツールが生まれてきました。

今回は、データを視覚化するためのツール Looker Studio の使い方を解説します。

Looker Studio

Looker Studioは、Google が提供する BI ツールです。さまざまなデータソースからデータを収集し、視覚化されたレポートやダッシュボードを作成・共有することができます。

Looker Studio

Looker Studio は、Google アカウントを持っていれば誰でも無料で使うことができます。(厳密には有料の機能があります。本記事では無料の範囲で実施します。)

対象

本記事は、Looker Studio を用いてデータの可視化を行いたい人が対象です。

  • ワークショップ(ハンズオン)形式で構成しており、実際に手を動かしながら、記事の通りにレポート作成を行っていくことで、Looker Studio に関する基礎的な理解を深められます。
  • エンジニアでなくても実施できるように構成しています。

レポートの作成を開始しよう

何はともあれ、使ってみましょう。Looker Studio にアクセスし、「使ってみる」ボタンを押下します。

レポート一覧の画面に遷移します。

まだ何もレポートを作成していないため、空っぽの状態です。

「空のレポート」を押下します。

アカウント設定のモーダルが表示されるので、入力と同意を行い、「続行」ボタンを押下します。

アカウント設定が完了したら、再度「空のレポート」を押下しましょう。

レポートの作成画面に遷移しますが、「データのレポートへの追加」が開くので右上のバツを押下して閉じておきます。

これでレポートの作成を開始できました。

ここにグラフなどを設置してレポートを仕上げていくことになります。レポートの名前は、画面左上の「無題のレポート」と表示されているところを押下すれば変更できます。

データを追加しよう

チャート(グラフ)を描画するためには、元となるデータが必要です。

データを読み込んでみましょう。

今回は、練習用のデータとして「とある店舗でのアイスクリームの販売履歴」をスプレッドシートで用意しました。

データ概要

  • データ日時範囲:
    • 2023/01/01 〜 2024/12/31 までの 2 年間
  • データ件数:
    • 10000 件
  • 店舗:
    • 店舗 A, B, C, D の 4 店舗
  • アイスクリーム品目:
  • リピーターによる購入かどうか:
    • 1: YES
    • 0: NO

練習用データは公開しているので誰でもアクセスできます。適宜、ご自身のスプレッドシートにコピーするなどして利用してください。

LookerStudioはじめの一歩サンプルデータ(スプレッドシート)

1. 「データを追加」ボタンを押下します

2. 「Google スプレッドシート」を押下します。

このとき、「Looker Studioに Google スプレッドシートへのアクセス権を許可してください。」という表示が出た場合は「承認」ボタンを押下し、自身の Google アカウントにログインします。

3. Google スプレッドシートの選択画面で、追加したいデータのシートを選択します。

  1. スプレッドシートを選択
  2. シートを選択
  3. 「追加」ボタンを押下

このとき、「このレポートにデータを追加しようとしています 」というモーダルが表示されたら「レポートに追加」ボタンを押下します。

しばらく待つと、データのレポートへの追加選択画面が非表示になり、元の画面に戻ってきます。

データの追加に成功しました。

データを読み込んだ際に作成された表は利用しないため削除してしまいましょう。

これでデータの読み込みは完了です。

チャートを作成してみよう

それでは実際にチャートを作ってみましょう。手始めに「売上の推移」をチャートにしてみます。

1. チャートの設置

「グラフを追加」ボタンを押下します。

「縦棒グラフ」のボタンを押下します。

「クリックして追加するか、ドラッグして描画します」という枠が現れるので、チャートを作成したい場所を選んでクリックします。

棒グラフが追加されました。

グラフの右下をドラッグして引っ張ってあげれば、グラフ描画の領域を広げることができます。

見やすくなるようにグラフの描画領域を広げておきましょう。

2. チャートの設定

設置したチャートを操作して、売上の推移の棒グラフを作成していきます。

棒グラフを選択した状態で、右側にあるメニューを操作していきます。

01. ディメンションの指定

「ディメンション」を見ると「店舗」になっています。ここを「購入日時」に変更します。

グラフが変化したのに気づきましたか?ディメンションを「店舗」から「購入日時」に変更したことによって、これまで X 軸は「店舗」でしたが、「購入日時」になりました。

02. 指標の指定

次に、「指標」を指定します。指標には「売上」を指定します。先程と同じ要領で、「Record Count」から「売上」に変更しましょう。

グラフがまた変化しましたね。Y 軸が売上になりました。

03. チャートの並べ替え

さて、ここで棒グラフを見てみると、並び順がなんだかよくわからないことになっていることに気づきます。日時がバラバラに並んでしまっているようです。

これは、グラフの並び順が「売上の降順(大きい順)」に設定されていることが原因です。

現在作成しているチャートは「売上の推移」ですから、日時がきちんと順番に並んでほしいですね。

並べ替えを「購入日時の昇順」に変更しましょう。そうすることで、グラフの並びが購入日時の古い順になります。

ディメンションと指標

ディメンションとは、を指しています。

グラフを作成する際に、「何を軸として見るか」をここに指定します。今回は「購入日時」を指定しました。時系列を軸として見たいからです。

指標とはその名の通り、評価する値を指します。

グラフを作成する際に、「どの数字を評価したいか」をここに指定します。今回は「売上」を指定しました。

つまり、このグラフでは「購入日時を軸として、売上を見る」=「購入日時の経過に対する売上の推移」というグラフを作成したことになります。

このように、順序立てて考えると、ディメンションには何を指定するべきか、指標には何を指定するべきか。が簡単にイメージできます。

ディメンション、指標にはそれぞれ何を指定すれば良いのか迷った時は、「何を軸にして、何の数値を評価したいのか」を考えてみてください。

チャートを便利にしてみよう

1. 日次・月次・年次で集計したグラフにする

さて、ここまでで売上推移のグラフが作成できましたが、よく見てみると、日時(年/月/日 時:分:秒)ごとでグラフが並んでいます。

これは、「この日時に売上があったものを集計してグラフに表示してる」という状態になっています。

いくらなんでも、時・分・秒という細かい粒度で売上を集計して見ても何かの気づきは得られにくいですよね。せめて、日・週・月・四半期・年などで集計してグラフに表示したいところです。

この操作を行っていきましょう。

01. 購入日時を日・月・年に丸める

現在、ディメンションには「購入日時」を指定していますが、ここを操作し、購入日時を日・月・年に丸めていきます。

ディメンション「購入日時」の左にカレンダーのアイコンがあるので、そこを押下します。

すると、小さなポップアップが開くので、「データの種類」を 「年」に変更します。

ディメンションが「購入日時(年)」となり、棒グラフが年ごとの売上に集計されて表示されました。

続いて、同じ要領でディメンションを「月」に変更してみましょう。データの種類は「年、月」を選択します。

月次で集計された棒グラフになりました。

最後に日次の棒グラフを表示させましょう。

ここまでくればもうおわかりですね。ディメンションを「日付」にすれば、日次で集計されたグラフにすることができます。

日次で集計された棒グラフになりました。

2. 日次・月次・年次を切り替えられるようにする

チャートから気づきを得るためには、日次だけ、月次だけ、年次だけといったように集計単位を固定せずに見られることが大切です。

先程までは、これらのいずれかにグラフを設定しましたが、これを切り替えて見られるようにしましょう。

01. ドリルダウンの有効化

ディメンションに「ドリルダウン」のトグルスイッチがあります。

これを有効化します。すると、ディメンションに 3 つの「購入日時(日付)」 がセットされます。

02. ディメンションの指定 

この 3 つのディメンションに対して、上の 2 つをそれぞれ上からから「購入日時(年)」「購入日時(年、月)」に変更します。

03. デフォルトドリルダウンレベルの指定

ディメンション下部に「デフォルトのドリルダウンのレベル」という項目があります。

現在は「購入日時(日付)」となっていますが、ここを「購入日時(年、月)」に変更します。

棒グラフの表示が月次集計の表示に変化しました。この指定によって、このレポートを開いた時は、このグラフは月次で集計されたグラフとして表示されるようになります。

これで集計単位の設定が完了しました。

04. グラフの集計単位を切り替える

実際にグラフの集計単位を切り替えてみましょう。

グラフの右上に、いくつかのアイコンが並んでいます。

このアイコンの中の「上矢印 」と「下矢印 」が集計単位を切り替えるボタンとなります。

ドリルアップは、集計を現在より 1 つ高レベルに変更します。

ドリルダウンは、集計を現在より 1 つ低レベルに変更します。

現在、ドリルダウンのデフォルトは「月次」ですので、ドリルアップを行えば「年次」の表示に、ドリルダウンを行えば「日次」になります。

実際に押下してみてください。棒グラフの集計が年次・月次・日次で切り替わります。

年次表示

月次表示

日次表示

3. 棒グラフに表示する棒の数を調整する

さて、現在のグラフを月次で確認してみましょう。

読み込んだデータは 2023 年 1 月〜 2024 年 12 月まであるはずですが、グラフに表示されているのは 2023 年 10 月までです。

棒グラフにおいて、いくつの棒を表示するかというのはスタイルで設定できます。

現在は「棒の数」が 10 と指定されているため、グラフには 10 本の棒が表示されているという状態になっています。

例えばこれを 100 に設定すれば、最高で 100 本の棒が描画されるようになるため、より多くのデータを確認できることになります。

ただし、何事も多ければ良いというわけではありません。例えば棒の数 100 本の設定で日次集計で表示するとどうなるでしょう。

逆多すぎて見にくいですね。例えば先月分だけ見たい場合があったとすると、この細さは辛いです。

このように、ピンポイントで「この期間だけ見たい」という場合もあるでしょう。

次でその方法を紹介します。

4. 表示するグラフの期間を選択できるようにする

現在、グラフの棒の数は 10 に設定しています。

ここから、期間指定を行い、その期間のグラフを表示させていきます。

01. コントロールの追加

画面上部にある「コントロールの追加」を押下し、「期間設定」を選択します。

「クリックして追加するか、ドラッグして描画します」という枠が出てくるので、グラフを追加した時と同じ要領で、棒グラフの上部の空いているスペースを押下します。

「期間を選択」というプルダウンメニューが設置されます。これが期間指定を行う際のコントローラーになります。

02. 実際に期間指定をしてみる

日付指定のプルダウンメニューが設置されたので、実際に日付範囲を指定してみましょう。

2024 年 1 月〜 2024 年 6 月までを指定してみます。日付を指定したら、「適用」ボタンを押下します。

すると、2024 年 1 月〜 2024 年 6 月までのグラフが描画されました。

これで、見たい日付の範囲でグラフが見られるようになりました。

03. デフォルトの期間指定

データは 2024 年 12 月までありますが、デフォルトで表示されているのは 2023 年 1 月〜 10 月のグラフです。

どうせなら古いデータのグラフではなく、現在の年月データを棒グラフに含めて表示させたいので、これを設定していきます。

現在の年月から 3 ヶ月間のデータを、デフォルトで表示させるようにしましょう。

期間指定のコントローラーを選択状態にすると、右メニューに「デフォルトの日付範囲」という項目が表示されます。

デフォルトの日付範囲は 「自動期間」に指定されていますが、ここを変更していきます。

デフォルトの日付範囲のプルダウンメニューを開くとカレンダーが表示されますので、右上に「自動期間」と表示されているプルダウンメニューをさらに開きます。

プルダウンメニューより「詳細設定」を選択します。

するとカレンダーの表示が変化するので、開始日の欄で上から 3 つ目の数値を 3と入力し、 4 つ目の欄を「」に指定します。

入力したら「適用」ボタンを押下します。

これで「開始月は現在から 3 か月前」という指定を行ったことになります。

記事執筆時点は 2023 年 11 月ですので、レポートを開くと自動で 2023/08 〜 11 月までのグラフが表示されるようになります。

これでデフォルトの期間指定は完了です。

はじめの一歩、おめでとうございます。

Looker Studio で売上推移のチャートを作成しました。成果物として、売上推移のグラフをレポート上で操作し見られるまでを作成できました。

ここまで理解できたなら、はじめの一歩は十分に踏み出せたと思います。

また、今回用意した練習用データには他の項目もあります。ぜひこれらを使用して別のグラフも作成してみてください。

例えば他にも、以下のような観点でレポートが作れそうです。

  • 店舗別の売上はどうなっている?
  • 売上の中でそれぞれの品目が占める割合はどれくらいだろうか?
  • どの品物が最も売上が多い?販売数は?
  • 売上の中でリピーターはどれくらいいるだろうか?各店舗では?

LookerStudioはじめの一歩サンプルデータ(スプレッドシート)

本記事が、 Looker Studio を使い始めたい誰かの役に立てたら幸いです。


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers

PHP 8.3 の新機能を試してみよう

この記事は個人ブログと同じ内容です

www.ritolab.com


PHP 8.3 が 2024 年 11 月にリリースになります。PHP 8.3 の新しい機能をいくつかピックアップして試してみます。

クラス定数の型指定

クラスで定義する定数に型の指定が行えるようになりました。

Typed class constants
https://wiki.php.net/rfc/typed_class_constants

<?php

class Person {
    const string NAME = 'rito';
}

指定した型ではない値を代入するとエラーになります。

<?php

class Person {
    const string NAME = 777;
}

// Fatal error: Cannot use int as value for class constant Person::NAME of type string in...

クラス定数の動的参照

Dynamic class constant fetch
https://wiki.php.net/rfc/dynamic_class_constant_fetch

クラスで定義した定数を、変数で動的に参照できるようになりました。

<?php

class Person {
    const string NAME = 'rito';
}

$personNameConstant = 'NAME';

echo Person::{$personNameConstant};
// => rito

PHP 8.2 までは constant() 関数を使わないと同様のことはできませんでした。

<?php

echo constant("Person::$personNameConstant");
// => rito

8.3 でも constant() 関数は引き続き使えます。

Override attribute によるオーバーライド指定

Marking overridden methods (#[Override])
https://wiki.php.net/rfc/marking_overriden_methods

PHP は、実装されたメソッドのシグネチャが指定されたインターフェイスまたは親クラスからオーバーライドされたメソッドと互換性があることを検証しますが、メソッドが実際にインターフェイスのメソッドを実装することを目的としているのか、親メソッドをオーバーライドすることを目的としているかどうかの「意図」を確認することができません。

例として、以下の基底クラスと派生クラスがあったとします。派生クラス側で、基底クラスのメソッドをオーバーライドしています。

<?php

class Book {
    protected function setName(string $name): void { /* some processing... */}
}

class PhpReference extends Book {
    // setName() をオーバーライドしている
    public function setName(string $name): void { /* some processing... */}
}

このとき、基底クラス側のメソッド名が変更されたらどうなるでしょう。

<?php

class Book {
    // protected function setName(): void { /* some processing... */}
    // ↓ メソッド名を変更した
    protected function setTitle(string $name): void { /* some processing... */}
}

class PhpReference extends Book {
    // setName() をオーバーライドしていたはずが、基底クラス側のメソッド名が変わったために派生クラス独自のメソッドとなってしまった
    public function setName(string $name): void { /* some processing... */}
}

基底クラス側のメソッド変更によって、派生クラス側のオーバーライドが独自メソッドになってしまいます。つまり、PhpReference クラスは setTitle() 関数も、setName() 関数も動作することになります。

PHP はこういった「意図」の検出ができません。

PHP 8.3 では、Override アトリビュート(#[\Override]) をつけることによって、そのメソッドがオーバーライドであることを示せるようになりました。 これによって、基底クラス側のメソッド名変更があった時にエラーにすることができます。

<?php

class Book {
    protected function setName(): void { /* some processing... */}
}

class PhpReference extends Book {
    #[\Override]
    public function setName(): void { /* some processing... */}
}

基底クラス側でメソッドの変更が行われた場合はエラーが発生します。 

<?php

class Book {
    // protected function setName(): void { /* some processing... */}
    // ↓ メソッド名を変更した
    protected function setTitle(): void { /* some processing... */}
}

class PhpReference extends Book {
    #[\Override]
    public function setName(): void { /* some processing... */}
    // => Fatal error: PhpReference::setName() has #[\Override] attribute, but no matching parent method exists in...
}

INI ファイル環境変数設定におけるデフォルト値設定

php.ini での環境変数設定でデフォルト値が設定可能になりました。

INI ファイル内では環境変数が参照できるため、これと併用することでデフォルト値の運用が可能になります。

まずは PHP 8.2 までの挙動を見てみます。

post_max_size = ${POST_MAX_SIZE}
<?php

/*
 * 〜 PHP 8,2
 */ 

// php.ini
// post_max_size = 8M
echo ini_get('post_max_size');
// => 8M

// 環境変数 POST_MAX_SIZE が設定されている
echo ini_get('post_max_size');
// => 10M

// 環境変数 POST_MAX_SIZE が設定されていない
echo ini_get('post_max_size');
// => ''

PHP 8,2 までは、値が指定されていない場合は未設定となり、値を参照すると空文字が返ります。

PHP 8.3 では、デフォルト値を指定しておけば、環境変数の設定が無い場合もデフォルト値を適用してくれます。

post_max_size = "${POST_MAX_SIZE:-6M}"
<?php

/*
 * PHP 8,3 〜 
 */ 

// 環境変数 POST_MAX_SIZE が設定されている
echo ini_get('post_max_size');
// => 10M

// 環境変数 POST_MAX_SIZE が設定されていない
echo ini_get('post_max_size');
// => 6M

json_validate() 関数

json_validate() 関数が追加されました。

json_validate
https://wiki.php.net/rfc/json_validate

この関数は、文字列が有効な json 文字列であるかどうかを検証します。

これまでは、json_decode() 関数 の結果によってハンドリングすることが多かったはずです。

<?php

$json = '';

try {
    json_decode(json: $json, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    /* some processing... */
}

ただしこの場合、文字列の解析で ZVAL(object, arrayなど) を生成するため、メモリを消費する上、メモリ保存処理分のオーバーヘッドも発生します。

対して PHP 8.3 で追加された json_validate() 関数はこれらの処理を行わないため、リソース節約・速度向上に貢献します。

また、json_validate() 関数で使用する Parser は json_decode() 関数と共通のため、json_validate() が通れば json_decode() も成功する。ということが担保されます。

json_validate( string $json, int $Depth = 512, int $flags = 0 ) : bool
<?php

$json = '';
json_validate($json);
// => false

$json = '{"a":"aaa","b":"bbb"}';
json_validate($json);
// => true

まとめ

PHP 8.3では、クラス定数の型指定やオーバーライド指定、そして json_validate() 関数など、うれしいアップデートがありました。

今回取り上げたものは一部です。新機能は他にもあるのでチェックしてみてください。

https://wiki.php.net/rfc#php_83


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers

国作りワークショップで見つけた、日常の小さな変化をシェアして安定的な成長環境を構築する心得

この記事は個人ブログと同じ内容です

国作りワークショップで見つけた、日常の小さな変化をシェアして安定的な成長環境を構築する心得

※この記事のタイトルは XP 祭りのセッション「あなたも元高角三(げんこうかくぞう)になろう!- 文章力で世界を変革する技術」のワークショップで学んだコピーライティングを実践した結果できたタイトルです。少々大袈裟かもですが、優しい目でお気楽に読み流していただけると幸いです。

こんにちは、back checkの開発をしておりますぐっきーです。 今回はXP祭りのオフラインの国作りワークショップに参加してきたのでイベントレポートとして本記事を書きます。

confengine.com

内省と周りの人との関係構築を促すワークショップ

まず @ryo_endo さんが考案された「 国作りワークショップ」の概要を説明します。

・国づくり ・国際交流

上記の2つのメタファーを通して自分自身の価値観や自分とは異なる価値観の理解を深めていくことを目的としたワークショップと紹介されていました。

進行

参加人数:約10名

参加者はテーマとしてあげられたいくつかのお題(今回は「問題解決」「関係性」「自己成長」)に対して自分の価値観として最も大切にするものを選択します。 価値観を選択した参加者は、同じ価値観を選択した他の参加者とグループになって国作りをしていきます。

国作りの手順は以下となります。

  1. なぜ自分がその価値観を選択したのかグループメンバーと共有する。
  2. グループ内のメンバーで協力し、国の国旗とその国特有の挨拶を作成する。

その後、他の国(別の価値観を選択したグループ)と国際交流を行います。

国際交流の流れ

  1. 国に観光にきた人へのおもてなしの準備を行います。ここでは各グループの中で自国に残り、他国からの観光客から自分達の国について投げられた質問に回答する人を選出します。
  2. 自国に残らないメンバーは他の国に観光に行きます。ここでは自国に残らないメンバーが他国のテーブルへいきます。
  3. 他国へ観光にいったメンバーは、国旗とその国の挨拶をはじめその国に関するちょっと意地悪な質問を投げます。質問に対しておもてなしをするメンバーは自国への質問に対して観光大使として回答します。
  4. 他国へ観光にいくメンバーはタイムボックスの間自国以外を行き来し、それぞれの国を知るための質問を繰り返します。
  5. タイムボックス終了後、自国に残ったメンバーと他国へ観光にいったメンバーを入れ替え、全ロールを経験するまで繰り返します。

日常の服を脱ぐ

印象的だったポイントとして、最も大切にする価値観を選択する流れで、最初に価値観を選んだ後ファシリテーターから「仕事のことを考えて価値観を選んでいませんか?」という問いかけがありました。その上で服を脱ぐ所作をするワークを行い「ユニークな服を着ていた人はいませんか?」など服を脱ぐワークに集中するような場作りがありました。

このワークを行った後に再度「服を脱いだ後、あなたは改めてどの価値観を大切に考えますか?」という問いかけを受けて参加者は再度価値観を選択しました。

この全員で体を動かして服を脱ぐワークを挟んだことによって、参加者間で自己主張に対する心理的安全性が高まり、その後の自分が選択した価値観をワークに参加した周囲のメンバーに共有することに対して抵抗がなくなったように感じました。 また、自分が最初に選択した価値観から1度考えをそらして再度選択肢に上がった価値観について見つめ直すことにより、ふりかえり手法の「5つのなぜ」のように自分の価値観に対して一段深く考えられたように思います。

ちなみに私は参加者として、最初の選択では「関係性」を選びました。これは自分自身が素敵だと思う人が集まるコミュニティとふれあうことが自分の潜在的な欲求だと考えたからです。しかし、服を脱いだ後に再度選択した価値観は「自己成長」でした。この大切に思う価値観の変更に至った理由は、自分が時間を共有したいと考えるコミュニティと出会うまでには必ず私の中で未知の領域の探索によって新しいコミュニティと出会ってきたからです。この未知の探索が「自己成長」のプロセスではないかと考えたため、最初の選択とは異なる価値観を選択しました。

自分達による価値観の可視化

次に国作りのワークで行った国旗づくりと挨拶づくりで感じたことを紹介します。

国旗づくりと挨拶づくりでは、事前にグループ内でなぜ自分がその価値観を選択したのかを共有し、その後グループメンバーの中で「自己成長」のイメージに対するキーワードを探しました。 私たちのグループでは、自己成長のイメージとして「頑張る」「周囲の人へのポジティブな影響」「手放す(周りの人を信じて託す。自分ができるようになることで周りの人達もできるようになっていく。育てて放流し、また次の世代に繋げていく。これらを自己の成長によって実現するようなイメージ)」が上げられました。これらのキーワードを元にインクリメンタルなサイクルが中心から生まれどんどん派生し増えていくようなデザインの国旗を作成しました。

また挨拶作りでは、サイクルがインクリメントしていく様子を表すジャスチャーを取り入れ、ヘリコプターのプロペラのように手を回す動作を取り入れました。 ちなみに私のニックネームを口に出した際の響きの良さがウケたことで、挨拶は「グッキー」に決定しました。 ※会場で参加者皆が「グッキー」を連呼するのはめちゃくちゃ恥ずかしかったです。その後懇親会まで引っ張られる程度には浸透しました。

国旗づくりと挨拶づくりのワークによって、なぜ自分自身が「自己成長」が重要だと感じたのか。また、その感覚の芯の部分にはどのような考えがあるのかが整理できたように思います。

周りの人からの問いによる価値観の深掘り

国際交流のワークでは、他国のテーブルに観光に行き、その国の国旗と挨拶をはじめ、その国に関するちょっと意地悪な質問を投げました。 ちょっと意地悪な質問とは、無茶振りのように「あなたの国ではどんな遊びが流行っていますか?」など国旗と挨拶に関する単純な質問よりもイメージを膨らませないと回答できないような質問を指します。

価値観をぶらさずに場面毎に適用するならなにが必要か?を即興で考えるのが難しかったですが、価値観を促進するアクションの具体がイメージできました。

以下に印象的だった質問とその回答を紹介します。

  1. 「自己成長国ではどんな遊びが流行っていますか?」

  2. いつもとちょっとだけなにかを変えてみる。例えばいつもの自分と違う服の着方をしてみる。新しい価値の発見を皆が楽しんでいる文化がある。

  3. 「自己成長国のおすすめのお店を教えてください」

  4. シェア屋さん。お客さんの興味をそそるような様々な領域の変化をパッケージ化して提供してくれる。(それって情報商材じゃないですか?というツッコミもあったり)

  5. 「自己成長国ではなにをしたら逮捕されますか?」

  6. 個人の成長、変化を強制しない。押しつけない。過去自己成長国が独裁政権だった時代は横行していたが、今の国では禁止し厳罰の対象としている。現在は自己成長国では変化は自由である。他人と変化量を比較することは犯罪ではないが、マナー違反でありこれをする人は紳士的でないと考えられている。

※自己成長国: 自己成長の価値観を選んだグループの国名

小さな変化をシェアして安定的な成長を実現する

ここまで一連の流れを終えた後、自分の価値観に対する発見をまとめ共有する時間がありました。

このワークショップ全体を通しての発見を私は下記のようにまとめました。

心得

まずは個人の範囲内で自己成長を楽しもう。望まない人には押し付けない。

成功も失敗も「変化」があることで得られた学びとしてポジティブに捉えよう。

変化を積極的にシェアしよう。様々な領域の変化掛け合わせで誰か(自分も含め)の役に立つパッケージを作ろう。

変化に「気づく」ようになろう。変化に気づいたら、相手に変化したねと伝え賞賛しよう。

これらを総じて振り返った中で自分達の考えていた「成長」とはなにか?という問いに対して、自分達の考えていた成長とは「変化」であるという結論に至りました。 行動によって生じるちょっとした変化。be agile を志すものとして、日常の小さな変化を捉え、言語化し、シェアすることで安定的な成長(変化)の環境を実現できるのではないでしょうか。

公開ほやほや Laravel Eloquent 用の日時フィルタリングパッケージ Laravel Date Filtering をお試し

この記事は個人ブログと同じ内容です

www.ritolab.com


Laravel Date Filtering という、Eloquent でのデータ取得における日時フィルタリングを提供するパッケージを試してみます。

Laravel Date Filtering

Laravel Date Filtering は、Laravel Eloquent モデル向けの、日付に基づく高度なフィルタリングと操作を簡素化するパッケージです。さまざまな日付と時間の間隔に基づいてレコードをフィルタリングするための便利なメソッドセットを提供します。

https://github.com/omarelnaghy/lara-date-filter

インストールと初期設定

パッケージをインストールします。

composer require omar-elnaghy/laradate-filters

設定ファイルを publish します。

php artisan vendor:publish --provider="OmarElnaghy\LaraDateFilters\ServiceProvider"

config/lara_date_filter.php が生成されます。

<?php
return [
    'custom_date_filter_convention' => [
        'get{duration}{unit}Records',
        'get{duration}{unit}Records',
        'get{duration}{unit}Records',
    ],
];

日付をフィルタリングするメソッド FilterByDateRange, FilterByDateHoursRange, FilterByDateMinutesRange などを使用可能にするため、対象のモデルの newEloquentBuilder() メソッドをオーバーライドしますします。

今回は User モデルで実装していきます。

use Illuminate\Database\Eloquent\Model;
use OmarElnaghy\LaraDateFilters\Traits\Builder\PackageBuilder;

class User extends Model
{
    .
    . (略)
    .
    
    
    /**
     * @param QueryBuilder $query
     * 
     * @return PackageBuilder
     */
    public function newEloquentBuilder($query): PackageBuilder
    {
        return new PackageBuilder($query);
    }
}

オーバーライドはしたものの、結果として Builder を extends し本パッケージ用のメソッドを Traits で付与したクラスを返しているため、ベースである Builder クラスに変更はありません。

これでこのパッケージを使い始める準備が整いました。

User データ

これからフィルタリングを行っていきますが、 今回使用しているデータは、 created_at が 2023-09-01 〜 2023-09-30 までの 30 日の範囲で 1 名ずつ User を作成した全 30 件のデータで行っています。

created_at は datetime で収録していますが、時間は全て 00:00:00 です。

フィルタリング:filterByDateRange

ここからは、実際のフィルタリングを見ていきたいと思います。

Laravel Date Filtering の基本メソッドである filterByDateRange() メソッドを使ってみます。

$startDate = Carbon::parse('2023-09-03');
$duration  = 2;
$dateUnit  = 'day';
$range     = DateRange::INCLUSIVE;
$direction = 'after';

// User をフィルタリング
$users = User::filterByDateRange($duration, $dateUnit, $startDate, $direction, $range)->get();

各項目を解説する前に、上記条件のフィルタリングによって得られた結果を先に共有しておきます。

// $users(配列に変換し必要なフィールドのみに限定しています。実際には\Illuminate\Database\Eloquent\Collection<int, \App\Models\User> が返ります。)
Array
(
    [0] => Array
        (
            [id] => 3
            [created_at] => 2023-09-03 00:00:00
        )

    [1] => Array
        (
            [id] => 4
            [created_at] => 2023-09-04 00:00:00
        )

    [2] => Array
        (
            [id] => 5
            [created_at] => 2023-09-05 00:00:00
        )

)

filterByDateRange メソッドの各引数は以下の意味とバリエーションになります。

/**
 *  @param int $duration
 *  @param string $dateUnit
 *  @param Carbon $date
 *  @param string $direction
 *  @param DateRange $range
 */
public function filterByDateRange(int $duration, string $dateUnit, Carbon $date, string $direction = 'after', DateRange $range = DateRange::INCLUSIVE)
  • $duration
    • 範囲の数値
  • $dateUnit
    • $duration の単位
      • 'second'
      • 'minute'
      • 'hour'
      • 'day'
      • 'week'
      • 'month'
      • 'year'
  • $date
    • 基点日
  • $direction
    • 基点日から過去・未来どちらの方向へ範囲を取るか
      • 'before'(過去)
      • 'after'(未来)
  • $range
    • 範囲指定における、起点、及び終点を含めるか
      • INCLUSIVE(含まれる)
      • EXCLUSIVE(含まれない)

$duration = 1, $dateUnit = 'day' なら、 1 日間ということになります。

この時、$duration = 0 なら、起点日のみという扱いになります。

また、$range が少しややこしいので説明しておくと、

$startDate = 9/3, $duration = 2, $dateUnit = 'day', $direction = 'after'

とした場合、DateRange::INCLUSIVE なら

9/3 <= targets <= 9/5

となり、9/3, 9/4, 9/5 のデータが返されます。

DateRange::EXCLUSIVE の場合は

9/3 < targets < 9/5

となり、9/4 のデータが返されます。

https://github.com/omarelnaghy/lara-date-filter/blob/6b69c9fe1695ecba77786c0c7c698269fb23d274/src/Traits/BuilderTrait.php#L21

クエリビルダとの違い

ここで、Laravel Date Filtering を用いた最初の実装を再度見てみます。

$startDate = Carbon::parse('2023-09-03');
$duration  = 2;
$dateUnit  = 'day';
$range     = DateRange::INCLUSIVE;
$direction = 'after';

// User をフィルタリング
$users = User::filterByDateRange($duration, $dateUnit, $startDate, $direction, $range)->get();

このときに、同じ実装をクエリビルダで行うとどうなるか見てみます。

日付範囲を内包する場合

Laravel Date Filtering でいうところの $rangeDateRange::INCLUSIVE の場合です。

$startDate = CarbonImmutable::parse('2023-09-03');
$duration  = 2;
$endDate   = $startDate->addDays($duration);

$users = User::whereBetween('created_at', [$startDate, $endDate])->get();

クエリビルダでも割合シンプルに書けそうです。では日付範囲を内包しない場合はどうでしょうか。

日付範囲を内包しない場合

Laravel Date Filtering でいうところの $rangeDateRange::EXCLUSIVE の場合です。

$startDate = CarbonImmutable::parse('2023-09-03');
$duration  = 2;
$endDate   = $startDate->addDays($duration);

$users = User::where(function (Builder $query) use ($startDate, $endDate) {
    $query->whereDate('created_at', '>', $startDate)
          ->whereDate('created_at', '<', $endDate);
})->get();

若干記述するコード量は増えましたが、まあ書けなくはなさそうです。

Laravel Date Filtering とクエリビルダでの日付フィルタリング

2 つを比べてみると、記述量にさほど違いはないものの、クエリビルダの場合は日付範囲の条件によって Builder の組み立て自体が変化します。

対して Laravel Date Filtering パッケージの場合は、そこを引数で吸収しているため、日付範囲の条件によって式を変えなくても良いという利点がありそうです。

その他のフィルタリングメソッド

filterByDateRange メソッドの他にも、以下のメソッドが提供されています。

これらは filterByDateRange メソッドにおける $dateUnit の省略版になっています。

Custom Eloquent Builder

Laravel Date Filtering ではカスタム Eloquent ビルダーを提供しており、メソッドの名前を "filterByDateXRange" パターンに従って命名すると、動的にメソッドを生成し、実行します。

// [Duration] は検索したい [日付単位(Date Unit)] の数を示す数値
return Post::filterByDate(Duration)(Date Unit)Range($startDate, $direction, $range)->get();

例えば、以下のようなメソッドが任意で組み立て可能です。

Post::filterByDate3DayRange($startDate, $direction, $range)->get();

Post::filterByDate3WeekRange($startDate, $direction, $range)->get();

Post::filterByDate6MonthRange($startDate, $direction, $range)->get();

created_at 以外のカラムでフィルタリングするには

(あくまでも 2023 年 9 月 22 日時点です)

Laravel Date Filtering のフィルタリングは、基本的に created_at を見るようになっています。

created_at 以外のカラムでフィルタリングする、 ないし動的にカラムを指定する方法は提供されていませんでした。

モデルに public $dateColumn = 'updated_at'; と指定すればフィルタリングのカラム変更はできるものの、あまり実用的ではないため、このあたりの開発はまだこれからのようです。

今後に期待の楽しみなパッケージ

本記事執筆が 2023 年 9 月 22 日ですが、Laravel Date Filtering は first commit が 2023 年 9 月 13 日と、まだ公開されて間もないパッケージです。

日付での複雑なフィルタリングが頻発するアプリケーションでは重宝しそうなパッケージなので、今後の機能拡充に期待しています。

https://github.com/omarelnaghy/lara-date-filter


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers