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?

AlembicでMySQLのFLOATをDOUBLEに変更する

Posted at

SQLAlchemyとAlembicを使った開発で、モデル定義とDBスキーマを一致させる autogenerate
Pythonのfloat型をMySQLのFLOAT/DOUBLE型に意図した通りマッピングくれないケースがあるので注意。

PythonのfloatがMySQLのFLOATにマッピングされる問題

Pythonの標準float型は64bitの倍精度浮動小数点数であり、MySQLのDOUBLE型に相当します。

しかし、SQLAlchemyで以下のように定義したモデルから生成されたテーブルでは、amountカラムが32bitの単精度FLOAT型になってしまってたんですよね。これでは有効数字が約7桁しかなく、高精度な数値が扱えません(というかバグとして報告されてしまっていました)。

# models.py
from sqlalchemy.orm import Mapped, mapped_column

class Transaction(Base):
    __tablename__ = 'transactions'
    # ...
    amount: Mapped[float] = mapped_column(nullable=False)

autogenerateが変更を検出しない

このカラムの型を修正するため、モデル定義を倍精度を意図するFloat(precision=53)や、より明示的なMySQL方言のmysql.DOUBLEに変更してみました。

# 変更案1: Float(precision=53)
from sqlalchemy import Float
amount: Mapped[float] = mapped_column(Float(precision=53), nullable=False)

# 変更案2: mysql.DOUBLE
from sqlalchemy.dialects.mysql import DOUBLE
amount: Mapped[float] = mapped_column(DOUBLE, nullable=False)

期待ではこれに対してマイグレーションを作成すると差異を検出してくれると思っていました。しかし、どちらのケースでalembic revision --autogenerateを実行しても、Alembicは変更を検出しなかったのです。

これは、Alembicのデフォルトの型比較ロジックが、DB上の既存のFLOAT型とこれらの型指定を「差異なし」と判断してしまうことがあるためらしいんですね。

解決策:マイグレーションファイルの手動作成

autogenerateが機能しない以上、手動でマイグレーションファイルを作成し、直接SQLの変更を記述する方法に切り替えました。

1. 空のマイグレーションファイルを生成

まず、Alembicコマンドでマイグレーションファイルの雛形を作成します。

alembic revision -m "change_amount_column_from_float_to_double"

2. マイグレーションファイルに直接変更を記述

生成されたversions/xxxxxxxx_....pyファイルを編集し、upgradedowngrade関数にop.alter_columnを記述します。

"""change amount column from float to double

Revision ID: xxxxxxxx
Revises: yyyyyyyy
Create Date: 2025-09-16 01:13:22.518320

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = 'xxxxxxxx'
down_revision = 'yyyyyyyy'
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.alter_column('transactions', 'amount',
                existing_type=mysql.FLOAT(),
                type_=mysql.DOUBLE(),
                nullable=False)


def downgrade() -> None:
    op.alter_column('transactions', 'amount',
                existing_type=mysql.DOUBLE(),
                type_=mysql.FLOAT(),
                nullable=False)

この方法であれば、Alembicの自動検出ロジックに依存せず、意図したスキーマ変更を確実に実行できました。

実はほかにも型を厳密にすることで検出できるかもしれないんでしすが、今回のケースではほかの個所で厳密にしてほしくないケースがあったのでこのやり方に落ち着きました。

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?