[Python]Oracle DataBase マスキングプログラム

目次
  用意するもの
  コード
  使用方法
  最後に
PGを書くきっかけは仕事でデータベースの個人情報をマスクする必要があり、
各DB毎にテーブルを確認しながら1列づつ処理していくのは大変だということで
マスク処理用のプログラムを作りました。(それでもある程度は設定が必要だが・・・)

用意するもの
  Python3.4~3.7(3.xxならほぼ使用できるはず)
    モジュール = cx_Oracle, pandas, sqlalchemy
  Oracle Instant Client(11.2 or 12.1 or 12.2は動作確認済み)←cx_Oracleを使用する為
  (あとはマスク対象のDBのテーブル定義書はあると便利かな?)

コード

import cx_Oracle
import json
import tkinter
from tkinter import StringVar
from tkinter import BooleanVar
from tkinter import ttk
import tkinter as tk
import tkinter.messagebox as msg
import time
import datetime
import sys
import os
import pandas as pd
from sqlalchemy import types, create_engine
import threading
#文字コードによる不具合回避
os.environ['NLS_LANG'] = 'JAPANESE_JAPAN.AL32UTF8' #マルチバイト文字を扱う際(主に日本語)文字コードの例外が出るので指定する
os.environ['NLS_DATE_FORMAT'] = "YYYY-MM-DD HH24:MI:SS" #上記同様
#Version
Version = 3.0
#Global変数
Time_start = None
Elapsed_time = None
sub_win = None
sub_win2 = None
help_win = None
Table_Name = "テーブル名+カラム名"
UPDATE_SQL_TEXT = []
#置き換え文字定義
txt_up_sql = "○"
num_up_sql = "9"        
han_up_sql = "x"
#条件SQLファイル管理(.sql)
Nwhere_sql_file = None
Kwhere_sql_file = None
Pwhere_sql_file = None
Twhere_sql_file = None
Mwhere_sql_file = None
Awhere_sql_file = None
#プライマルキーSQLファイル管理(.sql)
Primary_sql_file = os.path.join(os.path.dirname(sys.argv[0]), "primary_Key_Get_SQL.sql")
Primary_sql_file_check = os.path.isfile(Primary_sql_file)
Primary_Key_SQL = ""
Primary_List = None
Primary_Mode = False
#ベースSQL
Base_SQL_Text = "SELECT TABLE_NAME,COLUMN_NAME,COLUMN_ID,DATA_TYPE,DATA_LENGTH,NULLABLE FROM USER_TAB_COLUMNS "
ORDER_BY = "ORDER BY TABLE_NAME"
#条件SQLフォルダ名
Where_Folder = "WHERE_Base_Folder"
Null_Base_SQL_Text = ""
NSQL_txt = None
KSQL_txt = None
PSQL_txt = None
TSQL_txt = None
MSQL_txt = None
ASQL_txt = None
#DB情報ファイル管理(.json)
Dbaccess = {}
new_Dbaccess = {"TARGET_HOST": "", "PORT": "", "SERVICE_NAME": "", "SCHEME_NAME": "", "PASSWORD": "", "WHERE_Folder": "WHERE_Base_Folder"}
Dbaccess_file = os.path.join(os.path.dirname(sys.argv[0]), "Dbaccess.json")
Dbaccess_file_check = os.path.isfile(Dbaccess_file)
#Logファイル
LOG_Folder = "LOG_Folder"
Log_file = os.path.join(os.path.dirname(sys.argv[0]), LOG_Folder, "Log.txt")
ErrorLog_file = os.path.join(os.path.dirname(sys.argv[0]), LOG_Folder, "ErrorLog.txt")
#UPDATEカウンター(終了時のupdata件数表示用)
UPDATE_count = 0
NULL_Count = 0
ERROR_count = 0
#SQL条件ファイルチェック
def SQL_Where_Check():      
    global Nwhere_sql_file, Kwhere_sql_file, Pwhere_sql_file, Twhere_sql_file, Mwhere_sql_file, Awhere_sql_file 
    #ディレクトリがあるか検索しなければ作成
    if not os.path.exists(os.path.dirname(sys.argv[0]) + "\\" + Where_Folder):
        os.mkdir(os.path.dirname(sys.argv[0]) + "\\" + Where_Folder)   
    #NAME条件SQLファイル管理(.sql)
    Nwhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "NAME.sql")
    Nwhere_sql_file_check = os.path.isfile(Nwhere_sql_file) 
    #KANA条件SQLファイル管理(.sql)
    Kwhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "KANA.sql")
    Kwhere_sql_file_check = os.path.isfile(Kwhere_sql_file)
    #POST条件SQLファイル管理(.sql)
    Pwhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "POST.sql")
    Pwhere_sql_file_check = os.path.isfile(Pwhere_sql_file)
    #TEL条件SQLファイル管理(.sql)
    Twhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "TEL.sql")
    Twhere_sql_file_check = os.path.isfile(Twhere_sql_file)
    #MAIL条件SQLファイル管理(.sql)
    Mwhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "MAIL.sql")
    Mwhere_sql_file_check = os.path.isfile(Mwhere_sql_file)
    #ADDR条件SQLファイル管理(.sql)
    Awhere_sql_file = os.path.join(os.path.dirname(sys.argv[0]), Where_Folder, "ADDR.sql")
    Awhere_sql_file_check = os.path.isfile(Awhere_sql_file)
    #条件ファイル
    if None or not Nwhere_sql_file_check:
        with open(Nwhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)
    if None or not Kwhere_sql_file_check:
        with open(Kwhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)
    if None or not Pwhere_sql_file_check:
        with open(Pwhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)
    if None or not Twhere_sql_file_check:
        with open(Twhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)
    if None or not Mwhere_sql_file_check:
        with open(Mwhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)
    if None or not Awhere_sql_file_check:
        with open(Awhere_sql_file, mode="w") as f:
            f.write(Null_Base_SQL_Text)    
