- 投稿日:2020-10-22T18:04:25+09:00
INNER JOIN の DISTINCT が効かなくてハマった話
INNER JOIN で中間テーブルを経由してデータ取得するとき、DISTINCT をつけても重複が削除できなくてハマった話です
データ
Users ユーザー情報
id name 1 Bob 2 Mary Tags タグ
id name 1 Engineer 2 Leader Tag_Users ユーザー情報とタグの中間テーブル
user_id tag_id 1 1 2 1 1 2 BobはEngineer
MaryもEngineer
BobはLeader というデータとします最初に書いたSQL
Engineer または Leader のユーザーを取得する必要があり、以下のようなSQL文を書きました
SELECT * FROM users INNER JOIN tag_users ON users.id = tag_users.user_id WHERE (tag_users.tags_id = 1 or tag_users.tags_id = 2);表示された結果
users.id users.name tag_users.user_id tag_users.tag_id 1 Bob 1 1 1 Bob 1 2 2 Mary 2 1 Bobが2人いますね
重複を削除しようと思って書いたSQL
SELECT DISTINCT * FROM users INNER JOIN tag_users ON users.id = tag_users.user_id WHERE (tag_users.tags_id = 1 or tag_users.tags_id = 2);表示された結果
users.id users.name tag_users.user_id tag_users.tag_id 1 Bob 1 1 1 Bob 1 2 2 Mary 2 1 Bobが重複しているのでDISTINCTを追加したけどBobは減らなかった
本当に必要だったSQL
SELECT DISTINCT users.* FROM users INNER JOIN tag_users ON users.id = tag_users.user_id WHERE (tag_users.tags_id = 1 or tag_users.tags_id = 2);表示された結果
users.id users.name 1 Bob 2 Mary OK
結論
取得する範囲をきちんと指定する
- 投稿日:2020-10-22T17:28:46+09:00
【エラー】Mysql2::Error: Specified key was too long; max key length is 767 bytes
エラーの忘備録
エラー内容
rails db:migrate
をした時に、以下のエラー文が出ました。(Mysqlのエラー)Mysql2::Error: Specified key was too long; max key length is 767 bytes
指定したキーが長すぎる
というエラーです。
テーブルに色々カラムを追加すると、キーの制限である767バイトを超えてしまうため
このエラーが起こってしまいます。原因
config/database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sock
encoding: utf8mb4
という記述のためエラーが出ます。解決方法
encoding: utf8
に書き換えます。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sockutf8mb4とutf8の違いはいまいちわかっていないのですが、utf8mb4は1〜4バイト、utf8は1〜3バイトの文字が格納できるようです。そのためutf8だと、絵文字などは保存できませんが、代わりに容量が大きくなるのではないかな?と推測しました。
すでに
rails db:create
でデータベースを作成してしまっている場合は、% rails db:drop % rails db:createか
% rails db:resetで、もう一度データベースを作り直してあげる必要があります。
おわりに
もし間違えている点などございましたら、ご指摘いただけましたら幸いです。
読んでいただきありがとうございました!
- 投稿日:2020-10-22T12:35:50+09:00
docker-composeのmysqlで起動時に複数DB作るしダンプしたSQLデータも流し込むメモ
やりたいこと
docker-compose最初の起動時にDB複数作りたいです。
docker-compose.ymlにて、environmentでYSQL_DATABASEにDBを指定するとそのDBを初回起動時に作成してくれますが、複数DBやダンプしたデータの流し込みには対応していません。
MySQL(Docker Official Images)では
/docker-entrypoint-initdb.d
というディレクトリに入っているSQLやシェルスクリプトが初回起動時に実行されるようになっているので、そこに色々入れて実行してもらおうと思います。
MYSQL_DATABASEで作成したDBに対してしか実行出来ないので、作成した別のDBをUSE
します。〜やろうとしていることの流れ〜
1. DB1作る
2. DB1にダンプしたSQL流し込み
3. DB2作る
4. DB2をUSEする
5. DB2ダンプしたSQL流し込みdocker-entrypoint-initdb.dについて
このディレクトリに入っている
.sh
、.sql
、.sql.gz
いずれか拡張子を持つファイルがアルファベット順に実行されます。
それ以外の拡張子のファイルは無視されます。構成
ディレクトリ
├── docker-compose.yml └── mysql ├── Dockerfile ├── dbdata └── initdb ├── 001.sql ├── 002.sh └── 003.sqldocker-compose.yml
docker-compose.ymlservices: mysqldata: image: busybox volumes: - ./mysql/dbdata:/var/lib/mysql mysql: build: ./mysql volumes_from: - mysqldata ports: - 4306:3306 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_DATABASE=db_1 volumes: - ./mysql/initdb:/docker-entrypoint-initdb.d
- MYSQL_DATABASEで
db_1
を作成docker-entrypoint-initdb.d
を./mysql/initdb
にマウントしますdockerfile
dockerfileFROM mysql:5.7イメージ指定するだけ。
001.sql
db_1に流し込むダンプしたSQL。
002.sh
002.sh#!/bin/sh echo "CREATE DATABASE IF NOT EXISTS \`db_2\` ;" | "${mysql[@]}"
- db_2を作成
${mysql[@]}
= 引数付きのmysql
コマンドを展開しています
- docker-entrypoint.shで中身が確認出来ます
003.sql
003.sqlUSE db_2; /*以下db_2に流し込むダンプデータ*/
USE
コマンドはファイルをまたぐと無効になるので、ダンプデータの先頭に必要です。$ cat header.sql 003.sql > 003_new.sql // コマンドで合成するならおわり
他にもdb_2へのダンプするSQLファイルをtxt形式など(自動で実行されない形式)で設置しシェル上で流し込みを実行するやり方や、csvファイルで設置して流し込みするやり方があるようです。
個人的にはシェル上ででDBの作成だけをするやり方が楽が好きですが、
- DBが増えるとファイルが増えて複雑になる
- .sql.gz形式の時は先頭に
USE
を挿入出来ないので使い所は限られるかと思います。
- 投稿日:2020-10-22T11:30:06+09:00
外部キーを持つデータの削除方法
環境
この記事ではmacOS Catalina10.15.6にインストールしたRuby 2.6.5を使っています。
前提
3つのモデルを作成し、以下の状況となっています。
User / ユーザー
Report / レポート
Comment / ユーザーとレポートの関連付けテーブルコメントがついているレポートの削除を行う際に、以下のエラーが発生しました。
エラー発生
ActiveRecord::InvalidForeignKey in ReportsController#destroy
外部キーエラー
何が原因なのか、その解決策を考えます。
原因
レポートの削除を行う際に、コメントテーブルのユーザーとレポートのidの値がなくなることが原因と判断。(外部キー制約のため)
ユーザーとレポートのidの関連付けがなくなったものは、コメントも削除できるようにしたい。解決策
dependent: :destroy
をUserモデル
とReportモデル
に記述する。
この記述により、親モデル(Report)を削除する際に、その親モデルに紐づく「子モデル(Comment)」も一緒に削除できる」ようになります。検証
app/models/user.rbclass User < ApplicationRecord has_many :reports has_many :comments, dependent: :destroy endapp/models/report.rbclass Report < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy end無事解決しました!
以上です。
同じような悩みや壁にぶつかっている方のお役に立てれば幸いです!参考
下記を参考にしたので、より深く内容を理解したい方は、見てみてください!
https://qiita.com/Ushinji/items/650fa295a3054d2fe582
https://qiita.com/ITmanbow/items/2170ccaceafd5d401df8
https://qiita.com/Tsh-43879562/items/fbc968453a7063776637
- 投稿日:2020-10-22T10:40:10+09:00
DBコネクションプールをgolangで実現
初めに
GCPをつかっていてコネクションプールの話に行き着きました。
「最大コネクション数が100を超えました」というエラーが発生。
なぜコネクション数が多くなってしまうのかを調べ、見つけた対処方法となります。環境
クラウド:GCP
WEBサーバーをVue/CloudRun
APIサーバーをGolang/CloudRun
DBサーバーをMySQL/CloudSQL原因
APIを叩くたびにAPIサーバーとDBサーバー間で新しいコネクションができているからと判明した。
つまり、コネクションの使い回しができていなかった。
対策
database.sqlの以下のメソッドを使用し、コネクションプールの設定を行う。
また、ORMでもコネクションプールできるらしい。
メソッド名 説明 func (db *DB) SetMaxOpenConns(n int) 接続の最大数を設定。 nに0以下の値を設定で、接続数無制限。 func (db *DB) SetMaxIdleConns(n int) コネクションプールの最大接続数を設定。 func (db *DB) SetConnMaxLifetime(d time.Duration) 接続の再利用が可能な時間を設定。dに0以下の値を設定で、ずっと再利用可能。 以下のようにして実装。
DBconnectionfunc NewLocalDBConnection() error { /* ===== connect datebase ===== */ // user user := os.Getenv("MYSQL_USER") // password password := os.Getenv("MYSQL_PASSWORD") // connection database database := os.Getenv("MYSQL_DATABASE") // connection host host := "localhost" // connection port port := "3306" var err error DB, err = setupDB("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, password, host, port, database)) if err != nil { return fmt.Errorf("sql.Open: %v", err) } return err } //this function is a function for connection pooling func setupDB(Driver string, dsn string) (*sql.DB, error) { db, err := sql.Open(Driver, dsn) if err != nil { return nil, err } //コネクションプールの最大接続数を設定。 db.SetMaxIdleConns(100) //接続の最大数を設定。 nに0以下の値を設定で、接続数は無制限。 db.SetMaxOpenConns(100) //接続の再利用が可能な時間を設定。dに0以下の値を設定で、ずっと再利用可能。 db.SetConnMaxLifetime(100 * time.Second) return db, err }