0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Generated ColumnのUniqueIndexのSTOREDはNULLはダメみたい

Last updated at Posted at 2025-09-17

はじめに

今回業務上でユニークなインデックス追加をしてデータを規制したいなと思った際にGenerated Columnという便利なものが存在するので使ってみた時の備忘録

Generated ColumnとかVIRTUALとSTOREDについて

Generated Columnはその名の通り「生成値」、すなわち他のカラム同士とかで条件をつけた結果を格納したり、計算結果を入れたりなどできる。結果に対してindexも貼れちゃう便利なやつ。

そんなGenerated ColumnにはVIRTUALSTORE があります。
ざっくり説明すると、

  • VIRTUAL
    • 名前の通り実態をDBのストレージに保存することはないので、ストレージを圧迫せずに使用可能
    • ただ逆を言えば頻繁にアクセスされるテーブルだと毎回算出されるのであまりパフォーマンスがよくない
    • 何も指定しないとデフォルトでこれらしい
  • STORE
    • VIRTUALと違いDBのストレージに実際に保存される
    • 頻繁にアクセスされるならばこっちだと永続化されいているのでパフォーマンスが良い

説明はこんなところで詳細はドキュメントなどをみてください。
https://dev.mysql.com/doc/refman/8.0/ja/create-table-generated-columns.html

UniqueIndexでのSTOREDはNULLはダメみたい

本題ですが、今回Generated Columnを使ってUniqIndexの作成しました。

以下のようなindexを作成しようとしましたがダメでした。

-- これはNG

ADD COLUMN unique_idx VARCHAR(255) GENERATED ALWAYS AS (
    CASE
    WHEN user_id IS NOT NULL AND group_id IS NOT NULL
    THEN CONCAT(user_id, '_', group_id)
    ELSE NULL
    END
) STORED UNIQUE;

やろうとしていることは、user_idgroup_idをconcatで合体させたものをカラムに保存して、どちらかのカラムがない時はNULLにするという条件で生成、それに対してUniqueIndexを貼ろうという感じです。

なんでエラーになったのかを説明すると、この状態だとNULLがUniqueにならないためSTOREDの中にはどうやらNULLが入れられないらしいです。普通のカラムはそんなことないはずなので注意が必要です。
ちなみにVIRTUALだとこのパターンでもUniqueIndexを貼れるようでした。

-- これはOK

ADD COLUMN unique_idx VARCHAR(255) GENERATED ALWAYS AS (
    CASE
    WHEN user_id IS NOT NULL AND group_id IS NOT NULL
    THEN CONCAT(user_id, '_', group_id)
    ELSE NULL
    END
) VIRTUAL UNIQUE;

auto_incrementを含めてしまうのもダメみたい

次にNULLがダメなら、Uniqueにしてあるしidにしちゃえば被らないなと思い以下にしました。

-- これはNG

ADD COLUMN unique_idx VARCHAR(255) GENERATED ALWAYS AS (
    CASE
    WHEN user_id IS NOT NULL AND group_id IS NOT NULL
    THEN CONCAT(user_id, '_', group_id)
    ELSE id
    END
) STORED UNIQUE;

これもダメなようでした。どうやらGenerated Columnの生成タイミングの問題絡みでauto_incrementのカラムは採用できないらしいです。auto_incrementの値はレコードが挿入される直前に生成されて、Generated Columnの値はそれよりも前に決定される必要があるかららしいですね。

ちなみにSequelizeのモデル定義ではどう記述する?

普段の業務でSequelizeを使用しているので、どう記述するのかも調査しました。
結論、モデル定義ではGenerated Columnは定義できません。
ただし、DB上で作成したGenerated ColumnはSequelizeのモデル定義でカラムとして定義すれば取ってこれるらしいです。
なので、データベースマイグレーションのロールバックや作り直しの時はSequelizeだけで走りきると、DBに存在しているカラムが足りないことになるので注意が必要です(しっかりコメントアウトでメモを残しておくと良いかもしれません)

const User = sequelize.define('User', {
  firstName: {
    type: DataTypes.STRING,
    field: 'first_name'
  },
  lastName: {
    type: DataTypes.STRING,
    field: 'last_name'
  },
  fullName: {
    type: DataTypes.STRING,
    field: 'full_name' // こいつがGenerated Column
  }
}, {});

終わりに

Generated Columnを今回初めて知って触ったのですがかなりできることがありそうですので、indexを作成する際の選択肢として頭の片隅に置いておきたいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?