#jsonファイルの有無(有ればSQL条件フォルダ名の取得、無ければ新規作成)
def DBAccessFile_check():
    global Dbaccess, Where_Folder
    if None or not Dbaccess_file_check:
        with open(Dbaccess_file, "w") as fn:
            json.dump(new_Dbaccess, fn, indent=6)
    else:
        with open(Dbaccess_file) as f:
            jsn = json.load(f) 
        if not str(jsn["WHERE_Folder"]) == "":
            Where_Folder = str(jsn["WHERE_Folder"]) 
#Logファイルチェック
def LOG_File_Check():      
    global Log_file, ErrorLog_file
    #ディレクトリがあるか検索しなければ作成
    os.makedirs(LOG_Folder, exist_ok=True)
    now = datetime.datetime.now()
    logname = "Log_" + now.strftime('%Y%m%d') + ".txt"
    errorlogname = "ErrorLog_" + now.strftime('%Y%m%d') + ".txt"
    #Log
    Log_file = os.path.join(os.path.dirname(sys.argv[0]), LOG_Folder, logname)
    #Error
    ErrorLog_file = os.path.join(os.path.dirname(sys.argv[0]), LOG_Folder, errorlogname)
#Logファイル出力
def Log_file_exp(ex):
    with open(Log_file, mode="a") as f:
        f.write(str(ex) + "\n")
def ErrorLog_file_exp(ex):
    with open(ErrorLog_file, mode="a") as f:
        f.write(str(ex) + "\n")
#SQL実行用カーソル定義関数
def sql_cursor(connect):    
    cursor = connect.cursor() # カーソルを取得
    cursor.arraysize = 10000 #cur.arraysize 設定 デフォルトは100。
    return cursor          
#DB接続
def connect():
    global Dbaccess, Where_Folder
    try:
        with open(Dbaccess_file) as f:
            jsn = json.load(f)
        TARGET_HOST = jsn["TARGET_HOST"]
        PORT = jsn["PORT"]
        SERVICE_NAME = jsn["SERVICE_NAME"]
        SCHEME_NAME = jsn["SCHEME_NAME"]
        PASSWORD = jsn["PASSWORD"]
        Dbaccess = jsn
        Where_Folder = str(jsn["WHERE_Folder"])        
        label["text"] = jsn["SCHEME_NAME"] + "@" + jsn["SERVICE_NAME"]
        if not TARGET_HOST == "" and not PORT == "" and not SERVICE_NAME == "" and not SCHEME_NAME == "" and not PASSWORD == "":
            tns = cx_Oracle.makedsn(TARGET_HOST, PORT, SERVICE_NAME) # tnsを設定
            connect = cx_Oracle.connect(SCHEME_NAME, PASSWORD, tns) # DBに接続
            return connect
        else:
            msg.showerror('エラー', "DB接続用ファイルの項目に不備が在ります。\n確認してください。")
    except Exception as e:
        Log_file_exp(e)
        msg.showerror('ORACLE接続エラー', e)
#取得したテーブル名でselect文実行
def sql_create_updata_list(table, column):
    sql_txt = "SELECT " + column + " FROM " + table + " WHERE " + column + " IS NOT NULL"
    return sql_txt
#条件SQLファイル読み込み #改行毎に1つの値としてリストに代入 → [s.strip() for s in fe.readlines()]
def where_sql_load():
    global NSQL_txt, KSQL_txt, PSQL_txt, TSQL_txt, MSQL_txt, ASQL_txt
    with open(Nwhere_sql_file) as fe:
        NSQL_txt = [s.strip() for s in fe.readlines()]
    with open(Kwhere_sql_file) as fe:
        KSQL_txt = [s.strip() for s in fe.readlines()]
    with open(Pwhere_sql_file) as fe:
        PSQL_txt = [s.strip() for s in fe.readlines()]
    with open(Twhere_sql_file) as fe:
        TSQL_txt = [s.strip() for s in fe.readlines()]
    with open(Mwhere_sql_file) as fe:
        MSQL_txt = [s.strip() for s in fe.readlines()]
    with open(Awhere_sql_file) as fe:
        ASQL_txt = [s.strip() for s in fe.readlines()]
#条件SQLの配列を受け取りSQL文を生成し返すメソッド
def WHERE_SQL_CREATE(WHERE_Text_List):
    SQL_TEXT = Base_SQL_Text
    #条件文を改行毎に取得した配列から繋げて生成
    for i in range(len(WHERE_Text_List)):
        if not "" == WHERE_Text_List[i]:
            if 0 == i:
                SQL_TEXT += "WHERE " + WHERE_Text_List[i] + " "
            else:
                SQL_TEXT += "AND " + WHERE_Text_List[i] + " "
    #プライマルキー指定のテーブルとカラム名を除くための条件を生成
    if Primary_Mode == True:
        for wr in Primary_List.values:
            SQL_TEXT += "AND COLUMN_NAME NOT LIKE '" + str(wr[1]) + "' "
    SQL_TEXT += ORDER_BY
    return SQL_TEXT
#プライマルキー取得SQLファイル読み込み
def Primary_Key_Get_SQL_text():
    global Primary_Key_SQL
    try:
        with open(Primary_sql_file) as fe:
            Primary_Key_SQL = fe.read().replace('\n','') 
    except Exception as e:
        ErrorLog_file_exp(e)
        ErrorLog_file_exp("----- Error ----- " + str(datetime.datetime.now()))
        msg.showerror('ファイル読み込みエラー', str(e) + "\nプライマルキー取得SQLファイルがありません。\n確認してください。")
