技術ブログ

(技術系中心)基本自分用備忘録なので、あくまで参考程度でお願いします。

複雑なクエリの場合はO/Rマッパーではなく直接SQL書いた方が良いという話

複雑なクエリの場合はO/Rマッパーではなく直接SQL書いた方が良いという話

RailsのO/Rマッパーだけでは複雑な条件取得の場合に役不足のケースがあります。

その場合は直接SQLクエリを作成する方が早いケースがあります。

例)直近1週間で60分以上ゲームしたことがあるユーザーid一覧を取得せよ

この場合はどうすれば良いでしょうか?

0/Rマッパーでも取得はできそうですが、なんどもクエリ発行してDB問い合わせする実装になりそうな気がします。(複雑なので)

今回のケースは直接SQLを書いて、それをDB問い合わせすれば一度のクエリ問い合わせで済みそうです。

SQL

下記のようなSQLクエリを書けば、想定のデータを取得できると思われます。

SELECT user_id FROM
(
    SELECT COUNT(DISTINCT DATE_FORMAT(played_at, '%Y-%m-%d')) AS playing_count, user_id FROM game_rooms
    WHERE started_at BETWEEN now() - INTERVAL 7 DAY + INTERVAL 9 HOUR AND now() - INTERVAL 1 DAY + INTERVAL 9 HOUR
    AND play_time > 60
    GROUP BY user_id
) AS playing_count
WHERE playing_count >= 6;

サブクエリ

(
    SELECT COUNT(DISTINCT DATE_FORMAT(played_at, '%Y-%m-%d')) AS playing_count, user_id FROM game_rooms
    WHERE started_at BETWEEN now() - INTERVAL 7 DAY - INTERVAL 9 HOUR AND now() - INTERVAL 1 DAY - INTERVAL 9 HOUR
    AND play_time > 60
    GROUP BY user_id
) AS playing_count
SELECT COUNT(DISTINCT DATE_FORMAT(played_at, '%Y-%m-%d')) AS playing_count, user_id FROM game_rooms

game_roomsテーブルからselectしたい内容を記載します。

  1. プレイ回数(DISTINCTで同じ日にプレイした場合は重複削除する、countを利用するにはgroup_byが必須)
  2. ユーザーid(groupで利用するため)
WHERE started_at BETWEEN now() - INTERVAL 7 DAY + INTERVAL 9 HOUR AND now() - INTERVAL 1 DAY + INTERVAL 9 HOUR

直近1週間の絞り込みを行います。+ INTERVAL 9 HOURをしている理由はUTCタイムをTokyo/Asiaタイムに変換する為です。

AND play_time > 60

プレイ時間が60分以上のものに絞り込みを行います。

GROUP BY user_id

user_idでグルーピング化します。(GROUP BYで特定の列をキーにした合計値や平均値などが表示される)

AS playing_count

取得したデータに別名をつける(SELECT文を使ってデータを取得する時、カラム名に対して別名を付けることができます)

WHERE playing_count >= 6;

直近1週間で6日以上ゲームをしているものだけに絞り込む。

groupを利用した少し複雑な処理方法

groupを利用した少し複雑な処理方法

タイトルがうまく思いつかないので、よくわからないと思うのですが。

下記のようなケースを想定した場合の処理について説明します。(自分用備忘録です)

例)ユーザーがゲームをした回数を調べたい、日付毎に確認したい。

user_id = 1
 Game.where(user_id: user_id).group("date_format(DATE_ADD(played_at,INTERVAL 9 HOUR), '%Y/%m/%d')").count
=> {"2021/04/05"=>2}

上記の結果からユーザーidが1のユーザーは"2021/04/05"に2回ゲームをしたことが理解できます。

Game.where(user_id: user_id).

ユーザーidが1のレコードを絞り込みます。

Game.where(user_id: user_id).group("date_format(DATE_ADD(played_at,INTERVAL 9 HOUR), '%Y/%m/%d')")

