【Laravel】whereIn メソッドで生成されるIN句の中にサブクエリを書く
問題
以下のように複数のIDを指定して一括で更新したい場面がありました。
$divIds = Div::where('hoge_flg', true)->select('id')->get(); User::whereIn('div_id', $ids)->update(['hoge_flg' => true]);
ただ、上記のように書くと2回クエリが発行されるのであまり良くないなと思いました。
- Div モデルの ID を取得する
- User モデルのフラグを更新する
そこで以下のような SQL が生成されるような Eloquent がないか調べてみました。
update user_table set hoge_flg = true where id in ( select id from div_table where hoge_flg = true );
結論
whereIn メソッドの第2引数にクエリオブジェクトを渡せます…!
User::whereIn('div_id', Div::where('hoge_flg', true)->select('id'))->update(['hoge_flg' => true]);
ドキュメントにも思っきり書いてますね…
ドキュメント… やはりドキュメントはすべてを解決する…!
whereInRaw メソッドとか無い?とか謎の発想で検索してしまったので備忘録として書きました。
Laravel Sail 環境で Sail をインストールする前だとイメージのビルドができない
Sail インストール前だと Docker イメージのビルドがこける
これは開発環境の初期構築時のみに発生する問題です。
Laravel Sail 環境で Sail 本体がインストール前、つまり composer install
実行前だと以下のようなエラーが出て Docker イメージのビルドが失敗してしまいました。
=> ERROR [ 7/14] RUN groupadd --force -g sail 0.3s ------ > [ 7/14] RUN groupadd --force -g sail: #0 0.301 groupadd: invalid group ID 'sail' ------ failed to solve: executor failed running [/bin/sh -c groupadd --force -g $WWWGROUP sail]: exit code: 3
じゃあ先に composer install
すれば良いのでは?
となるのですが、この場合ローカルで実行することになります。(コンテナが立ち上がってないので当たり前ですね)
すると、該当するバージョンの PHP をローカルにインストールする必要が発生します。
そもそも、そんな風にバージョンを気にしなくて良くするのがコンテナなのでは…?と思ったりしたので調べてみると割とすぐに対応できました。
対処:環境変数をセットしよう
エラーを見てみると$WWWGROUP
という環境変数がセットされていないため groupadd コマンドが失敗してしまっているようです。
じゃあセットしましょう。ということで以下のコマンドで事前に実行しておけば問題なく通りました。
export WWWGROUP=${WWWGROUP:-$(id -g)}
あとはいつも通りコンテナを立ち上げてから中に入りcomposer install
を実行することで Sail をインストールすることができました!
コマンドの出どころ
vendor/laravel/sail/bin/sail
内に同じコマンドがあります。
sail コマンド実行時にセットしているのと同じ値を環境変数にセットしています。
yum で MySQL を最新ではない任意のマイナーバージョンへアップデートする
目的
アップデート作業時の最新は 5.7.41 だったのですが、これを 5.7.40 にしたいです。
$ mysql --version mysql Ver 14.14 Distrib 5.7.36, for Linux (x86_64) using EditLine wrapper
アップデートできる MySQL のバージョン確認
まず yum でアップデートできる最新バージョンの確認をしました。
$ yum search all --showduplicates mysql-community-server 読み込んだプラグイン:fastestmirror, presto, priorities Loading mirror speeds from cached hostfile * epel: d2lzkl7pfhq30w.cloudfront.net 649 packages excluded due to repository priority protections ======================================================= 一致: mysql-community-server ======================================================= mysql-community-server-5.7.9-1.el6.x86_64 : A very fast and reliable SQL database server mysql-community-server-5.7.10-1.el6.x86_64 : A very fast and reliable SQL database server <中略> mysql-community-server-5.7.39-1.el6.x86_64 : A very fast and reliable SQL database server mysql-community-server-5.7.40-1.el6.x86_64 : A very fast and reliable SQL database server mysql-community-server-5.7.41-1.el6.x86_64 : A very fast and reliable SQL database server
末尾に以下のようにあるので 5.7.41 が最新のようです。
数日前に更新されたばかりでした。ぐぬぬ…
mysql-community-server-5.7.41-1.el6.x86_64 : A very fast and reliable SQL database server
おそらくこのままバージョン指定をしないでアップデートするとこのバージョンがインストールされそうです。
そこで、ほしいバージョンの rpm をダウンロードして、それを使ってアップデートします。
ほしいバージョンをダウンロード
MySQL :: Download MySQL Community Server (Archived Versions)
上記のリンク先で以下を指定すると、対応したパッケージのダウンロードリンクが表示されます。
- 目当ての MySQL バージョン
- OS 種類
- OS のバージョン
ちなみに OS のバージョンやビット数は以下のコマンドで確認しました。
$ cat /etc/redhat-release CentOS release 6.10 (Final) $ arch x86_64
以下の「RPM Bundle」の「Download」からダウンロードします。
ローカルに落としてからサーバへアップしてもよかったのですが直接ダウンロードしたほうが早そうだったので curl コマンドでダウンロードしました。
curl -OL <ダウンロードのリンク>
※ wget コマンドでもいけますが入ってなかった
インストール
現在インストールされているバージョンの確認です。
あれ、なんかバージョン揃ってない…?
$ sudo yum list installed | grep "^mysql" mysql-community-client.x86_64 5.7.36-1.el6 @mysql57-community mysql-community-common.x86_64 5.7.36-1.el6 @mysql57-community mysql-community-libs.x86_64 5.7.36-1.el6 @mysql57-community mysql-community-libs-compat.x86_64 5.6.43-2.el6 @mysql56-community mysql-community-server.x86_64 5.7.36-1.el6 @mysql57-community
さきほど落としてきたファイルを指定してインストールします。
$ sudo yum localinstall mysql-community-server-5.7.40-1.el6.x86_64.rpm \ % mysql-community-client-5.7.40-1.el6.x86_64.rpm \ % mysql-community-common-5.7.40-1.el6.x86_64.rpm \ % mysql-community-devel-5.7.40-1.el6.x86_64.rpm \ % mysql-community-libs-5.7.40-1.el6.x86_64.rpm \ % mysql-community-libs-compat-5.7.40-1.el6.x86_64.rpm
インストール後のバージョン確認
$ sudo yum list installed | grep "^mysql" mysql-community-client.x86_64 5.7.40-1.el6 @/mysql-community-client-5.7.40-1.el6.x86_64 mysql-community-common.x86_64 5.7.40-1.el6 @/mysql-community-common-5.7.40-1.el6.x86_64 mysql-community-devel.x86_64 5.7.40-1.el6 @/mysql-community-devel-5.7.40-1.el6.x86_64 mysql-community-libs.x86_64 5.7.40-1.el6 @/mysql-community-libs-5.7.40-1.el6.x86_64 mysql-community-libs-compat.x86_64 5.7.40-1.el6 @/mysql-community-libs-compat-5.7.40-1.el6.x86_64 mysql-community-server.x86_64 5.7.40-1.el6 @/mysql-community-server-5.7.40-1.el6.x86_64
念の為 MySQL の再起動と upgrade コマンドを流しておきます。
$ sudo service mysqld restart $ sudo mysql_upgrade
アプリケーション側で動作確認して無事終了!
【MySQL】数字を含む文字列の数字だけでソートする SQL
MySQL で数字を含む文字列の数字部分だけでソートする SQL を書いたのでメモです。
やりたいこと
log_1, log_2 のような文字列 + 数字の値を持つカラムで数字順にソートしたい場面がありました。
末尾の数字が1桁のみであればそのままソートできたと思いますが、log_10 など2桁の値もあり変換の必要がありました。
※数値が2桁の場合は log_1, log_10, log_2 のような順番になってしまうため
数字部分のみ抜き出す
下記2点を行えばカラムが数値になりソートができます。
- REPLACE関数で数字部分のみを抜き出し
- CAST関数で抜き出された数字を整数型に変換
SELECT CAST( str_name like 'log_%' THEN REPLACE(str_name, 'log_', '') -- 条件に一致する文字列を数値のみに置換する AS SIGNED -- 整数に変換 ) AS log_no FROM log_table WHERE ... -- 形式の異なるデータを除外する条件 OREDER BY log_no;
感想
変換してソートできるようにするというのは割と使う場面が多い書き方かなと思いました。
そういえばCASE文で条件ごとにランク付けしてソートするような SQL を書いたのでうっすらでも覚えているうちに記事にしておきたいです。
GitLab CI でアクセストークンを使いリポジトリをクローンする
GitLab CI でアクセストークンを使いリポジトリをクローンするように変更したので手順のメモです。 元は SSH を使っており鍵の設定が煩雑そうでしたが、アクセストークンの発行・設定は10分くらいでできると思います。
アクセストークンを発行する
メニューの「設定 > アクセストークン」からトークンを発行します。
クローンするだけなら「read_repository」の権限さえあれば大丈夫そうですが別の作業も行っているので「write_repository」も有効にしています。
トークンを発行すると画面上部にトークンが表示されるのでメモしておきます。
なお、トークンを発行すると同じ画面の下部に以下のように表示されます。
発行済のトークンの管理ができるようです。
アクセストークンを CI/CD の変数に設定する
トークンを設定ファイルに直書きするわけにはいかないので環境変数を設定するようなイメージです。
メニューの「設定 > CI/CD」の「変数」にトークンの変数を追加します。
ここで設定した変数は .gitlab-ci.yml の中で変数として使えます。
今回は変数GITLAB_ACCESS_TOKEN
にトークンを設定しました。※変数名はもちろん任意です
アクセストークンを使うように変更する
以下のように書けばアクセストークンを使ってリポジトリをクローンしてくれます。
git clone https://oauth2:$GITLAB_ACCESS_TOKEN@git.hoge.jp/fuga/repo.git working-copy
$GITLAB_ACCESS_TOKEN
は前の手順で設定した変数を参照しています。
【Laravel】モデルのアクセサでリレーションを呼ぶと N+1 問題になるときの回避方法
ある日 Telescope 上で同じクエリが数十件発行されているリクエストを見つけて驚きました。
コードを追ってみるとどうやらモデルのアクセサでリレーションが呼ばれていることが原因のようでした。
そもそもそういうコードを書くな、ということなのでしょうがすでに書いてあるためどうやってクエリの大量発行が回避できるか検討してみました。
問題
モデルのアクセサでリレーションを呼び、appends
にそのアクセサを追加していると index 系のメソッドなどでモデルのインスタンスが大量に作られると N+1 問題が発生します。
モデル側のコードとしては以下のようなイメージです。
protected $appends = ['user_name']; public function getUserNameAttribute() { return $this->user->name . 'さん'; }
私の担当しているプロジェクトでは不用意に上記のようなコードを追加してしまっており、N+1 問題だけでなく、このアクセサを使用しない場面でもリレーションが呼ばれて不要なクエリが発行されてしまっていました。
※モデルのインスタンスが作成されるたびにアクセサが実行されます
回避策1:そもそも $appends に書かない
身も蓋もないのですがそのアクセサの情報が必要な箇所でのみ使用するようにしましょう(自戒) 。 $appends には記載しない場合以下のように書けばアクセサをその箇所でのみ追加できます。
$hoge->append('user_name')
show 系のメソッドで単体のモデルを取得する場合なら、リレーションのクエリも1回しか発行されてないため上記で対応できます。
回避策?という感じではあるのですが、私のプロジェクトでは使用する場面が限られるアクセサも $appends に追加されており、不要な呼び出しが発生していたので記載しました。
回避策2:アクセサが参照しているリレーション先を with で指定する
これは index 系のメソッドでアクセサを定義しているモデルを何十件と取得する場合に有効な回避策です。
冒頭のアクセサが Hoge モデルに書かれているとします。
Hoge モデルを一度に何十件も取ってくる以下のようなクエリに、 アクセサで呼ばれるリレーション先の user を with で指定しておきます。
$hoges = Hoge::query() ->with('user') ->limit(50) ->get();
以下のような流れでアクセサは実行され、N+1 問題は発生しません。
- Hoge モデルをまとめて取得するクエリが実行される
- Hoge モデルのリレーション先の User モデルをまとめて取得するクエリが実行される
- Hoge モデルのアクセサが実行されるが、この時点ではすでにリレーション先の User モデルは取得されているので User モデル取得のクエリは実行されない
回避策3:scope を使う
回避策とは少し違うとは思うのですが、少し複雑なクエリかつ複数箇所で使用する場合に scope を使う方法もあります。
良い例が出せないのですが、以下のように addSelect で行を追加することで擬似的なアクセサのような使い方ができます。
※もちろんこの程度なら使うまでもないのですが…!
public function scopeHogeUserName($query)
{
return $query
->addSelect(DB::raw("concat(user.name, 'さん') AS user_name"));
}
まとめ
アクセサを追加する場合は以下の2点を頭に置いておこうと思いました…!
- できるだけアクセサでクエリを呼ばない
- クエリを呼ぶ場合は $appends には追加しない
Docker 環境の Mailhog で詳細なログを出力する
今回 SMTP を使っての APサーバ <=> SMTPサーバ 間のメール送信処理がうまくいってないようだったのでその部分の詳細なログを取得したくて調べていました。
Mailhog で詳細なログを出力する
開発環境では Mailhog というツールを使っています。
SMTPサーバとして指定するとメールを受け取って実際には送信せず、通常のメーラのように Mailhog が用意した画面に表示してくれます。
docker-compose.yaml に以下のように logging という設定を追加するだけで出力できました。
mailhog: image: mailhog/mailhog logging: # ここを追加! driver: 'none' ports: - '8025:8025'
docker compose up
で-d
オプションをつけないで立ち上げるとログをコンソールに出し続けてくれ、そこに Mailhog のログも出力されるようになりました!
以下のようにSMTP のセッションが始まってから FROM はどのアドレスで〜という詳細が出力されます。
hoge-mailhog-1 | 2022/12/01 01:12:02 [SMTP 172.21.0.4:60546] Starting session hoge-mailhog-1 | 2022/12/01 01:12:02 [SMTP 172.21.0.4:60546] [PROTO: INVALID] Started session, switching to ESTABLISH state hoge-mailhog-1 | 2022/12/01 01:12:02 [SMTP 172.21.0.4:60546] Sent 35 bytes: '220 mailhog.example ESMTP MailHog\r\n' <以下略>
これで解決できるといいのですが…!
参考
verbose logging using docker image · Issue #56 · mailhog/MailHog · GitHub