#対象カラム数取得関数
def get_count_chenges(Primary_boolean):
    global Primary_List
    try:
        #DB接続変数
        Con = connect()
        #戻り値用int
        Count = 0
        #同時にプライマルキー指定のテーブル名とカラム名を取得し配列をグローバル変数にセット
        if Primary_boolean == True:
            Primary_List = pd.read_sql(Primary_Key_SQL, Con)
        #変更カラム数取得
        if len(NSQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(NSQL_txt), Con)
            Count += len(db_rows)
        if len(KSQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(KSQL_txt), Con)
            Count += len(db_rows)
        if len(PSQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(PSQL_txt), Con)
            Count += len(db_rows)
        if len(TSQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(TSQL_txt), Con)
            Count += len(db_rows)
        if len(MSQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(MSQL_txt), Con)
            Count += len(db_rows)
        if len(ASQL_txt) != 0:            
            db_rows = pd.read_sql(WHERE_SQL_CREATE(ASQL_txt), Con)
            Count += len(db_rows)
        return Count
    except Exception as e:
        ErrorLog_file_exp(e)
        msg.showerror('SQLエラー', e)
    finally:
        Con.close() 
#UPDATE文生成 (受け取った[ cheng ]変数が数値に変換できるかどうかで分岐
def sql_UPDATE(datatable, columns, cheng): 
    if not cheng.isdecimal():
        txt_sql = "UPDATE " + datatable + " SET " + columns + " = '" + str(cheng) + "'||SUBSTR(" + columns + ", 2, 3) || '" + str(cheng) + str(cheng) + "' WHERE " + columns + " IS NOT NULL"
    else:
        txt_sql = "UPDATE " + datatable + " SET " + columns + " = regexp_replace(" + columns + ",'[0-9]','" + str(cheng) + "', 4) WHERE " + columns + " IS NOT NULL"
    return txt_sql
#該当列が半角か全角かを判別するSQL(文字バイト数で計算しているのでキャラクタセットがUTF-8だと半角カナも全角扱いです)
def Check_Column_str(datatable, columns):
    SQL = "SELECT " + columns + ", CASE WHEN LENGTH(" + columns + ") = LENGTHB(" + columns + ") THEN 1 ELSE 0 END NARROW, ROWNUM"
    SQL += " FROM " + datatable
    SQL += " WHERE CASE WHEN LENGTH(" + columns + ") = LENGTHB(" + columns + ") THEN 1 ELSE 0 END = 1 AND ROWNUM = 1"
    return SQL
#PLSQL用UPDATE SQL生成ロジック
def PLSQL_UPDATE(datatable, columns, cheng):
    global UPDATE_SQL_TEXT
    if not cheng.isdecimal():
        UPDATE_SQL_TEXT.append("UPDATE " + datatable + " SET " + columns + " = '" + str(cheng) + "'||SUBSTR(" + columns + ", 2, 3) || '" + str(cheng) + str(cheng) + "' WHERE " + columns + " IS NOT NULL")
    else:
        UPDATE_SQL_TEXT.append("UPDATE " + datatable + " SET " + columns + " = regexp_replace(" + columns + ",'[0-9]','" + str(cheng) + "', 4) WHERE " + columns + " IS NOT NULL")
# ORACLE データベース接続設定画面
def Oracle_Access():
    global sub_win2, Dbaccess
    if sub_win2 is None or not sub_win2.winfo_exists(): #ウィンドウを無ければ新規作成
        sub_win2 = tk.Toplevel()
        sub_win2.title('ORACLE 接続先設定')
        sub_win2.resizable(False, False)
        frame1 = ttk.Frame(sub_win2, padding=10)
        frame1.grid()
        #jsonファイルを開き設定内容取得
        with open(Dbaccess_file) as f:
            jsn = json.load(f)

        label1 = ttk.Label(frame1, text='TARGET_HOST', padding=(5,2))
        label1.grid(row=0,column=0,sticky=tk.E)
    
        label2 = ttk.Label(frame1, text='PORT', padding=(5,2))
        label2.grid(row=1,column=0,sticky=tk.E)
    
        label3 = ttk.Label(frame1, text='SERVICE_NAME', padding=(5,2))
        label3.grid(row=2,column=0,sticky=tk.E)

        label4 = ttk.Label(frame1, text='SCHEME_NAME', padding=(5,2))
        label4.grid(row=3,column=0,sticky=tk.E)

        label5 = ttk.Label(frame1, text='PASSWORD', padding=(5,2))
        label5.grid(row=4,column=0,sticky=tk.E)

        TARGET_HOST = StringVar()
        TARGET_HOST = ttk.Entry(
            frame1,
            textvariable=TARGET_HOST,
            width=60 )
        TARGET_HOST.grid(row=0,column=1)
        TARGET_HOST.insert(tkinter.END, jsn["TARGET_HOST"])

        PORT = StringVar()
        PORT = ttk.Entry(
            frame1,
            textvariable=PORT,
            width=60 )
        PORT.grid(row=1,column=1)
        PORT.insert(tkinter.END, jsn["PORT"])

        SERVICE_NAME = StringVar()
        SERVICE_NAME = ttk.Entry(
            frame1,
            textvariable=SERVICE_NAME,
            width=60 )
        SERVICE_NAME.grid(row=2,column=1)
        SERVICE_NAME.insert(tkinter.END, jsn["SERVICE_NAME"])

        SCHEME_NAME = StringVar()
        SCHEME_NAME = ttk.Entry(
            frame1,
            textvariable=SCHEME_NAME,
            width=60 )
        SCHEME_NAME.grid(row=3,column=1)
        SCHEME_NAME.insert(tkinter.END, jsn["SCHEME_NAME"])

        PASSWORD = StringVar()
        PASSWORD = ttk.Entry(
            frame1,
            textvariable=PASSWORD,
            width=60 )
        PASSWORD.grid(row=4,column=1)
        PASSWORD.insert(tkinter.END, jsn["PASSWORD"])

        #入力した内容を反映して保存するコマンド
        def ok_info():
            Dbaccess["TARGET_HOST"] = TARGET_HOST.get()
            Dbaccess["PORT"] = PORT.get()
            Dbaccess["SERVICE_NAME"] = SERVICE_NAME.get()
            Dbaccess["SCHEME_NAME"] = SCHEME_NAME.get()
            Dbaccess["PASSWORD"] = PASSWORD.get()
            Dbaccess["WHERE_Folder"] = Where_Folder
            with open(Dbaccess_file, "w") as f:
                json.dump(Dbaccess, f, indent=6)
            msg.showinfo("OK", "保存しました")
            sub_win2.destroy()
        #キャンセルボタン用コマンド
        def sub2_close():
            sub_win2.destroy()

        frame2 = ttk.Frame(frame1, padding=(0,5))
        frame2.grid(row=5,column=1,sticky=tk.W)

        button1 = ttk.Button(frame2, text="OK", command=ok_info)
        button1.pack(side=tk.LEFT)
        
        button2 = ttk.Button(frame2, text="Cancel",command=sub2_close)
        button2.pack(side=tk.LEFT)
