こいけるの日記

データサイエンス / データエンジニアリングに興味がある若手SIer社員の日記

【Django】マイグレーション時のエラー OperationalError: no such table を解決する

概要

Djangoを使ってちょっとしたアプリを作っていた時に、マイグレーション実行時以下のようなエラーが出てきた。

django.db.utils.OperationalError: no such table: tests_test1

エラーの原因とそれに対する解決方法をメモしておく。

バージョン情報

Django 3.1.4 を使用

事象の再現

以下のようなオペレーションを行うことによって、事象を再現することが可能。

  1. マイグレーションで新しいテーブルを作成
  2. 1で作成したテーブルを手動で消去
  3. マイグレーションでテーブルに対する変更を実行

詳細に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. 適用済みのマイグレーションを再度実行する
  2. マイグレーションファイルを手で編集し、実行する
  3. 手動で直接テーブルを作成する

ここでは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のコンペで、横持ちしている時系列データを縦持ちの形に変換したうえで処理したい、というニーズがあったので、やり方をメモ。

やりたいこと

横持ちのデータ構造を持つデータフレームを、

f:id:zaohgyu:20200405145514j:plain
横持ちの時系列売り上げデータ

縦持ちに変換する。

f:id:zaohgyu:20200405152152j:plain
縦持ちの時系列売り上げデータ

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のコンペに参加します

f:id:zaohgyu:20200312221830p:plain

会社の同期とKaggleのコンペに参加することになりました。 Kaggleって何?についてはこちらをご参照。

お題は以下。

www.kaggle.com

内容はウォルマートの28日間の売り上げ予測をする、というもの。

機械学習・データサイエンスは基本の基本くらいしか分からないけど、がんばります(賞金は総額$500,00らしい)。 学習したこととかは、ここに書いていこうと思います。

【Pega】Lock and Rollでバージョニングを行う

f:id:zaohgyu:20191110170601p:plain

やりたいこと

  • PegaのApplicationと、Applicationを構成しているRule Setをバージョニングする
  • 古いバージョンのRule Setはロックして編集できないようにする

Pegaのバージョニングの考え方

Pegaのバージョンは下の図のように、3つの数字で表される。

f:id:zaohgyu:20200303220230j:plain

  • 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のリストを開く

f:id:zaohgyu:20200303223056p:plain

② Lock and Rollを押下する

f:id:zaohgyu:20200303222142j:plain

③開いたウィザードで、以下の設定を行う

f:id:zaohgyu:20200303222335j:plain

  • 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

About RuleSet Versions | Pega

おわり~

【Pega】テーブルの内容をCSV出力するボタン

f:id:zaohgyu:20191110170601p:plain

やりたいこと

画面に表示しているテーブルの内容を、CSV形式で出力するボタンを実装する。

f:id:zaohgyu:20191130155811p:plain

実装方法

Activityの作成

Dev Studio右上の検索窓から、pxConvertResultsToCSVを検索して開く。このActivityは標準で提供されている、PageList のPropertyをCSV形式のファイルに変換するActivityである。

このActivityをこのまま使うことも可能だが、文字コードの違いによりExcelで開くと日本語が文字化けされて表示されるので、下記のようにカスタマイズする。

① pxConvertResultsToCSVをSave asで別名で保存

f:id:zaohgyu:20191201142629p:plain

② Identifierは適当に名前をつけ、Apply Toにボタンを作成するSectionの属するクラスを指定し、Create and openする

f:id:zaohgyu:20191201142832p:plain

③ ステップ5のJavaコード内22行目を以下のように変更

// PRWriter out = new PRWriter(stream, null, false); ←ここを変更
PRWriter out = new PRWriter(stream, "MS932", false); 

④ ActivityをSave

ボタンの作成

① 画面にボタンを配置

f:id:zaohgyu:20191201151958p:plain

② ボタンアクションにOpen URL in Windowを設定

f:id:zaohgyu:20191201152020p:plain

主に設定するのは以下。

  • 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

実装結果

ボタンを押すと、、、 f:id:zaohgyu:20191201154159p:plain

CSVをダウンロードできる。 f:id:zaohgyu:20191201154227p:plain

環境

Pega Platform 8.3.0 Personal Editionで実行。

おわり~