【Django】マイグレーション時のエラー OperationalError: no such table を解決する
概要
Djangoを使ってちょっとしたアプリを作っていた時に、マイグレーション実行時以下のようなエラーが出てきた。
django.db.utils.OperationalError: no such table: tests_test1
エラーの原因とそれに対する解決方法をメモしておく。
バージョン情報
Django 3.1.4 を使用
事象の再現
以下のようなオペレーションを行うことによって、事象を再現することが可能。
詳細に1ステップずつ見ていく。
models.py に以下のように、2つのカラムを持つ Test というモデルを定義する。
from django.db import models class Test(models.Model): column1 = models.CharField(max_length=10) column2 = models.CharField(max_length=20)
マイグレーションを作成・実行する。
(py39) C:\Users\XXX\TestProject>python manage.py makemigrations Migrations for 'tests': tests\migrations\0003_test.py - Create model Test (py39) C:\Users\XXX\TestProject>python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, tests Running migrations: Applying tests.0003_test... OK
次に、上記のマイグレーションで作成したテーブルを、SQLiteのコマンドラインツールから直接削除する。
(py39) C:\Users\XXX\TestProject>sqlite3 db.sqlite3 SQLite version 3.33.0 2020-08-14 13:23:32 Enter ".help" for usage hints. sqlite> .tables auth_group django_admin_log auth_group_permissions django_content_type auth_permission django_migrations auth_user django_session auth_user_groups tests_test auth_user_user_permissions tests_test1 sqlite> DROP TABLE tests_test; sqlite> .tables auth_group django_admin_log auth_group_permissions django_content_type auth_permission django_migrations auth_user django_session auth_user_groups tests_test1 auth_user_user_permissions
DROP TABLEの実施前後に実施した .tables コマンドの結果から、tests_test というテーブルが削除されていることが分かる。
次に、 models.py 上で削除されたテーブルに対する変更を加える。
from django.db import models class Test(models.Model): column1 = models.CharField(max_length=10) column2 = models.CharField(max_length=20) column3 = models.CharField(max_length=30) # 追加
ここでは新たに column3 というカラムを追加している。
この状態で再度マイグレーションを作成すると、以下の警告が出力される。
(py39) C:\Users\XXX\TestProject>python manage.py makemigrations You are trying to add a non-nullable field 'column3' to test without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option:
ここでは1を選択し、デフォルトの値として適当な値(1など)を入力して処理を進めたうえ、マイグレーションを実行すると、以下のエラーが得られる。
(py39) C:\Users\XXX\TestProject>python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, tests Running migrations: ~~~ (中略) ~~~ django.db.utils.OperationalError: no such table: tests_test
エラーの原因
エラーの原因はエラーメッセージにもある通り「マイグレーションで変更を加えようとしたテーブルが存在しないこと」である。
事象の再現に記載したように、Djangoのマイグレーション以外の方法でテーブルを消去すると、Djangoのマイグレーションで管理しているDBの情報と実際のDBの状態に不整合が生じ、上記のエラーが発生する。
解決方法
エラーの原因はテーブルが存在しないことなので、テーブルを再度作成することで解決しそうである。 テーブルを復活させるには、以下のような方法が考えられる。
ここでは1の方法を記載する。
まずは現在のマイグレーションの状態を、showmigrations というコマンドで確認する。
(py39) C:\Users\XXX\TestProject>python manage.py showmigrations admin [X] 0001_initial [X] 0002_logentry_remove_auto_add ~~~ (中略) ~~~ sessions [X] 0001_initial tests [X] 0001_initial [X] 0002_test1_column3 [X] 0003_test [ ] 0004_test_column3
今回対象のアプリである tests の欄に注目すると、
- 0003_test は[X](反映済み)
- 0004_test_column3 は[ ](未反映)
ということが分かる。言い換えると、
という状態であるといえる。
従って、この状態のままマイグレーションを実施したとしても、testテーブルをCREATEする0003_testは実行されず、0004_test_column3から実施することになる。
その場合、「そんなテーブルはないよ」というエラーが再度発生することになる。
逆に言うと、0003_testまで遡って再度実行すれば事象が解決しそう、という方針がたつ。
これを実行するために、以下のコマンドを実行する。
python manage.py migrate tests 0002 --fake
このコマンドのポイントは2つある。
1つは 0002 のようにマイグレーションのバージョンを指定していることである。これは Reversing migrations と呼ばれ、指定したバージョン以降に実行したマイグレーションをなかったことにする処理になる。今回は0003のマイグレーションをなかったことにするために0002を指定している。また、すべてのマイグレーションに対してリバースする場合は、zero というオプションが指定可能である。
もう1つは、--fake というオプションを付けている点である。このオプションは簡単に言うと「マイグレーションは実行しないが、マイグレーションを実行したことにする」というオプションである。
このオプションを付けない場合、要するに以下のようなコマンドを実行する場合、
python manage.py migrate tests 0002
0003_test のマイグレーションをなかったことにするため、スキーマに対する処理としては「test テーブルをDROPする処理が走る」ことになる。ただ、test テーブルは既に手動で削除されて存在しないので、DROPする対象がない、というエラーが発生することになる。 これを避けてマイグレーションを遡るために、--fake オプションをつけることによって、「リバースマイグレーションをしたことにする」ことが可能になる。
上記のコマンド実行後、再度showmigrationsをすると、
(py39) C:\Users\XXX\TestProject>python manage.py showmigrations admin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions [X] 0012_alter_user_first_name_max_length contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial tests [X] 0001_initial [X] 0002_test1_column3 [ ] 0003_test [ ] 0004_test_column3
0003_testのXが外れている(=未反映であると認識されている)ことが分かる。
この状態で再度マイグレーションを実行すると、
(py39) C:\Users\XXX\TestProject>python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, tests Running migrations: Applying tests.0003_test... OK Applying tests.0004_test_column3... OK
となり、無事エラーが解消したことが分かる。
まとめ
Djangoのマイグレーションで OperationalError: no such table エラーが発生したときの解決方法をまとめた。
そもそもの話だが、再現方法に書いたようなマイグレーション以外の方法で直接DBスキーマをいじるのは極力避け、Djangoのマイグレーション機能のみを使ってスキーマを管理していくのがベストプラクティスだと思う。
【Python】横持ちの時系列データを縦持ちに変換する
概要
現在参加中のKaggleのコンペで、横持ちしている時系列データを縦持ちの形に変換したうえで処理したい、というニーズがあったので、やり方をメモ。
やりたいこと
横持ちのデータ構造を持つデータフレームを、
縦持ちに変換する。
melt関数
使用するのはpandasのmelt関数。 詳しいパラメータの説明は上記リンクを参照してもらうこととして、今回使うパラメータは、
- id_vars → ID列として残しておく列の指定
- var_name → 縦持ちさせるカラムの列名の指定
- value_name → 縦持ちさせる値の列名の指定
の3つ。
実行例
import pandas as pd
file_path = "../data/sample_a.csv"
data_pivot = pd.read_csv(file_path)
# 変換前のデータ
data_pivot.head()
ID | Item_ID | Shop_ID | Day1 | Day2 | Day3 | |
---|---|---|---|---|---|---|
0 | itemA_shopX | itemA | shopX | 6 | 6 | 6 |
1 | itemA_shopY | itemA | shopY | 8 | 6 | 9 |
2 | itemB_shopX | itemB | shopX | 5 | 1 | 7 |
3 | itemB_shopY | itemB | shopY | 10 | 1 | 7 |
# melt関数を使って変換 data_unpivot = data_pivot.melt(id_vars=["ID", "Item_ID", "Shop_ID"], var_name="Day", value_name="Count")
# 変換後のデータ data_unpivot.head(12)
ID | Item_ID | Shop_ID | Day | Count | |
---|---|---|---|---|---|
0 | itemA_shopX | itemA | shopX | Day1 | 6 |
1 | itemA_shopY | itemA | shopY | Day1 | 8 |
2 | itemB_shopX | itemB | shopX | Day1 | 5 |
3 | itemB_shopY | itemB | shopY | Day1 | 10 |
4 | itemA_shopX | itemA | shopX | Day2 | 6 |
5 | itemA_shopY | itemA | shopY | Day2 | 6 |
6 | itemB_shopX | itemB | shopX | Day2 | 1 |
7 | itemB_shopY | itemB | shopY | Day2 | 1 |
8 | itemA_shopX | itemA | shopX | Day3 | 6 |
9 | itemA_shopY | itemA | shopY | Day3 | 9 |
10 | itemB_shopX | itemB | shopX | Day3 | 7 |
11 | itemB_shopY | itemB | shopY | Day3 | 7 |
おわり~
Kaggleのコンペに参加します
【Pega】Lock and Rollでバージョニングを行う
やりたいこと
- PegaのApplicationと、Applicationを構成しているRule Setをバージョニングする
- 古いバージョンのRule Setはロックして編集できないようにする
Pegaのバージョニングの考え方
Pegaのバージョンは下の図のように、3つの数字で表される。
- Major Version → プロダクトに対して大きなアップデートが入るリリース時などに更新
- Minor Version → プロダクトに対して機能追加などの小規模なアップデートが入ったときなどに更新
- Patch Version → バグ対応などをしたときに更新
例) SampleApp : 01-01-01 → SampleApp : 01-02-01 → SampleApp : 01-02-02 → SampleApp : 02-01-01
みたいな感じで、変更とともにバージョンをあげていく。
RuleSetのバージョニングのベストプラクティスは以下。
- 最新のバージョン以外はロックしておく
- 最新のバージョンのみで開発を行う
- 一度ロックされたRuleSetのロックを解かない
- すべてのRuleがCheck-outされていない状態で、ロックを行う
実行方法
① Configure > Application > Structure > RuleSet Stuck からアプリケーションが持つRuleSetのリストを開く
② Lock and Rollを押下する
③開いたウィザードで、以下の設定を行う
- Lock → ロックするRuleSetを選択する
- Password → ロックを解除するときのパスワードを設定
- Roll → バージョンを上げるRuleSetを選択する
- Roll to Version → バージョンを上げたときの数字を設定
- Description → 適当に設定
- Roll to Version → アプリケーションのバージョンを上げた時の数字を設定
これをRunすることで、
- Applicationをバージョンアップ(01-01-03→01-01-04)
- RuleSetをバージョンアップ(01-01-02→01-01-03)
- 古いRuleSetのバージョンをロック(01-01-02)
ができる。
実行環境
Pega Platform 8.3.0 Personal Edition
参考にしたページ
How to use the Lock and Roll feature for managing RuleSet Versions | Pega
おわり~
【Pega】テーブルの内容をCSV出力するボタン
やりたいこと
画面に表示しているテーブルの内容を、CSV形式で出力するボタンを実装する。
実装方法
Activityの作成
Dev Studio右上の検索窓から、pxConvertResultsToCSVを検索して開く。このActivityは標準で提供されている、PageList のPropertyをCSV形式のファイルに変換するActivityである。
このActivityをこのまま使うことも可能だが、文字コードの違いによりExcelで開くと日本語が文字化けされて表示されるので、下記のようにカスタマイズする。
① pxConvertResultsToCSVをSave asで別名で保存
② Identifierは適当に名前をつけ、Apply Toにボタンを作成するSectionの属するクラスを指定し、Create and openする
③ ステップ5のJavaコード内22行目を以下のように変更
// PRWriter out = new PRWriter(stream, null, false); ←ここを変更 PRWriter out = new PRWriter(stream, "MS932", false);
④ ActivityをSave
ボタンの作成
① 画面にボタンを配置
② ボタンアクションにOpen URL in Windowを設定
主に設定するのは以下。
Use Page : チェックをつける
Activity : 先ほど作成したActivityを設定
CSVProperties : CSVで各カラムに出力するPropertyをダブルクォーテーションで囲み、カンマ区切りで指定。カンマ前後にスペースは含めない。
CSVPropertyTypes : 出力時のカラムフォーマットをダブルクォーテーションで囲み、カンマ区切りで指定。String, Decimal, Double, Date, DateTimeを指定可。カンマ前後にスペースは含めない。
PageListProperty : Page ListのPropertyをダブルクォーテーションで囲んで指定。
FileName : 出力時のCSVファイル名を指定。空白の場合は”ResultsToCSV”になる。
AppendTimeStampToFileName : Trueを指定すると出力時のファイル名にTimeStampが付与される。
CSVPropHeaders : 出力時のヘッダーを、カンマ区切りで指定。カンマ前後にスペースは含めない。
③ SubmitしてSave
実装結果
ボタンを押すと、、、
CSVをダウンロードできる。
環境
Pega Platform 8.3.0 Personal Editionで実行。
おわり~