#条件フォルダー名設定def
def Where_Directory():
    global sub_win, Dbaccess, Where_Folder
    if sub_win is None or not sub_win.winfo_exists(): #ウィンドウを無ければ新規作成
        sub_win = tk.Toplevel()
        sub_win.title('条件SQLフォルダ名設定')
        sub_win.resizable(False, False)
        frame1 = ttk.Frame(sub_win, padding=10)
        frame1.grid()
        #jsonファイルを開き設定内容取得
        with open(Dbaccess_file) as f:
            jsn = json.load(f)


        label1 = ttk.Label(frame1, text='条件SQLフォルダ名', padding=(5,2))
        label1.grid(row=0,column=0,sticky=tk.E)
    
        WHERE_Folder = StringVar()
        WHERE_Folder = ttk.Entry(
            frame1,
            textvariable=WHERE_Folder,
            width=60 )
        WHERE_Folder.grid(row=0,column=1)
        WHERE_Folder.insert(tkinter.END, jsn["WHERE_Folder"])


        #入力した内容を反映して保存するコマンド
        def ok_info():
            try:                    
                global Where_Folder
                Dbaccess["TARGET_HOST"] = jsn["TARGET_HOST"]
                Dbaccess["PORT"] = jsn["PORT"]
                Dbaccess["SERVICE_NAME"] = jsn["SERVICE_NAME"]
                Dbaccess["SCHEME_NAME"] = jsn["SCHEME_NAME"]
                Dbaccess["PASSWORD"] = jsn["PASSWORD"]
                Dbaccess["WHERE_Folder"] = WHERE_Folder.get()
                Where_Folder = WHERE_Folder.get()
                os.makedirs(Where_Folder, exist_ok=True)
                with open(Dbaccess_file, "w") as f:
                    json.dump(Dbaccess, f, indent=6)
                SQL_Where_Check()
                where_sql_load()
                msg.showinfo("OK", "保存しました")
                sub_win.destroy()
            except Exception as e:
                Log_file_exp(e)
                msg.showerror('エラー', e)
        #キャンセルボタン用コマンド
        def sub2_close():
            sub_win.destroy()

        frame2 = ttk.Frame(frame1, padding=(0,5))
        frame2.grid(row=5,column=1,sticky=tk.W)

        button1 = ttk.Button(frame2, text="OK", command=ok_info)
        button1.pack(side=tk.LEFT)
        
        button2 = ttk.Button(frame2, text="Cancel",command=sub2_close)
        button2.pack(side=tk.LEFT)
#ヘルプ画面用関数
def ver_help():
    global help_win
    if help_win is None or not help_win.winfo_exists(): #ウィンドウを無ければ新規作成
        help_win = tk.Toplevel()
        help_win.title("バージョン情報")
        frame1 = ttk.Frame(help_win,padding=10)
        frame1.grid()
        #OKボタン用コマンド
        def help_close():
            help_win.destroy()

        label1 = ttk.Label(frame1, text="Oracle DB マスクプログラム Ver " + str(Version), padding=(5,2))
        label1.grid(row=0,column=0,sticky=tk.W)
        
        #ボタンUIの位置及びコマンド設定
        button1 = ttk.Button(frame1, text='OK', padding=5, command=help_close)
        button1.grid(row=10, column=1, sticky=tk.E)
#メインフレーム終了コマンド
def Exit():
    root.quit()
#各タイムログ出力処理関数(Table)
def Time_Log_Writing(table, timevalue):
    Table_Each_Elapsed_time = time.time() - timevalue
    TEET = datetime.timedelta(seconds = Table_Each_Elapsed_time)
    txt = "「" + str(table) + "」テーブル終了時間「" + str(TEET) + "」" + str(datetime.datetime.now())
    Log_file_exp(txt)
#各タイムログ出力処理関数(Columns)
def Time_Log_Writing_Columns(columns, timevalue):
    Columns_Each_Elapsed_time = time.time() - timevalue
    CEET = datetime.timedelta(seconds = Columns_Each_Elapsed_time)
    txt = "「" + str(columns) + "」カラム終了時間「" + str(CEET) + "」" + str(datetime.datetime.now())
    Log_file_exp(txt)
#コミットタイムログ出力処理関数
def Time_Log_Writing_Commit(timevalue):
    Commit_Each_Elapsed_time = time.time() - timevalue
    CEET = datetime.timedelta(seconds = Commit_Each_Elapsed_time)
    txt = "コミット終了時間「" + str(CEET) + "」" + str(datetime.datetime.now())
    Log_file_exp(txt)
