Filemaker to Python (5) - Updateを試す

Select, InsertときたらUpdateも試しておこうということで、コードを書いてみましたというお話です。

上書きのルール

既存のレコードをUpdateするにあたって、何かしらのルールを決めておく事は重要だと思いました。例えば単語のDBを例にすると、単語そのものというのは基本的に上書きしてはダメなモノの考える事が出来るし、単語に新たな意味を追加したいのであれば、内容を比較してこれまでにない内容であれば既存の内容に追記、または問答無用に上書きした方がよい項目もあります。なので、ちょっと面倒なのですが、classのコンストラクタ(__init__)にちょいと手を加えてみました。

class AccessCWordFM:
    def __init__(self):
        self.DSN = 'DSN=CWord;UID=xxxxxx;PWD=xxxxxxxxxxx'
        self.TabName = 'CWordsDB'
        self.FieldList = ['WordID', 'Word', 'Pinyin', 'POS', 'Meaning', 'Source', 'Status', 'Batch']
        self.FieldStatus = ['NC', 'NC', 'CP', 'CP', 'CP' , 'AO', 'AO', 'AO']
        self.FieldLen = len(self.FieldList)
        
        self.cxcn = pyodbc.connect(self.DSN)
        self.cursor = self.cxcn.cursor()

self.FieldListという中に、Filemaker側のテーブルの項目名が入っていて、さらにself.FieldStatusというリストを追加して、各項目に対して上書き禁止(NC)、内容比較の上で判断(CP)、問答無用に上書き(AO)を定めてみました。

新規追加か既存レコード更新か

あるデータが受け渡されて、既存のレコードがあればそれを更新、なければ新規追加というのは、通常のアプリケーションでもよくある考え方だと思います。ということで、条件分岐のためのコードをこんな感じで書いてみました。

    def insert_cword(self, cwdata, test=False):
        if cwdata[0] == '':
            print 'Error: Either Word is missing'
        elif len(cwdata) != self.FieldLen - 1:
            print 'Error: Number of elements are not match with the database'
        else:
            # Check if the word is already registered
            self.get_cwords(cwdata[0], cwdata[1])
            if len(self.rows) == 0:
                #Add new entry to CWordDB
                self.add_new_cword(cwdata, test)
            elif len(self.rows) >= 1:
                #Modify existing record
                wid = int(self.rows[0][0])
                self.modify_record(wid, cwdata, test)

とりあえず、最低限の項目だけ検索して既存のレコードありなしを判定させ、レコードがなければレコード追加のためのFunctionを呼んで、レコードがある場合は更新のためのFunctionを呼びます。

Updateをかける

少し長いのですが、更新のためのロジックはこんな感じ。いくつかに分けて見ていきましょう。

    def modify_record(self, wid, cwdata, test=False):
        self.get_single_fullrecord(wid)
        NewRecord = self.rows[0]        #NewRecord includes WordID
        for i in range(self.FieldLen):
            if self.FieldStatus[i] == 'AO':
                NewRecord[i] = cwdata[i - 1]
            if self.FieldStatus[i] == 'CP':
                if NewRecord[i] == '':
                    NewRecord[i] = cwdata[i -1]
                elif NewRecord[i].find(cwdata[i -1]) == -1:
                    NewRecord[i] = NewRecord[i] + ', ' + cwdata[i -1]
                else:
                    pass

まずは既存レコードの全項目内容を取得(別function)して、NewRecordというリストに放り込んでいます。そこから、さっきコンストラクタの中で問答無用で上書き(AO)になっているものは、そのまま項目内容を上書きしています。さらに、比較(CP)となったものについては、元の値がブランクだったら上書き、何か入っていれば新しい値と比較して、重複がなければ元の内容に追記するという処理を各項目に対して繰り返しています。

        sqlString = []
        sqlString.append('update')
        sqlString.append(self.TabName)
        sqlString.append('set')
        for i in range(self.FieldLen):
            if self.FieldStatus[i] == 'AO' or self.FieldStatus[i] =='CP':
                if sqlString[-1] != 'set':
                    sqlString.append(',')
                sqlString.append(self.FieldList[i])
                sqlString.append('=')
                sqlString.append("'" + NewRecord[i] + "'")
        sqlString.append('where')
        sqlString.append(self.FieldList[0])
        sqlString.append('=')
        sqlString.append(str(wid))

        #commit
        self.call_commit(sqlString, test)

残りの部分については、これまでのInsertなどと同じでsqlStringというリストにSQL文の元となる要素をAppendで追加しています。1つだけ注意点は、FilemakerのテーブルにWordIDという数字が入る項目があるのですが、あとで" ".join()する関係で文字列として受け渡す必要があるので、str(wid)という表記になっています。しかし、実際のところは数字が入る項目なので(')で囲ってやる必要はありません。最後のCommit部分は外出しにしました。

最後にCommitをお忘れなく

これがCommit用のfunctionです。

    def call_commit(self, sqlString, test=False):
        sqlCommand = " ".join(sqlString)
        if test == True:
            print sqlCommand
        self.cursor.execute(sqlCommand)
        if test == False:
            self.cursor.commit()
            print 'Record(s) updated with commit work'

sqlStringというリストの要素を" ".join()で文字列として生成してやって、あとはSQLのコマンドをODBC側に投げています。一応テストモードもつけてあるのは、生成されたSQL文がきちんと確認するオプションがあった方が便利だと思ったからです。

いちおうまとめ

ということで、誰の役に立つのか分かりませんが、PythonからODBC経由でFilemakerのデータベースにアクセスしてみるという一応の目的は達成しました。自分の持っている6000件弱の全件検索に2-3秒というところからして、たかだか数千件から1万件前後のデータ量であれば十分使えるのではないかなと感じがします。

また興味のありそうなネタがあったら試してみようと思います。