入力されたデータ保持とシステムの連携（超初心者用）

#初めに
この記事は、これまでに自身で作成したシステムに入力されたデータを保存するためには、どのような方法があるのだろうというところから始まり、簡単なDB作成やデータ保存処理まで、DBの基礎中の基礎と思われるところをメモとして記事に残します。
SQLやDBの操作がまだまだ、知識不足なので、このサイト見たら知識増えるよだったり実践的に実力をつけるにはこうしたら良いということがありましたら、是非教えてください！！

#実現したいこと
まず、なにを実現したかったかというと「正常な手順（リッチメニューで機能を選択して、選択肢を選ぶ）で目的の情報を送信する」ということを完成させる予定でした。（下図参考）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701238/76ed2101-0740-ecbd-4316-64b10e41ef57.png)

しかし、実際に完成したものは「どのような状態でも、プログラム側で設定していたキーワードを送信すると特定の処理が行われるようになっていた。」というものでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701238/892262bb-eb1c-577d-a74c-db8a31d2a0f0.png)


特にこれと言って問題はなさそうと思ったのですが手順すっ飛ばしてしまうのはどうかなと思い、入力されたデータの保存を行い、１つ前の手順が完了されていなければそれ以降の手順は実行されないようにしようと決意しました。

#データの保存方法について
結果からいうと、sqlite3を使用してDBで管理をすることにしました。
テーブルの内容としては、「ユーザのID」、「地方」、「地域」、「市区町村」の４つを列としたものを作成しました。
それぞれSTRING型となっています。
下図のタイミングでDBの読み書きを行っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701238/6797dd0e-b3ee-6dbc-1812-5c9ccc226fb4.png)

```python
# user_idは処理を要求してきたユーザのID
# table_nameは処理の初めに定数宣言で指定する
# CREATE TABLE persons(user_id STRING PRIMARY KEY, area STRING, rural STRING, municipality STRING);でテーブル作成
def create_data(user_id, table_name):
    dbname = 'weatherDatabase.db'
    conn = sqlite3.connect(dbname)
    # sqliteを操作するカーソルオブジェクトを作成
    cur = conn.cursor()
    # IDが登録されていなければtry文、登録されていればレコードを削除して再度登録
    try:
        if table_name == "weather_input":
            cur.execute('INSERT INTO weather_input(user_id) values(?);', (user_id,))
    except sqlite3.IntegrityError:
        if table_name == "weather_input":
            cur.execute('DELETE FROM weather_input WHERE user_id = ?;', (user_id,))
            cur.execute('INSERT INTO weather_input(user_id) values(?);', (user_id,))
    finally:
        # データベースへコミット。これで変更が反映される。
        conn.commit()
        conn.close()


# user_idは処理を要求してきたユーザのID
# table_nameは処理の初めに定数宣言で指定する
def exist_record(user_id, table_name, column=""):
    dbname = 'weatherDatabase.db'
    conn = sqlite3.connect(dbname)
    # sqliteを操作するカーソルオブジェクトを作成
    cur = conn.cursor()

    try:
        if table_name == "weather_input":
            if column != "":
                if column == "area":
                    cur.execute('SELECT * FROM weather_input WHERE user_id = ? and area IS NOT NULL;', (user_id,))
                if column == "rural":
                    cur.execute('SELECT * FROM weather_input WHERE user_id = ? and rural IS NOT NULL;', (user_id,))
            else:
                cur.execute('SELECT * FROM weather_input WHERE user_id = ?;', (user_id,))
        # データがあるかどうか判定
        if cur.fetchone()[0] != 0:
            return True
        else:
            return False
    except TypeError:
        return False
    finally:
        conn.close()


# user_idは処理を要求してきたユーザのID
# table_nameは呼び出し元で指定する
# dataはユーザIDと一致したレコードを更新する
# column更新するカラム
def update_data(user_id, table_name, data, column):
    dbname = 'weatherDatabase.db'
    conn = sqlite3.connect(dbname)
    # sqliteを操作するカーソルオブジェクトを作成
    cur = conn.cursor()

    try:
        if table_name == "weather_input":
            if column == "area":
                cur.execute('UPDATE weather_input SET area = ? WHERE user_id = ?;', (data, user_id))
            elif column == "rural":
                cur.execute('UPDATE weather_input SET rural = ? WHERE user_id = ?;', (data, user_id))
            elif column == "municipality":
                cur.execute('UPDATE weather_input SET municipality = ? WHERE user_id = ?;', (data, user_id))

    finally:
        # データベースへコミット。これで変更が反映される。
        conn.commit()
        conn.close()


def delete_record(user_id, table_name):
    dbname = 'weatherDatabase.db'
    conn = sqlite3.connect(dbname)
    # sqliteを操作するカーソルオブジェクトを作成
    cur = conn.cursor()

    try:
        if table_name == "weather_input":
            cur.execute('DELETE FROM weather_input WHERE user_id = ?;', (user_id, ))
    finally:
        # データベースへコミット。これで変更が反映される。
        conn.commit()
        conn.close()
```

[以前のconnectDB.py](https://qiita.com/ryo0819/items/b6ae6ded6d6e037af870)のファイルに追加した内容が上記の通りです。普段DBをいじることがないのでなんともおざなりなメソッドを書き連ねてしまいましたが、一応例外処理などは適切に行えたのではないかなと考えています。
（finalllyだけでなくexceptも入れた方が良さそうな気もします、、、）

このメソッド達をmain処理でユーザから入力を受けた時に、レコードの存在有無をif文判定して分岐することで当初の目標を実現することに成功しました。

#おわりに
いまさらDB作成するのかよ、、という感じですが折れずに調べながら達成することができました。
SQLインジェクションの対策には「？」を使った方が良いという情報や「％s」の方が良いという情報もあり、混乱しましたがとりあえず自分は「？」で統一しました。（公式ドキュメントとか見たら情報あるかも？）

幸いにもこれから実装しようとしている乗り換え案内の入力データ保存にも使えるかもしれないということに気づき、遠回りしたけれど良い収穫がありました。

ではまた次回。