#PL/SQL実行時間タイムログ出力関数
def Time_Log_Writing_PLSQL(timevalue, ColumnsCount):
    Commit_Each_Elapsed_time = time.time() - timevalue
    CEET = datetime.timedelta(seconds = Commit_Each_Elapsed_time)
    txt = "PL/SQL(BEGIN) 実行終了時間「" + str(CEET) + "」 対象カラム数「" + str(ColumnsCount) + "」" + str(datetime.datetime.now())
    Log_file_exp(txt)
#PLSQL生成ロジック(テーブル単位で作成されている) #キャッチ後該当列のUPDATEを無視して続行するために関数を分ける
def PLSQL_Change_SQL(Cur):
    global ERROR_count, UPDATE_SQL_TEXT
    try:
        PLSQL_Each_Time_start = time.time()
        PLSQL = "begin \n"
        for strList in UPDATE_SQL_TEXT:
            PLSQL += "    " + strList + ";\n"
        PLSQL += "    COMMIT WRITE BATCH NOWAIT;\n"
        PLSQL += "end;"
        Cur.execute(PLSQL)
        Time_Log_Writing_PLSQL(PLSQL_Each_Time_start, len(UPDATE_SQL_TEXT))      
        #テーブル単位でのコミット後にstring配列を初期化
        UPDATE_SQL_TEXT.clear()    
    except Exception as e:
        ErrorLog_file_exp(e)
        ERROR_count += 1
        UPDATE_SQL_TEXT.clear()
        pass