保存されているplayed_atはMySQL標準のUTCタイムになっているのでDATE_ADD(played_at,INTERVAL 9 HOURで+9時間して日本時間に変更します。 さらにdate_formatで日時を整形します。("2021/04/05"みたいな感じに)

 Game.where(user_id: user_id).group("date_format(DATE_ADD(played_at,INTERVAL 9 HOUR), '%Y/%m/%d')").count

何日にどれくらいゲームしたかをカウントするためにcountメソッドを利用します。

まとめ

少し複雑なgropuメソッドの利用方法を説明しました。

Google Could SDKでk8sのコンテナを作成する方法

Google Could SDKインストール方法

業務でGoogle Could SDKをインストールする方法を記載します。

Google Could SDKを導入することで、gcloud コマンドライン ツールを利用することが可能になり認証、ローカルの構成、デベロッパー ワークフロー、Google Cloud APIs の操作を管理することが出来る様になります。

  1. ターミナルを開く
  2. curl https://sdk.cloud.google.com | bash -> curlでインストールする
  3. gcloud init -> 以下のコマンドを打つとブラウザが立ち上がるのでGoogleのログインを行

gcloud initの設定で初期設定するのですが。設定した内容はgcloud config listでいつでも確認することが可能です。

Cloud SDK の初期化  |  Cloud SDK のドキュメント  |  Google Cloud

k8sのコンテナを作成する

gcloud container clusters createコマンドでコンテナを作成する事が出来ます。 gcloud container clusters create  |  Cloud SDK Documentation

既にある、k8sコンテナをローカルに作成したい時はgcloud container clusters get-credentialsコマンドで作成する事が可能です。

gcloud container clusters get-credentials  |  Cloud SDK Documentation

(豆知識)kubectlでRails consoleする方法

上記の工程でk8sコンテナを作成できたので、コンテナに繋げてRails consoleする方法を記載します。

コンテナに繋げるにはkubectl execコマンドを利用します。

例)nginx-78f5d695bd-czm8zというPod(コンテナ)に繋げてrails cする

kubectl exec nginx-78f5d695bd-czm8z -- bundle exec rails c -e production

kubernetesのCronJobについて

kubernetesのCronJobについて

業務でk8sのクローンジョブを利用して、定期バッジ処理をする機会があった。

忘れない為に学んだ内容を記載する

クローンジョブを行うようなケースは下記のようなものがあります。

1. 定期的に送信メールを送る
2. 1日一回ユーザー情報を取得して、不正ユーザーがいない確認する

k8sのcronjobの仕組み

k8sを利用すれば、CronJobというリソースで定期バッジ処理を行うことが可能です。

K8s CronJob ではコントローラーがスケジュールを管理し、実行ごとに Pod を作成して、終了したらそれを破棄します。(コンテナを stateless, immutable, ephemeral に保てます)

lhiroki1205.hatenablog.com

cronJobの作成方法

例)

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *" ※1
  concurrencyPolicy: Forbid ※2
  startingDeadlineSeconds: 10 ※3
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure ※4

※1 実行時間の指定

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *

例)毎日午前0時01分に定期実行したい場合

1 0 * * *

※2 古いJobがまだ動いてる際に、新しいJobを実行するかどうかのポリシーを設定する

Allow(default):同時実行に対して制限を行わない
Forbid:前のJobが終了していない場合は次のJobは実行しない(同時実行を行わない)
Replace:前のJobをキャンセルし、新たにJobを開始する

※3 開始時刻が遅れた場合に許容できる秒数

※4 kebectl runによってpodを作成する時のrestart policy

--restart=AlwaysならDeploymentが作成される
--restart=OnFailureならJobがが作成される
--restart=Neverなら通常のpodが作成される

参考

kubernetes.io

kubernetes.io

大文字小文字を区別しないでActiveRecord(where)で取得する方法

大文字小文字を区別しないでActiveRecord(where)で取得する方法

■ モデル構造

スクリーンショット 2019-12-10 14.40.52.png

Personテーブルから名前がstevejobsのレコードを検索取得したい場合

モデルクラス.where("列名 LIKE ?", "%値%")  # 値(文字列)を含む

