【MySQL】【Laravel】DBデータを取得する際に、連番を付与する方法
こんにちは、スズキです。
今回は、DBからデータを取得する際に連番を付与する方法を紹介します。
今回は、DBからデータを取得する際に連番を付与する方法を紹介します。
MySQLで連番を付与する方法
MySQLで連番を付与する方法です。
結論からいうと、
「FROM句で変数を初期化→SELECT句に『変数=変数+1』を記載して連番値を取得」
とすると連番を実装できます。
ソースとしては以下の様になります。
結論からいうと、
「FROM句で変数を初期化→SELECT句に『変数=変数+1』を記載して連番値を取得」
とすると連番を実装できます。
ソースとしては以下の様になります。
SELECT (@num := @num + 1) AS serial_number, target_table.* FROM target_table, (select @num := 0) dummy_table ;
Laravelで連番を付与する方法
MySQLで連番を付与するのはさほど苦労することはないと思います。
問題はLaravelでどうやって連番を実装するかということです。
Laravelの何が問題かというと、QueryBuilderを経由する関係で、基本的に「Inner Join」と「Left Join」しか使えないということです。
そのため、「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のようなソースを直接記述することができません。
「Inner Join」や「Left Join」しか使えないため、「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のようなSQLを実現するためには、「基点となるテーブルを作るタイミング(\DB::table()メソッドを呼び出すタイミング)」で直接「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のような記述を行う必要があります。
ソースとしては以下のようになります。
さて、これで連番を利用する方法がわかったので良かった良かった、と行きたいところですが、まだ完成ではありません。
何故なら、Laravelを利用する場合、N+1問題を解決する関係で以下のような形式でデータを取得するのが一般的だからです。
さて、この場合、
そのため、モデルクラス経由でデータを取得し、かつ、連番を付与するためには少し工夫が必要になります。
私が以前に上記問題に直面した際は、以下のようにサブクエリを経由するようにすることで「モデルクラスを利用しつつ、連番を付与する」という目的を実現することにしました。
convertQueryWithSerialNumberメソッドの内容は以下になります。
(連番付与の部分は共通的な処理のため、メソッド化しています。)
さて、これで「Laravelで連番を付与する」という目的が達成できました。
Laravelでもっとスマートに書ける方法とかあれば良いのですけどね・・・
問題はLaravelでどうやって連番を実装するかということです。
Laravelの何が問題かというと、QueryBuilderを経由する関係で、基本的に「Inner Join」と「Left Join」しか使えないということです。
そのため、「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のようなソースを直接記述することができません。
「Inner Join」や「Left Join」しか使えないため、「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のようなSQLを実現するためには、「基点となるテーブルを作るタイミング(\DB::table()メソッドを呼び出すタイミング)」で直接「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のような記述を行う必要があります。
ソースとしては以下のようになります。
$subQuery = \DB::table('test_tables'); $subQuery->select([ 'test_tables.*', ]); $subQuerySql = $subQuery->toSql(); $convertQuery = \DB::table(\DB::raw("({$subQuerySql}) sub_table, (select @num := 0) dummy_table")); $convertQuery->select([ 'sub_table.*', \DB::raw('(@num := @num + 1) AS serial_number'), ]); $rows = $convertQuery->get();
さて、これで連番を利用する方法がわかったので良かった良かった、と行きたいところですが、まだ完成ではありません。
何故なら、Laravelを利用する場合、N+1問題を解決する関係で以下のような形式でデータを取得するのが一般的だからです。
$query = TestTable::with([ 'test_tables2', 'test_tables3', ]); $query->select([ 'test_tables.*', ]); $rows = $query->get();
さて、この場合、
- Laravelで連番を付与するためには、基点テーブルを作成するタイミングで変数を初期化する必要がある。(基点テーブルの記述後はLeftJoin、InnerJoinしか使えないため)
- N+1問題回避のために、「TestTable::query()」のようなモデルクラス経由の記述を行ってしまうと、「FROM <<対象テーブル>>, (select @num := 0) dummy_table」のような記述を基点テーブル作成のタイミングで記述できない。
そのため、モデルクラス経由でデータを取得し、かつ、連番を付与するためには少し工夫が必要になります。
私が以前に上記問題に直面した際は、以下のようにサブクエリを経由するようにすることで「モデルクラスを利用しつつ、連番を付与する」という目的を実現することにしました。
// サブクエリのSQL作成 $subQuery = \DB::table('test_tables'); $subQuery->select([ 'test_tables.id', ])->distinct(); $subQuery->join('test_tables2', 'test_tables.id', '=', 'test_tables2.parent_id'); $subQuery->whereRaw("test_tables.price > 50000"); $subQuery->whereRaw("test_tables2.distance < 50"); // 連番付与 $subQuery = $this->convertQueryWithSerialNumber($subQuery); // データ取得 $query = TestTable::with([ 'test_tables2', 'test_tables3', ]); $query->select([ 'test_tables.*', \DB::raw('sub_test_tables.serial_number AS serial_number'), ]); $subQuerySql = $subQuery->toSql(); $query->join(\DB::raw("({$subQuerySql}) AS sub_test_tables"), 'test_tables.id', '=', 'sub_test_tables.id'); // 念のため連番の順番で並ぶようにしておく $query->orderBy('sub_test_tables.serial_number', "ASC"); $rows = $query->get();
convertQueryWithSerialNumberメソッドの内容は以下になります。
(連番付与の部分は共通的な処理のため、メソッド化しています。)
/** * クエリを連番付きクエリに変換します。 * @param $subQuery * @return mixed */ private function convertQueryWithSerialNumber($subQuery) { $subQuerySql = $subQuery->toSql(); $convertQuery = \DB::table(\DB::raw("({$subQuerySql}) sub_table, (select @num := 0) dummy_table")); $convertQuery->select([ 'sub_table.*', \DB::raw('(@num := @num + 1) AS serial_number'), ]); return $convertQuery; }
さて、これで「Laravelで連番を付与する」という目的が達成できました。
Laravelでもっとスマートに書ける方法とかあれば良いのですけどね・・・