#Main
if __name__ == '__main__':   
    #GUIモジュールのtkinterを定義
    root = tk.Tk()
    #必要なファイル検索(DB, SQL)
    DBAccessFile_check()
    SQL_Where_Check()
    #絞り込み条件読み込み
    where_sql_load()     
    #LOGファイルチェック
    LOG_File_Check()
    #メニューバー定義
    menubar = tk.Menu(root)
    # File Menu
    filemenu = tk.Menu(menubar, tearoff=0)    
    filemenu.add_command(label="ORACLE 接続先設定", command=Oracle_Access)
    filemenu.add_command(label="条件SQLフォルダー名設定", command=Where_Directory)
    filemenu.add_separator()
    filemenu.add_command(label="Exit", command=Exit)
    # Help
    helpmenu = tk.Menu(menubar, tearoff=0)
    helpmenu.add_command(label="バージョン情報etc", command=ver_help)
    # Add
    menubar.add_cascade(label="Menu", menu=filemenu)
    menubar.add_cascade(label="Help", menu=helpmenu)
    #ローカル変数定義
    stop_flag = False
    thread = None
    row = 0
    #スタートボタン関数
    def start_btn():
        global stop_flag, thread, Time_start, row, UPDATE_count, NULL_Count, ERROR_count, Elapsed_time, Primary_Mode
        button1.configure(state=tk.DISABLED)
        #プライマルキーを取得する可否のチェック(取得する場合はSQLファイルのチェック)
        Primary_Mode = Primary_chk.get()
        if Primary_Mode == True:
            Primary_Key_Get_SQL_text()
        # スレッドが無いなら生成してstart()する
        if not thread:             
            #列数
            row = get_count_chenges(Primary_chk.get())
            label2["text"] = " / " + str(row)
            flag = msg.askyesno("確認", "条件に一致するカラム数が「" + str(row) + "」列在ります。\n実行しますか?")
            if flag == True:                
                UPDATE_count = 0
                NULL_Count = 0
                ERROR_count = 0
                Elapsed_time = None
                #Logファイルに現在の日時を記載
                Log_file_exp("----- Start ----- " + str(datetime.datetime.now()))
                ErrorLog_file_exp("----- Start ----- " + str(datetime.datetime.now()))
                #処理時間計測開始
                Time_start = time.time()
                thread = threading.Thread(target=repase_sql)
                stop_flag=False
                thread.start()
            else:
                button1.configure(state=tk.NORMAL)
    #ストップボタン関数
    def stop_btn():
        global stop_flag, thread
        # スレッドがある場合確認後停止フラグを立てる
        if thread:
            flag = msg.askyesno("確認", "停止してもよろしいですか?\n(停止処理が終わるまでは[start]ボタンは押下できません)")
            if flag == True:
                stop_flag=True            
    #UPDATE処理
    def repase_sql():
        global UPDATE_count, stop_flag, thread, Elapsed_time, NULL_Count, Primary_List, Primary_Mode
        ms_txt = ""
        ms_title = ""
        try:            
            #DB接続変数
            Con = connect()
            Cur = sql_cursor(Con)            
            Table_Name_Save = ""            
            #NAME(主にVarchar2)         
            if stop_flag == False:  
                Table_Name_Save = ""                           
                if len(NSQL_txt) != 0:
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time()
                    #初回テーブルタイム計測開始
                    Table_Each_Time_start = time.time()
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(NSQL_txt), Con)
                    for r in db_rows.values:  
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                           
                            break        
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始                            
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time()
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0])
                        check_columns_row = pd.read_sql(Check_Column_str(r[0], r[1]), Con)
                        if len(check_columns_row) == 0:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], txt_up_sql)                                
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count
                        else:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], han_up_sql)
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count   
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "NAME条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #KANA(必要に応じて)                                         
            if stop_flag == False:   
                Table_Name_Save = ""                         
                if len(KSQL_txt) != 0:
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time()
                    #初回テーブルタイム計測開始
                    Table_Each_Time_start = time.time()
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(KSQL_txt), Con)
                    for r in db_rows.values:   
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                                                         
                            break
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time() 
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0])
                        check_columns_row = pd.read_sql(Check_Column_str(r[0], r[1]), Con)
                        if len(check_columns_row) == 0:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], txt_up_sql)
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count
                        else:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], han_up_sql)
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count     
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "KANA条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #ADDR(住所(基本NAME部分で処理))  
            if stop_flag == False: 
                Table_Name_Save = ""
                if len(ASQL_txt) != 0:
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time()
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(ASQL_txt), Con)
                    for r in db_rows.values:    
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                                                         
                            break
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time()            
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0]) 
                        check_columns_row = pd.read_sql(Check_Column_str(r[0], r[1]), Con)
                        if len(check_columns_row) == 0:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], txt_up_sql)
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count
                        else:
                            #処理テーブルラベル更新
                            labelName["text"] = str(r[0]) + " / " + str(r[1])                              
                            #1列UPDATE文作成
                            PLSQL_UPDATE(r[0], r[1], han_up_sql)
                            #実行回数カウントを+1                                        
                            UPDATE_count += 1   
                            label1["text"] = UPDATE_count
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "ADDR条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #POST(郵便番号(Varchar2やNum型等バラバラなのでその都度調整必要か?), ハイフンがあると面倒)        
            if stop_flag == False:
                Table_Name_Save = ""
                if len(PSQL_txt) != 0:      
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time()                  
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(PSQL_txt), Con)
                    for r in db_rows.values:  
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                                                         
                            break
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #Commit_Process(Con) 
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time()                    
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0]) 
                        #処理テーブルラベル更新
                        labelName["text"] = str(r[0]) + " / " + str(r[1])                                   
                        #1列UPDATE文作成
                        PLSQL_UPDATE(r[0], r[1], num_up_sql)
                        #実行回数カウントを+1                                        
                        UPDATE_count += 1   
                        label1["text"] = UPDATE_count
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "POST条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #TEL(電話番号(Varchar2やNum型等バラバラなのでその都度調整必要か?), ハイフンがあると面倒)             
            if stop_flag == False:   
                Table_Name_Save = ""
                if len(TSQL_txt) != 0:
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time() 
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(TSQL_txt), Con)
                    for r in db_rows.values:   
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                                                         
                            break                        
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time()                 
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0])
                        #処理テーブルラベル更新
                        labelName["text"] = str(r[0]) + " / " + str(r[1])                                   
                        #1列UPDATE文作成
                        PLSQL_UPDATE(r[0], r[1], num_up_sql)
                        #実行回数カウントを+1                                        
                        UPDATE_count += 1   
                        label1["text"] = UPDATE_count
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "TEL条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #Mail(メールアドレス, 必要に応じて)          
            if stop_flag == False:
                Table_Name_Save = ""
                if len(MSQL_txt) != 0:
                    #各条件ごとの時間計測開始
                    Each_Time_start = time.time() 
                    #条件を追加して更に絞り込み
                    db_rows = pd.read_sql(WHERE_SQL_CREATE(MSQL_txt), Con)
                    for r in db_rows.values:   
                        if stop_flag == True: 
                            Elapsed_time = time.time() - Time_start                                                                                         
                            break
                        #空文字(初回)以外で前回処理のテーブル名と相違した場合にコミット処理               
                        if Table_Name_Save != str(r[0]) and not Table_Name_Save == "":
                            PLSQL_Change_SQL(Cur)
                            #各テーブル終了時間のログ出力と次テーブルタイム計測開始
                            Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                            Table_Each_Time_start = time.time()           
                        #現在の処理テーブルを保持
                        Table_Name_Save = str(r[0])
                        #処理テーブルラベル更新
                        labelName["text"] = str(r[0]) + " / " + str(r[1])                                 
                        #1列UPDATE文作成
                        PLSQL_UPDATE(r[0], r[1], han_up_sql)
                        #実行回数カウントを+1                                        
                        UPDATE_count += 1   
                        label1["text"] = UPDATE_count
                    #for文終了時の最後のテーブルをコミット
                    PLSQL_Change_SQL(Cur)
                    #最終テーブルタイムログ
                    Time_Log_Writing(Table_Name_Save, Table_Each_Time_start)
                    #大本条件タイムログ
                    Each_Elapsed_time = time.time() - Each_Time_start
                    EET = datetime.timedelta(seconds = Each_Elapsed_time)
                    txt = "Mail条件終了時間「" + str(EET) + "」"
                    Log_file_exp(txt)
            #終了             
            if not Elapsed_time:
                if ERROR_count == 0:
                    Elapsed_time = time.time() - Time_start
                    td = datetime.timedelta(seconds = Elapsed_time)  
                    ms_txt = "対象DB+スキーマ「" + str(label["text"]) + "」\n実行した総件数「" + str(UPDATE_count) + "」です。"
                    ms_txt += "\nNull列は「" + str(NULL_Count) + "」でした。\n初期総件数 - Null列 = [" + str(row - NULL_Count) + "] \n処理時間は「" + str(td) + "」秒でした。" 
                    Log_file_exp(ms_txt)  
                    button1.configure(state=tk.NORMAL)
                    ms_title = "正常終了"
                else:
                    Elapsed_time = time.time() - Time_start
                    td = datetime.timedelta(seconds = Elapsed_time)  
                    ms_txt = "対象DB+スキーマ「" + str(label["text"]) + "」\n実行した総件数「" + str(UPDATE_count) + "」です。"
                    ms_txt += "\nNull列は「" + str(NULL_Count) + "」でした。\n初期総件数 - Null列 = [" + str(row - NULL_Count) + "] \n処理時間は「" + str(td) + "」秒でした。" 
                    ms_txt += "\nなおエラー件数は「" + str(ERROR_count) + "」件です。詳しくはエラーログファイルを参照してください。"
                    Log_file_exp(ms_txt)  
                    button1.configure(state=tk.NORMAL)
                    ms_title = "終了"
            else:       
                td = datetime.timedelta(seconds = Elapsed_time)  
                ms_txt = "対象DB+スキーマ「" + str(label["text"]) + "」\n実行した総件数「" + str(UPDATE_count) + "」です。\n処理時間は「" + str(td) + "」秒でした。"                
                Log_file_exp(ms_txt) 
                button1.configure(state=tk.NORMAL)
                ms_title = "キャンセル終了"
        except Exception as e:
            Elapsed_time = time.time() - Time_start
            td = datetime.timedelta(seconds = Elapsed_time)  
            ms_txt = "対象DB+スキーマ「" + str(label["text"]) + "」\n実行した総件数「" + str(UPDATE_count) + "」です。\n処理時間は「" + str(td) + "」秒でした。"
            Log_file_exp(ms_txt) 
            Log_file_exp(e)
            ms_txt = str(e)
            ms_title = "例外エラー"         
        finally:
            thread = None
            Primary_List = None
            Cur.close()
            Con.close()            
            Log_file_exp("----- End ----- " + str(datetime.datetime.now()))
            ErrorLog_file_exp("----- End ----- " + str(datetime.datetime.now()))
            msg.showinfo(ms_title, ms_txt)
    
    root.config(menu=menubar)

    #root.withdraw()
    root.title("Oracle DB マスクプログラム")
    root.minsize(450, 120)
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)
    root.grid()
    #button配置
    frame1 = ttk.Frame(root, padding=(0,5))
    frame1.grid(row=0, column=0, sticky=tk.W+tk.N)
    button1 = ttk.Button(frame1, text="Start", command=start_btn)
    button1.pack(side=tk.LEFT)
    button2 = ttk.Button(frame1, text="Cancel",command=stop_btn)
    button2.pack(side=tk.LEFT)
    #プライマルキー使用チェックボックスの表示
    Primary_chk = BooleanVar()
    Primary_chk_box = tkinter.Checkbutton(variable=Primary_chk, text=u"Primary Key Mode(ON/OFF)")
    Primary_chk_box.grid(row=0, column=0, sticky=tk.E)
    #接続先名ラベル配置
    frame2 = ttk.Frame(root, padding=(0,5))
    frame2.grid(row=1, column=0, sticky=tk.W+tk.N)
    label = ttk.Label(frame2, text = "接続先ユーザ名 + スキーマ名", anchor='w', padding = (0,5))
    label.pack(side=tk.LEFT)
    #現在処理中テーブル情報ラベル配置
    frame3 = ttk.Frame(root, padding=(0,5))
    frame3.grid(row=2, column=0, sticky=tk.W+tk.N)
    labelName = ttk.Label(frame3, text = Table_Name, anchor='w', padding = (5,5))
    labelName.pack(side=tk.LEFT)
    #処理カウント配置
    frame4 = ttk.Frame(root, padding=(0,5))
    frame4.grid(row=2, column=2, sticky=tk.W+tk.N)
    label2 = ttk.Label(frame4, text = " / " + str(row), anchor='e', padding = (5,5))
    label2.pack(side=tk.RIGHT)
    label1 = ttk.Label(frame4, text = str(UPDATE_count), anchor='e', padding = (5,5))
    label1.pack(side=tk.RIGHT) 
                            
    root.mainloop()     
    # 終了時にスレッドを停止する処理
    if thread:
        stop_flag=True
        thread.join()

