複雑なクエリの場合は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したい内容を記載します。
- プレイ回数(DISTINCTで同じ日にプレイした場合は重複削除する、countを利用するにはgroup_byが必須)
- ユーザー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 の操作を管理することが出来る様になります。
- ターミナルを開く
curl https://sdk.cloud.google.com | bash
-> curlでインストールする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 に保てます)
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が作成される
参考
大文字小文字を区別しないでActiveRecord(where)で取得する方法
大文字小文字を区別しないでActiveRecord(where)で取得する方法
■ モデル構造
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
参考
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: リソースの部分置換