CakePHP4 で Insert into Select を行う方法

大量データを INSERT すると、どうしても処理が遅くなります。
INSERT into SELECT でも対応できる場合、INSERT into SELECT にすることで速度を改善できます。

# この記事でわかること

- CakePHP4 での INSERT into SELECT のやり方


# バージョン情報

|  | バージョン |
|:-----------------|:------------------|
| CakePHP4         | 4.0.5 |


# 参考サイト

- https://book.cakephp.org/4/en/orm/query-builder.html#inserting-data


# 試し用テーブル

INSERT into SELECT を試すために以下の2つのテーブルを用意します。

tasks テーブルの内容を copy_tasks テーブルへコピー（INSERT into SELECT）するテーブルです。
PrimaryKey は uuid で採番します。
copy_tasks テーブルには、copied したことをマークするフラグを持たせいて、これはコピー時に常に 1 をセットします。

```php:config/Migrations/yyyymmddhhmiss_CreateTasks.php
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

class CreateTasks extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change(): void
    {
        $this->table('tasks', ['id' => false, 'primary_key' => ['id']])
            ->addColumn('id', 'uuid', ['default' => null, 'null' => false])
            ->addColumn('description', 'text', ['default' => null, 'limit' => null, 'null' => false])
            ->addColumn('created', 'datetime', ['default' => 'CURRENT_TIMESTAMP', 'limit' => null, 'null' => false])
            ->addColumn('modified', 'datetime', ['default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP', 'limit' => null, 'null' => false])
            ->create();
    }
}
```

```php:config/Migrations/yyyymmddhhmiss_CreateCopyTasks.php
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

class CreateCopyTasks extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change(): void
    {
        $this->table('copy_tasks', ['id' => false, 'primary_key' => ['id']])
            ->addColumn('id', 'uuid', ['default' => null, 'null' => false])
            ->addColumn('description', 'text', ['default' => null, 'limit' => null, 'null' => false])
            ->addColumn('copied', 'boolean', ['default' => null, 'limit' => null, 'null' => false])
            ->addColumn('created', 'datetime', ['default' => 'CURRENT_TIMESTAMP', 'limit' => null, 'null' => false])
            ->addColumn('modified', 'datetime', ['default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP', 'limit' => null, 'null' => false])
            ->create();
    }
}
```

# INSERT into SELECT

INSERT するために SELECT するカラムを `select()` します。

id は MySQL で uuid を採番します。CakePHPの `func()` メソッドには ( https://book.cakephp.org/4/en/orm/query-builder.html#using-sql-functions )、 `uuid()` は定義されていませんが、定義されていない場合、直接DBの関数を呼び出すようになっているので、問題なく動きます。

description は tasksテーブルのレコード内容を使います。

copied は 常に `1` をセットします。


```php
        $Tasks = $this->getTableLocator()->get('Tasks');
        $CopyTasks = $this->getTableLocator()->get('CopyTasks');

        $select = $Tasks->find();
        $select = $select->select([$select->func()->uuid(), 'description', 1])
            ->limit(5);

        $CopyTasks->query()
            ->insert(['id', 'description', 'copied'])
            ->values($select)
            ->execute();
```

実際に発行される SQL は以下です。

```sql
INSERT INTO copy_tasks (id, description, copied) SELECT (uuid()), Tasks.description AS Tasks__description, 1 FROM tasks Tasks LIMIT 5
```