非営利目的なら編集等、自由に使用して頂いてもかまいませんがここのリンク位は貼っておいてほしいかな?

使用方法
VScodeやbatから起動した際、[Oracle_DB_masking.py]が存在するディレクトリを基準に設定ファイルやベースフォルダが作成されます。

↑背景と同化しててちょっと見づらいですが起動直後の画面です。

↑基本的に変更する「Menu」タブ押下後です。

Oracle接続先設定ボタンを押下すると下記画面が表示されます。

Oracle Net Configで設定するように
TARGET_HOST = ホスト名(接続先ホスト名 or IPAddress)
PORT = DBのポート番号(デフォルトは1521だが鯖によって違うので適切な値を入れてね)
SERVICE_NAME = サービス名(Oracle_SID)
SCHEME_NAME = スキーマ名(ユーザ名)
PASSWORD = スキーマのパスワード
上記5つの情報を入力後OKボタンを押下すると保存しましたとダイアログが表示され、
.pyと同じディレクトリの「Dbaccess.json」に上書きされます。


上記は初回起動時のデフォルト値が入っていますが.pyと同ディレクトリに上記名前のフォルダが作成されていると思います。
各DB名やスキーマ名を入れて条件フォルダを分けておくことをお勧めします。
そして上記条件フォルダが作成されると同時に下記ファイル群も存在しない場合は自動で作成されます。

DBの構成にもよりますがしっかりとテーブルのデータ型等が定義してあるDBなら[NAME.sql],[KANA.sql],[TEL.sql]の3つでほぼ条件が指定できると思います。
例として[NAME.sql]ファイルに記載してある内容を乗せておきます

DATA_TYPE = 'NVARCHAR2'
DATA_LENGTH >= 10
TABLE_NAME NOT LIKE 'SYS%'
NULLABLE = 'Y'
LAST_ANALYZED IS NOT NULL
COLUMN_NAME <> 'OPR_PASSWD'
COLUMN_NAME NOT LIKE '%YUBIN%'
COLUMN_NAME NOT LIKE '%TEL%'
COLUMN_NAME NOT LIKE '%FAX%'
TABLE_NAME <> 'XXXX_OPRPA'
TABLE_NAME <> 'XXXX_MENU'
COLUMN_NAME <> 'LOG_PROGRAMNAME'
(COLUMN_NAME LIKE '%NAME%' OR COLUMN_NAME LIKE '%KANA%' OR COLUMN_NAME LIKE '%MEIGI%' OR COLUMN_NAME LIKE '%ADR%' OR COLUMN_NAME LIKE '%KANJI%' OR COLUMN_NAME LIKE '%MEMO%' OR COLUMN_NAME LIKE '%KNJ%' OR COLUMN_NAME LIKE '%KINMUSAKI%' OR COLUMN_NAME LIKE '%KENSK')
COLUMN_NAME <> 'XXXXX_SKANA'
TABLE_NAME <> 'XXXX_RIK'
TABLE_NAME <> 'XXXX_SYS'
COLUMN_NAME <> 'XXXXXXXX_KAISNAME'
COLUMN_NAME <> 'XXXXXXXX_KAIBNAME'
COLUMN_NAME <> 'XXXXXXXX_FKSNAME'
COLUMN_NAME <> 'XXXXXXXX_FKBNAME'
COLUMN_NAME <> 'XXX_BKANA'

