Fix migration
This commit is contained in:
179
check_null_values.py
Normal file
179
check_null_values.py
Normal file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
NULL値チェック・デフォルト値テストスクリプト
|
||||
"""
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
import logging
|
||||
|
||||
# ログ設定
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# データベース設定
|
||||
OLD_ROGDB_CONFIG = {
|
||||
'host': os.getenv('OLD_ROGDB_HOST', 'postgres-db'),
|
||||
'database': os.getenv('OLD_ROGDB_NAME', 'old_rogdb'),
|
||||
'user': os.getenv('OLD_ROGDB_USER', 'admin'),
|
||||
'password': os.getenv('OLD_ROGDB_PASSWORD', 'admin123456'),
|
||||
'port': int(os.getenv('OLD_ROGDB_PORT', 5432))
|
||||
}
|
||||
|
||||
ROGDB_CONFIG = {
|
||||
'host': os.getenv('ROGDB_HOST', 'postgres-db'),
|
||||
'database': os.getenv('ROGDB_NAME', 'rogdb'),
|
||||
'user': os.getenv('ROGDB_USER', 'admin'),
|
||||
'password': os.getenv('ROGDB_PASSWORD', 'admin123456'),
|
||||
'port': int(os.getenv('ROGDB_PORT', 5432))
|
||||
}
|
||||
|
||||
def check_null_values():
|
||||
"""NULL値の問題を事前チェック"""
|
||||
try:
|
||||
old_conn = psycopg2.connect(**OLD_ROGDB_CONFIG)
|
||||
new_conn = psycopg2.connect(**ROGDB_CONFIG)
|
||||
|
||||
old_conn.autocommit = True
|
||||
new_conn.autocommit = True
|
||||
|
||||
old_cursor = old_conn.cursor()
|
||||
new_cursor = new_conn.cursor()
|
||||
|
||||
# 共通テーブル取得
|
||||
old_cursor.execute("""
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name LIKE 'rog_%'
|
||||
""")
|
||||
old_tables = [row[0] for row in old_cursor.fetchall()]
|
||||
|
||||
new_cursor.execute("""
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name LIKE 'rog_%'
|
||||
""")
|
||||
new_tables = [row[0] for row in new_cursor.fetchall()]
|
||||
|
||||
common_tables = list(set(old_tables) & set(new_tables))
|
||||
|
||||
logger.info(f"チェック対象テーブル: {len(common_tables)}個")
|
||||
|
||||
null_issues = {}
|
||||
|
||||
for table_name in common_tables:
|
||||
logger.info(f"=== {table_name} NULL値チェック ===")
|
||||
|
||||
# 新しいDBのNOT NULL制約確認
|
||||
new_cursor.execute("""
|
||||
SELECT column_name, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = %s AND table_schema = 'public'
|
||||
AND is_nullable = 'NO'
|
||||
ORDER BY ordinal_position
|
||||
""", (table_name,))
|
||||
|
||||
not_null_columns = new_cursor.fetchall()
|
||||
|
||||
if not not_null_columns:
|
||||
logger.info(f" NOT NULL制約なし")
|
||||
continue
|
||||
|
||||
logger.info(f" NOT NULL制約カラム: {[col[0] for col in not_null_columns]}")
|
||||
|
||||
# 古いDBのNULL値チェック
|
||||
for col_name, is_nullable, default_val in not_null_columns:
|
||||
try:
|
||||
# PostgreSQL予約語とcamelCaseカラムのクォート処理
|
||||
reserved_words = ['group', 'like', 'order', 'user', 'table', 'index', 'where', 'from', 'select']
|
||||
quoted_col = f'"{col_name}"' if (col_name.lower() in reserved_words or any(c.isupper() for c in col_name)) else col_name
|
||||
|
||||
# カラム存在チェック
|
||||
old_cursor.execute("""
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_name = %s AND column_name = %s AND table_schema = 'public'
|
||||
""", (table_name, col_name))
|
||||
|
||||
if old_cursor.fetchone()[0] == 0:
|
||||
logger.warning(f" ⚠️ {col_name}: 古いDBに存在しないカラム")
|
||||
continue
|
||||
|
||||
old_cursor.execute(f"""
|
||||
SELECT COUNT(*) FROM {table_name}
|
||||
WHERE {quoted_col} IS NULL
|
||||
""")
|
||||
|
||||
null_count = old_cursor.fetchone()[0]
|
||||
|
||||
if null_count > 0:
|
||||
logger.warning(f" ⚠️ {col_name}: {null_count}件のNULL値あり (デフォルト: {default_val})")
|
||||
|
||||
if table_name not in null_issues:
|
||||
null_issues[table_name] = []
|
||||
null_issues[table_name].append((col_name, null_count, default_val))
|
||||
else:
|
||||
logger.info(f" ✅ {col_name}: NULL値なし")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ {col_name}: チェックエラー: {e}")
|
||||
|
||||
# サマリー
|
||||
if null_issues:
|
||||
logger.warning("=" * 60)
|
||||
logger.warning("NULL値問題のあるテーブル:")
|
||||
for table, issues in null_issues.items():
|
||||
logger.warning(f" {table}:")
|
||||
for col, count, default in issues:
|
||||
logger.warning(f" - {col}: {count}件 (デフォルト: {default})")
|
||||
else:
|
||||
logger.info("✅ NULL値の問題はありません")
|
||||
|
||||
old_cursor.close()
|
||||
new_cursor.close()
|
||||
old_conn.close()
|
||||
new_conn.close()
|
||||
|
||||
return null_issues
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ NULL値チェックエラー: {e}")
|
||||
return {}
|
||||
|
||||
def suggest_default_values(null_issues):
|
||||
"""デフォルト値の提案"""
|
||||
if not null_issues:
|
||||
return
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("推奨デフォルト値設定:")
|
||||
|
||||
for table_name, issues in null_issues.items():
|
||||
logger.info(f" '{table_name}': {{")
|
||||
for col_name, count, default in issues:
|
||||
# データ型に基づくデフォルト値推測
|
||||
if 'trial' in col_name.lower() or 'is_' in col_name.lower():
|
||||
suggested = 'False'
|
||||
elif 'public' in col_name.lower():
|
||||
suggested = 'True'
|
||||
elif 'name' in col_name.lower() or 'description' in col_name.lower():
|
||||
suggested = "''"
|
||||
elif 'order' in col_name.lower() or 'sort' in col_name.lower():
|
||||
suggested = '0'
|
||||
else:
|
||||
suggested = 'None # 要確認'
|
||||
|
||||
logger.info(f" '{col_name}': {suggested}, # {count}件のNULL")
|
||||
logger.info(" },")
|
||||
|
||||
def main():
|
||||
logger.info("=" * 60)
|
||||
logger.info("NULL値チェック・デフォルト値提案スクリプト")
|
||||
logger.info("=" * 60)
|
||||
|
||||
null_issues = check_null_values()
|
||||
suggest_default_values(null_issues)
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("チェック完了")
|
||||
logger.info("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user