- 投稿日:2021-06-21T23:25:16+09:00
Laravelで違う文字(濁点/半濁点/平仮名/片仮名)が同じ文字として使われてしまう問題の対処法
はじめに Laravelで日本語の比較処理をしようとしたところ、違う文字列であるにも関わらず同じものとして扱われてしまうことがありました。 その時の対処法を忘れないように記事にしたいと思います。 動作確認環境 PHP 8.0 Laravel 8.0 MySQL 8.0 ※下位環境でも動作する場合がございます テーブル構造 問題点 $nana = Sample::where('name', 'バッグ')->get(); この処理。一見だと違う文字列であるため、一致する値は取得できないように思えますが、実はID:1の情報が返ってきてしまいます。 解決法1 $nana = Sample::where('name', 'LIKE BINARY', 'バッグ')->get(); あいまい一致を回避するためにはLaravelでは'LIKE BINARY'を付けることで完全一致に切り替えることができます。 解決法2 ただ、解決法1ではSelect文の時は回避できるのですが、該当カラムにユニークキーが貼られていた場合、Insertは依然として通りません。 該当カラムにユニークキーを使っている場合は、該当カラムの参照順序(文字コード)を変えてしまいましょう。 MySQLのバージョンにもよりますが8.0.2以降であれば、utf8mb4_ja_0900_as_cs_ksを使っておけば無難です。 ※MySQL8系が使えない場合はutf8mb4_binが無難です(適時読み替えて下さい) /** * Run the migrations. * * @return void */ public function up() { // マイグレーションの書き方 Schema::create('samples', function (Blueprint $table) { $table->id(); $table->string('name')->unique()->collation('utf8mb4_ja_0900_as_cs_ks'); $table->timestamps(); }); } 解決法3 文字コードで混乱しなくないという場合は、Laravelのconfig/database.phpの設定を書き換えてしまうのが簡単です。 MySQL 8.0.2以降を使用する場合は下記の設定しておけば問題ないかと思います。 マイグレーションした際の初期値がutf8mb4_ja_0900_as_cs_ksになります(個別設定は不要)。 ※なお、MySQL8系が使えない場合はutf8mb4_binが無難です(適時読み替えて下さい) 'mysql' => [ ... 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_ja_0900_as_cs_ks', ... ], 参考記事
- 投稿日:2021-06-21T11:26:53+09:00
mysql基本かなり使うコマンド
以下基本コマンドを記載 パス初期化 sudo mysql_secure_installation データベースに接続 sudo mysql -u root -p 上記コマンド入力後にパスワードを入力 ユーザー作成 CREATE USER ユーザー名; データベース作成 create DATABASE データベース名; データベース一覧表示 SHOW DATABASES; データベース削除 DROP DATABASE データベース名; 使用するデータべ―スの選択 USE データベース名; 使用するテーブルの選択 USE テーブル名; 以下、テーブル操作 CREATE TABLE [テーブル名] ( column [データ型] [その他オプション], column2 [データ型] [その他オプション], column3 [データ型] [その他オプション], ); 例以下参考 テーブル一覧表示 SHOW TABLES; データの挿入 INSERT INTO `xxx` テーブルからデータを取得(SELECT) SELECT * FROM `xxxx`; tasksテーブルから、idとnameだけを取得する。 SELECT `id`, `name` FROM `tasks`; tasksテーブルから、idとnameだけを取得する。 SELECT * FROM `tasks` WHERE id = 2; 権限付与 GRANT SELECT ON *.* TO 'user名'@'localhost';
- 投稿日:2021-06-21T10:51:42+09:00
sqlboiler ~参照編まとめ~
sqlboilerの参照系のクエリ構築の記事になります。 ポイントとしてはクエリに利用するテーブル名やカラム名などはタイプセーフに扱いましょう。 タイプセーフにクエリを構築することで、テーブル構造が変わった際の変更忘れもコンパイルエラーを起こして、安全に開発することができます。 クエリ構築の概念 sqlboilerでのクエリ構築は主に、Query Mod Systemにより制御されます。 Query Mod SystemはStarterとFinisherによるクエリ構築を提供しています。 Starter sqlboilerで生成されたmodelの複数名になります。(UsersやStaffsなど) Finisher StarterとWhereなどの取得条件の後にくる関数で、Finisherをセットすることで最終的なクエリ結果をsqlboilerのmodelで返します。 下記はusersテーブルから全てのデータをUsersmodelとして取得している例になります。 import ( "context" qm "github.com/volatiletech/sqlboiler/v4/queries/qm" "github.com/sqlboiler_sample/models" // sqlboilerのgenerateコマンドで作成されたmodel群 ) var err error // contextのセット(以下省略) ctx := context.Context() // dbコネクションのセット(以下省略) if dbConn, err = sql.Open("devdb", fmt.Sprintf("connection_%d", time.Now().UnixNano())); err != nil { panic(err) } // Strat().Finisher();の形でクエリ結果をmodelの形で取得する var users []*models.Users if users, err = models.Users().All(ctx, db); err != nil { fmt.Println(err.Error()) } 通常のクエリ 該当レコードを1つ取得 var err error var user *models.User if user, err = models.Users().One(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコードを全て取得 var err error var users models.UserSlice if users, err = models.Users().All(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコード数を取得 var err error var usersCount int64 if usersCount, err = models.Users().Count(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコードの存在有無を取得 var err error var userExists bool if userExists, err = models.Users().Exists(ctx, db); err != nil { fmt.Println(err.Error()) } 複雑なクエリ 上記のような単純なクエリだけでなく、複雑なクエリが必要になることもあります。 通常のSQLと同じように、whereやgroup_by, limit, offsetなどSQLで使える基本的なものは sqlboilerでも使えます。 var err error var users models.UserSlice if users, err = models.Users( qm.Select("id", "name"), qm.InnerJoin("credit_cards c on c.user_id = users.id"), qm.Where("age > ?", 30), qm.AndIn("c.kind in ?", "visa", "mastercard"), qm.Or("email like ?", `%aol.com%`), qm.GroupBy("id", "name"), qm.Having("count(c.id) > ?", 2), qm.Limit(5), qm.Offset(6), ).All(ctx, db); err != nil { fmt.Println(err.Error()) } sqlboilerはタイプセーフに利用しよう! クエリに限らずですが、sqlboiler model で生成された識別子を利用することで安全な実装をすることができます。 【タイプセーフに使う】 上記処理をタイプセーフに書き換えるとこうなります。 var err error var users models.UserSlice if users, err = models.Users( ID qm.Select(UserColumns.ID, UserColumns.Name), // 「テーブル名Columns」のように、各テーブルのカラムがstructとして生成されます InnerJoin( fmt.Sprintf("%s on %s.%s = %s.%s", TableNames.CreditCards, TableNames.CreditCards CreditCardColumns.ID, TableNames.Users, UserColumns.ID), ), qm.Where(fmt.Sprintf("%s > ?", UserColumns.Age), 30), qm.AndIn(fmt.Sprintf("%s.%s in ?", CreditCardColumns.Kind), "visa", "mastercard"), qm.Or(fmt.Sprintf("%s like ?", UserColumns.Email), `%aol.com%`), qm.GroupBy(UserColumns.ID, UserColumns.Name), qm.Having(fmt.Sprintf("count(%s) > ?", CreditCardColumns.ID), 2), qm.Limit(5), qm.Offset(6), ).All(ctx, db); err != nil { fmt.Println(err.Error()) } この記事に書いてあることができたら・・・ 「Issue」のタスクをこなすことができます❗️ IssueではIssue単位で企業のお仕事ができるサービスです Ex. Firebaseでメールアドレス確認メール文章変更の実装 単価3,000円で受けて10時間ほどで実装した場合 3,000円 × 10時間 = 30,000円 登録は こちら から! サービスページは こちら! 企業様登録はこちら
- 投稿日:2021-06-21T10:49:21+09:00
sqlboilerの導入
環境 Go 1.16~ sqlboiler v.4.4.0 1. sqlboilerのインストール # slqboilerのインストール go install github.com/volatiletech/sqlboiler/v4@latest # driverのインストール(今回はMySQL) go install github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-mysql@latest 古いバージョンをインストールして期待通りの動作にならないことを防ぐため、/vサフィックス(/v4の部分)を忘れないようにしましょう! 2. 構成ファイル(sqlboiler.toml)の作成 データベースドライバーや構成オプションを設定するために、slqboiler.tomlファイルを作成します。 MySQLを利用する場合下記のような構成になります。 sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" tips 【オプションの追加】 オプションとしてwhitelist、blacklistが存在します。 whitelist→追加したテーブルのsqlboiler modelが作成されます blacklist→追加したテーブルのsqlboiler modelは作成されなくなります whitelist sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" blacklist=["users"] # usersテーブルのsqlboiler modelのみが作成される blacklist すでにデータベースには存在しているが、sqlboiler modelとして生成したくない場合はテーブル名を指定します。 sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" blacklist=["staffs"] # staffsテーブルのsqlboiler modelは作成されない 3. sqlboilerモデル群の作成 sqlboiler ドライバー名でsqlboiler modelの生成を行います。 $ sqlboiler mysql コマンド実行後、実行フォルダ配下にsqlboiler model群が作成されます。 . ├── models │ ├── users.go # DBに存在するテーブル │ ├── users_test.go # users modelのテストファイル │ ├── staffs.go │ ├── staffs_test.go │ ├── boil_main_test.go │ ├── boil_queries.go │ ├── boil_queries_test.go │ ├── boil_suites_test.go │ ├── boil_table_names.go # model出力されたテーブル名が定義されている │ ├── boil_types.go # enum値が定数として保存 advanced! 【オプションをつけてmodel生成をカスタマイズ】 model生成コマンドにもオプションをつけることができます。 Name 役割 Defaults pkgname 作成ファイル名の指定 "models" output 出力フォルダ名 "models" tag Struct tag出力オプション [] debug デバッグモードの出力制限 false add-global-variants グローバル変数の出力制限 false add-panic-variants panic 変数の出力制限 false no-context context.Context利用無効 false no-hooks hooks機能の無効 false no-tests テストファイル無効 false no-auto-timestamps タイムスタンプ無効 false no-rows-affected APIで影響を受ける行の無効 false no-driver-templates DBドライバーのテンプレート解析無効 false tag-ignore タグの無視カラムリスト [] 下記のような実行もできます。 sqlboiler mysql --pkgname sqlboilermodels --output "./package/sqlboilermodels" --wipe --config ./sqlboiler.toml ・pkgname 出力ファイル名の指定 ・output 出力フォルダの指定 ・wipe 健全性確保のため、出力modelファイルを先に削除→出力する ・config sqlboiler.tomlで構成した内容で設定を上書き 出力結果 . ├── package │ └── sqlboilermodels(--output, --pkgname) │ ├── users.go │ ├── users_test.go │ ├── staffs.go │ ├── staffs_test.go │ ├── boil_main_test.go │ ├── boil_queries.go │ ├── boil_queries_test.go │ ├── boil_suites_test.go │ ├── boil_table_names.go │ ├── boil_types.go 参考 この記事に書いてあることができたら・・・ 「Issue」のタスクをこなすことができます❗️ IssueではIssue単位で企業のお仕事ができるサービスです Ex. Firebaseでメールアドレス確認メール文章変更の実装 単価3,000円で受けて10時間ほどで実装した場合 3,000円 × 10時間 = 30,000円 登録は こちら から! サービスページは こちら! 企業様登録はこちら
- 投稿日:2021-06-21T00:13:13+09:00
inner joinで条件を設定しない時
inner join で条件を指定しないとどうなるか inner join句は結合するテーブル同士でどのキーで結合するかを指定する。 指定しなくてもクエリは実行される(実行エラーにならない)が、挙動が変わる。 テスト用テーブル customers (顧客テーブル) id customer_name 1 hirata 2 kanazawa 3 fukuda products (商品テーブル 名称と金額のみ) id product_name price 1 pencil 100 2 eraser 120 3 note 150 order_history (注文履歴、注文数などは省略) id customer_id product_id date 1 1 1 2021-06-07 2 2 1 2021-06-07 3 2 2 2021-06-07 4 2 3 2021-06-08 5 3 1 2021-06-05 6 3 3 2021-06-08 基本的な(left) inner join句の書き方 -- 注文履歴と商品テーブルと顧客テーブルを結合 select * from order_history o inner join products p on o.product_id = p.id result o.id o.customer_id o.product_id o.order_date p.id p.product_name p.price c.id c.customer_name 1 1 1 2021-06-07 1 pencil 100 1 hirata 2 2 1 2021-06-07 1 pencil 100 2 kanazawa 3 2 2 2021-06-07 2 eraser 120 2 kanazawa 4 2 3 2021-06-08 3 note 150 2 kanazawa 5 3 1 2021-06-05 1 pencil 100 3 fukuda 6 3 3 2021-06-08 3 note 150 3 fukuda 注文履歴をベースに、 結合条件を指定しない場合 select * from order_history o inner join products p inner join customers c; result o.id o.customer_id o.product_id o.order_date p.id p.product_name p.price c.id c.customer_name 1 1 1 2021-06-07 1 pencil 100 1 hirata 1 1 1 2021-06-07 2 eraser 120 1 hirata 1 1 1 2021-06-07 3 note 150 1 hirata 1 1 1 2021-06-07 1 pencil 100 2 kanazawa 1 1 1 2021-06-07 2 eraser 120 2 kanazawa 1 1 1 2021-06-07 3 note 150 2 kanazawa 1 1 1 2021-06-07 1 pencil 100 3 fukuda 1 1 1 2021-06-07 2 eraser 120 3 fukuda 1 1 1 2021-06-07 3 note 150 3 fukuda 2 2 1 2021-06-07 1 pencil 100 1 hirata 2 2 1 2021-06-07 2 eraser 120 1 hirata 2 2 1 2021-06-07 3 note 150 1 hirata 2 2 1 2021-06-07 1 pencil 100 2 kanazawa 2 2 1 2021-06-07 2 eraser 120 2 kanazawa ....... と、結果が大量になるので途中までだが、結合条件を指定しないと全行で結合してしまう。 これはcross join指定をしているのと同じ。 select * from order_history o cross join products p cross join customers c この挙動が便利な時(cross join) 全組み合わせが確認できるので、それなりに便利。 私は、日付ごとに集計結果を出力したい時で、「集計結果にならない日付も出力したい場合」に全ての日付でテーブルを作成する時に使用したりします。 例のorder_historyでは、6月5日、7日、8日のデータがあるため、これで商品の日付ごとを集計すると6日のデータが出力されない。 select product_id ,order_date, sum(price) from order_history o inner join products p on p.id = o.product_id group by product_id ,order_date; product_id order_date sum(price) 1 2021-06-05 100 1 2021-06-07 200 2 2021-06-07 120 3 2021-06-08 300 cross joinで商品✕日付の全組み合わせを出力しておいて、それに結果を紐付けると結果の無い日付も出力対象になる。 select p.id, p.product_name, d, order_date, sum_price from products p cross join (select date_format(date_add('2021-06-04', interval td.generate_series day), '%Y-%m-%d') as d from ( SELECT 0 generate_series FROM DUAL WHERE (@num:=1-1)*0 UNION ALL SELECT @num:=@num+1 FROM `information_schema`.COLUMNS LIMIT 7 ) as td ) date_table left join (select product_id ,order_date, sum(price) as sum_price from order_history o inner join products p on p.id = o.product_id group by product_id ,order_date) ledger on ledger.product_id = p.id and ledger.order_date = date_table.d order by p.id, d ; id product_name d order_date sum_price 1 pencil 2021-06-05 2021-06-05 100 1 pencil 2021-06-06 1 pencil 2021-06-07 2021-06-07 200 1 pencil 2021-06-08 1 pencil 2021-06-09 1 pencil 2021-06-10 1 pencil 2021-06-11 2 eraser 2021-06-05 2 eraser 2021-06-06 2 eraser 2021-06-07 2021-06-07 120 2 eraser 2021-06-08 2 eraser 2021-06-09 2 eraser 2021-06-10 2 eraser 2021-06-11 3 note 2021-06-05 3 note 2021-06-06 3 note 2021-06-07 3 note 2021-06-08 2021-06-08 300 3 note 2021-06-09 3 note 2021-06-10 3 note 2021-06-11 MySQL8.0はWindow関数も使えたり集計関数も増えているので、5.7までで無理やりやるとこんな感じというところはありますが。 ともあれ、たまにSQL書こうとすると間違ったりするので注意。