SQL文のWHERE条件を1行づつ記載します。

Base_SQL_Text = "SELECT TABLE_NAME,COLUMN_NAME,COLUMN_ID,DATA_TYPE,DATA_LENGTH,NULLABLE FROM USER_TAB_COLUMNS "
ORDER_BY = "ORDER BY TABLE_NAME"

PG内の上記ベースSelect文と合わせてマスク対象のカラム名とテーブル名を取得するのが目的です。
入力された値は[ def WHERE_SQL_CREATE ]メソッドで改行ごとにstring Listに変換された条件を組み合わせマスク対象のカラムを絞り込んでいます。
SI Object Browser等で確認しながらカラムを絞っていくと楽だと思います。

プライマルキーモードのチェックボックスがあると思いますが、この機能は[ NOT NULL ]が設定されていて
尚且つ電話番号等の個人情報が含まれていた場合に使用しますがプライマルキー以外のカラムすべてが対象になるので
SQLで対象のカラムを絞る作業がかなり大変になります。(Not Nullに電話番号なんか入れる設計がそもそも・・・大変だった)

SELECT T1.TABLE_NAME,T1.COLUMN_NAME FROM USER_CONS_COLUMNS T1
WHERE T1.CONSTRAINT_NAME IN(SELECT T2.CONSTRAINT_NAME FROM USER_CONSTRAINTS T2
WHERE T2.TABLE_NAME IN(SELECT T3.TABLE_NAME FROM USER_TABLES T3 WHERE T3.STATUS = 'VALID') AND T2.CONSTRAINT_TYPE = 'P')
ORDER BY T1.TABLE_NAME,T1.POSITION

この機能を使用する場合は上記SQLを[ primary_Key_Get_SQL.sql ]と名前を付け同ディレクトリに保存しておくことでキーのみ取得し、対象外に設定されます。

スタートボタン押下後は「対象カラム数」が表示され実行確認のダイアログが表示されます。
キャンセルボタンは現在の処理までで終了するボタンであり、ロールバックはしませんのでご注意ください。
(尚、PLSQL実行中の場合はその処理が終了するまでは終了ダイアログも表示されません)

#PLSQL生成ロジック(テーブル単位で作成されている) #キャッチ後該当列のUPDATEを無視して続行するために関数を分ける
def PLSQL_Change_SQL(Cur):
    global ERROR_count, UPDATE_SQL_TEXT
    try:
        PLSQL_Each_Time_start = time.time()
        PLSQL = "begin \n"
        for strList in UPDATE_SQL_TEXT:
            PLSQL += "    " + strList + ";\n"
        PLSQL += "    COMMIT WRITE BATCH NOWAIT;\n"
        PLSQL += "end;"
        Cur.execute(PLSQL)
        Time_Log_Writing_PLSQL(PLSQL_Each_Time_start, len(UPDATE_SQL_TEXT))      
        #テーブル単位でのコミット後にstring配列を初期化
        UPDATE_SQL_TEXT.clear()    
    except Exception as e:
        ErrorLog_file_exp(e)
        ERROR_count += 1
        UPDATE_SQL_TEXT.clear()
        pass

PLSQLの生成は[ ORDER BY TABLE_NAME ]としてあるようにテーブル単位で作成、実行されます。

目安となる時間ですが約5GBのDMPファイルですと、約40分程です。
(Oracle REDOバッファサイズやREDOログファイルサイズでかなり時間が変化しますがチューニングの話は後日で)

一応途中まで書いた説明書のExcelファイルを置いておきますが、
面倒になりあくまで途中ですのでご了承ください → マスクプログラム(.py)_説明書

置き換え文字は前から2文字、3・4文字目はそのままで後ろに2文字○を追加しています。
元々は置き換え行の長さで行っていましたがキャラクタセットが[ UTF-8 ]等の文字になっているとサイズエラーになりマスクされなかったので止めました。

ログファイルをエラーキャッチ時とPLSQL実行時等のタイミングで吐くようにしてます。
(詳しくはソース見るか一番下に置いてあるzipの中のログフォルダ見てね)

最後に
恐らくデフォルトのDBパラメータですと5GBものDMPファイルをマスクする場合、
HDDにDBがインストールされており、[ REDOバッファ ], [ REDOログ ]のさいずを変更していない場合
約3時間~5時間かかると思います。(コードやコミットタイミングを変更しても全く早くならなかった・・・)
REDOバッファサイズは公式ドキュメントにもあるようにREDOログ生成頻度が高い場合(UPDATE等の処理頻度と量)は
最低256MB(デフォルト8MBくらい)にしましょう。(2Gくらいあるとオブジェクトブラウザのメモリの空き待機カウントが増えないと思います)
REDOログファイルは調べれば結構出てきますがとりあえずDMPファイルの5倍くらいにしとくとマスク速度が段違いです。

 

ソースやサンプル等含めたzipファイルを置いておきますので好きに編集してください。(再配布はやめてね!)
Zipファイル → Oracle_DB_masking_ver3.0

コメント

タイトルとURLをコピーしました