で取得できるので以下のように実装します。

Person.where("name like ?", "%stevejobs%")  # 値(文字列)を含む

これでnameがstevejobsのレコードが取得できます。

しかし検索する時に英語の大文字小文字を区別したいケースに遭遇しました。

例) 人物テーブルからステーブジョブスを検索したい場合

Aさん -> "SteveJobs"と検索
Bさん -> "stevejobs"と検索

現在の実装ではstevejobsと検索しているのでBさんの検索方法でしかヒットしません。

arel_tableを利用する。

色々調査した結果、Active Recordの内部で使用されるSQL生成ライブラリArelを利用すれば実現可能ということがわかりました。

Arelを使うと文字列でSQLを書くことなく、Rubyのコードとしてクエリを書くことができます。

arelではmatchesを使う事でlikeクエリが書けるので以下のように変更します。

persons = Person.arel_table
persons.where(persons[:name].matches("%name%"))

matchesメソッドではデフォルトでcase_sensitive = falseと設定されているので大文字小文字を区別しません。

def matches(other, escape = nil, case_sensitive = false)
  Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
end

これで、AさんパターンBさんパターンのどちらもヒットします。

環境

Rails 6.0.1
ruby 2.6.5p
postgres:11.2

参考

https://cbabhusal.wordpress.com/2015/06/04/ruby-on-rails-case-insensitive-matching-in-rails-where-clause/

Nuxt.js初期設定

? Project name: (hello) 
 
プロジェクトネームを設定できます。カッコの中のhelloがデフォルトの値です。
 
? Programming language: (Use arrow keys)
❯ JavaScript 
  TypeScript 
 
プログラミング言語を選択します。
このコースではJavaScriptを利用します。

 
? Package manager: 
  Yarn 
❯ Npm 
 
パッケージマネージャーを選択します。
ざっくりいうと、ソフトウェア管理ツールです。
 
? UI framework: (Use arrow keys)
❯ None 
  Ant Design Vue 
  Bootstrap Vue 
  Buefy 
  Bulma 
  Chakra UI 
  Element 
  Framevuerk 
  iView 
  Tachyons 
  Tailwind CSS 
  Vuesax 
  Vuetify.js 
 
UIフレームワークを選択します。

 
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Axios
 ◯ Progressive Web App (PWA)
 ◯ Content
 
Nuxt.jsのモジュールを追加する場合は、選択するのですが、
ここでは、なにも選択しないで、エンターキー
 
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ ESLint
 ◯ Prettier
 ◯ Lint staged files
 ◯ StyleLint
 ◯ Commitlint
 
Linting toolsを選択できます。(コードレビューツール)
 
? Testing framework: (Use arrow keys)
❯ None 
  Jest 
  AVA 
  WebdriverIO 
 
テストフレームワークの選択です。

 
? Rendering mode: (Use arrow keys)
❯ Universal (SSR / SSG) 
  Single Page App 
 
レンダリングモードを選択します。
ざっくりいうと、
Universalは、サーバー側で、jsを実行してレンダリングするモード。
Single Page Appは、クライアント側でjsを実行してレンダリングするモード。
 
 
 
? Deployment target: (Use arrow keys)
❯ Server (Node.js hosting) 
  Static (Static/JAMStack hosting)
 
Serverの方は、node.jsとして サーバーで動かす場合に選択、
Staticは、静的なファイルとして、静的ファイルを書き出して動作するので静的ホスティングサービス上にデプロイする場合に選択。

PUTとPATCHの違い

PUTとPATCHの違い

WEBのHTTPメソッドにはリソースの更新処理をおこうなう時に利用するPUTとPATCHを利用します。

ただ、この違いがよくわかないので調査しました。

PUT

丸ごと更新する場合。 User を丸ごと更新する場合など。

PATCH

一部分を更新したい場合。 User.first_name だけを変更し、User.last_name等の他のプロパティはそのままの場合など。

まとめ

PUT: リソースの作成、リソースの置換
PATCH: リソースの部分置換