Pythonでデータベースを操作しよう
このハンズオンでは、PythonからMySQLデータベースに接続し、データベースやテーブルの作成からデータの操作までを実際にハンズオン形式で手を動かしながら体験します。
- PythonからMySQLに接続する方法
- Pythonからデータベースとテーブルを作成する方法
- 接続文字列(ホスト、ポート、ユーザ名、パスワード、データベース名)の指定
- SELECT文によるデータの抽出
- INSERT / UPDATE / DELETE文によるデータの更新
- エラー処理の実装
- リソースの解放(
with文の活用) - パラメータ化クエリによるSQLインジェクション対策
1. 事前準備
このハンズオンでは、以下のツールが必要です。まだ準備できていない場合は、リンク先の手順に沿って準備をお願いします。
2. データ操作の流れ
PythonからMySQLを操作する基本的な流れは、以下の4つのステップで構成されます。
flowchart LR
A[接続<br>connect] --> B[カーソルの取得<br>cursor]
B --> C[SQLの実行<br>execute]
C --> D[リソースの解放<br>close]
まず、Pythonプログラムからデータベースに接続します。接続にはホスト名、ポート番号、ユーザ名、パスワードといった情報を指定します。接続が成功すると、コネクションと呼ばれるオブジェクトが返却されます。
次に、コネクションからカーソルを取得します。カーソルは、SQLを実行し結果を受け取るためのオブジェクトです。
カーソルを取得したら、実際にSQLを実行します。SELECT文でデータを取得したり、INSERT文でデータを追加したりといった操作は、すべてこのカーソルを通じて行います。
最後に、使い終わったカーソルとコネクションを閉じてリソースを解放します。リソースの解放を忘れると、接続が残り続けてデータベースサーバに負荷がかかる原因となります。
3. プロジェクトの準備
3.1 ライブラリのインストール
PythonからMySQLに接続するために、mysql-connector-pythonというライブラリをインストールします。お使いのOSに合わせて下記コマンドにてライブラリをインストールしてください。
Windowsの場合:
pip install mysql-connector-python
Macの場合:
pip3 install mysql-connector-python
以下のようなメッセージが表示され、インストールが完了します。
Successfully installed mysql-connector-python-x.x.x
| 📝 mysql-connector-pythonとは |
|---|
mysql-connector-pythonは、Oracle社が公式に提供しているPython用のMySQLドライバです。PythonからMySQLに接続し、SQLを実行するための機能を提供します。同様のライブラリとしてPyMySQLもありますが、本講座では公式ドライバであるmysql-connector-pythonを使用します。 |
| 💡 ポイント |
|---|
Pythonからデータベースに接続するには、データベースエンジンごとに対応するドライバ(ライブラリ)が必要です。本講座ではMySQLを使用するためmysql-connector-pythonをインストールしていますが、他のデータベースを使用する場合はそれぞれ専用のドライバをインストールします。例えば、PostgreSQLの場合はpsycopg2、Oracle Databaseの場合はoracledbを使用します。ドライバは異なりますが、接続してSQLを実行するという基本的な流れはどのデータベースでも共通です。 |
3.2 フォルダとファイルの作成
まず、任意の場所にdatabase_testフォルダを作成します。
database_test/ ← このフォルダを作成
作成したフォルダをVisual Studio Codeで開きます。Visual Studio Codeのメニューから「ファイル」→「フォルダーを開く」を選択し、作成したフォルダを開いてください。
main.pyの作成
main.pyを作成します。Visual Studio Codeのエクスプローラーでdatabase_testフォルダを右クリックし、「新しいファイル」を選択してmain.pyという名前で作成してください。
database_test/
└── main.py ← このファイルを作成
4. データベースに接続する
まずは、PythonからMySQLに接続する方法を見ていきます。
mysql.connector.connect()メソッドに接続情報を指定して、MySQLへの接続を行います。基本的な構文は以下のとおりです。
import mysql.connector
connection = mysql.connector.connect(
host="ホスト名",
port=ポート番号,
user="ユーザ名",
password="パスワード"
)
実際に試してみましょう。作成したmain.pyに以下の内容を記述して保存します。
import mysql.connector
# MySQLに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password"
)
print("MySQLに接続しました")
# 接続を閉じる
connection.close()
print("接続を閉じました")
接続に成功すると、データベース操作に必要なコネクションオブジェクトが返却されます。
| 💡 ポイント |
|---|
passwordには、MySQLのインストール時に設定したrootパスワードを指定してください。本講座のコード例ではyour_passwordと記載していますが、ご自身のパスワードに置き換えてください。 |
4.1 接続パラメータ
接続時に指定するパラメータの意味は以下のとおりです。
| パラメータ | 説明 | 今回の値 |
|---|---|---|
host |
MySQLサーバのホスト名またはIPアドレス | localhost |
port |
MySQLサーバのポート番号 | 3306(MySQLのデフォルト) |
user |
MySQLに接続するユーザ名 | root |
password |
ユーザのパスワード | (ご自身のrootパスワード) |
| 💡 ポイント |
|---|
hostにlocalhostを指定すると、自分のPC上で動作しているMySQLに接続します。ローカルにインストールしたMySQLはデフォルトでポート3306で起動しているため、localhost:3306で接続できます。 |
4.2 動作確認
main.py を実行して、接続が成功することを確認します。以下の出力が表示されれば成功です。
MySQLに接続しました
接続を閉じました
mysql.connector.connect()による接続とconnection.close()による切断が正常に行われたことが確認できます。
5. データベースの作成
MySQLへの接続ができたので、次はPythonからデータベースを作成します。前の講座SQL基本文法ではMySQL CLIから直接SQLを実行しましたが、Pythonからも同様にSQLを実行してデータベースやテーブルを作成できます。
5.1 Pythonコードの修正
main.pyを以下の内容に書き換えます。
import mysql.connector
# MySQLに接続(データベースを指定しない)
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password"
)
cursor = connection.cursor()
# データベースの作成
cursor.execute("CREATE DATABASE testdb")
print("データベース testdb を作成しました")
cursor.close()
connection.close()
追加したコードを解説します。
cursor = connection.cursor()
connection.cursor()で、SQLを実行するためのカーソルオブジェクトを取得しています。カーソルは、前のセクションで説明したとおり、SQLの実行と結果の取得を担うオブジェクトです。
cursor.execute("CREATE DATABASE testdb")
cursor.execute()にCREATE DATABASE testdbというSQLを渡すことで、testdbという名前のデータベースを作成しています。このように、cursor.execute()にはSELECT文だけでなく、データベースやテーブルを操作するSQLも渡すことができます。
cursor.close()
connection.close()
処理が完了したら、cursor.close()でカーソルを、connection.close()でコネクションをそれぞれ閉じてリソースを解放しています。
5.2 Pythonの実行確認
main.py を実行します。
データベース testdb を作成しました
上記の出力が表示されれば、データベースの作成が正常に完了しています。
5.3 MySQLで確認
MySQLに接続して、データベースが作成されているか確認してみましょう。
mysql -u root -p -e "SHOW DATABASES;"
パスワードを入力すると、以下のようにデータベースの一覧が表示されます。testdbが含まれていれば成功です。
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| testdb |
+--------------------+
6. テーブルの作成
続いて、作成したデータベースにテーブルを作成します。
6.1 Pythonコードの修正
main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
cursor = connection.cursor()
# テーブルの作成
cursor.execute("""
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(10),
age INT
)
""")
print("テーブル users を作成しました")
cursor.close()
connection.close()
追加したコードを解説します。
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
前のセクションとの違いとして、database="testdb"パラメータが追加されています。先ほど作成したデータベースに接続するため、接続先のデータベース名を指定しています。
cursor.execute("""
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(10),
age INT
)
""")
cursor.execute()にCREATE TABLE文を渡して、usersテーブルを作成しています。idカラムにはAUTO_INCREMENTとPRIMARY KEYを指定しています。
| 📝 AUTO_INCREMENTとPRIMARY KEY |
|---|
AUTO_INCREMENTは、レコード挿入時に値を指定しなくても自動的に連番が割り振られる属性です。PRIMARY KEYはテーブルのキーとなるカラムで、他のレコードと重複しない一意の値であることを保証します。 |
6.2 Pythonの実行確認
main.py を実行します。
テーブル users を作成しました
上記の出力が表示されれば、テーブルの作成が正常に完了しています。
6.3 MySQLで確認
MySQLに接続して、テーブルが作成されているか確認してみましょう。
mysql -u root -p -e "SHOW TABLES FROM testdb;"
パスワードを入力すると、以下のようにテーブルの一覧が表示されます。usersが含まれていれば成功です。
+------------------+
| Tables_in_testdb |
+------------------+
| users |
+------------------+
6.4 INSERTの実行
続いて、作成したテーブルにテストデータを挿入します。
Pythonコードの修正
main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
cursor = connection.cursor()
# テストデータの挿入
cursor.execute("INSERT INTO users (name, age) VALUES ('Suzuki', 20)")
cursor.execute("INSERT INTO users (name, age) VALUES ('Tanaka', 30)")
cursor.execute("INSERT INTO users (name, age) VALUES ('Suzuki', 25)")
connection.commit()
print("テストデータを3件挿入しました")
cursor.close()
connection.close()
追加したコードを解説します。
cursor.execute("INSERT INTO users (name, age) VALUES ('Suzuki', 20)")
INSERT INTO文で、テーブルにレコードを挿入しています。同様のcursor.execute()を3回呼び出すことで、3件のテストデータを挿入しています。
connection.commit()
connection.commit()で、INSERT文による変更をデータベースに確定しています。INSERT、UPDATE、DELETEなどのデータを変更するSQLを実行した後は、commit()を呼び出さないと変更が反映されません。
Pythonの実行確認
main.py を実行します。
テストデータを3件挿入しました
上記の出力が表示されれば、テストデータの挿入が正常に完了しています。
MySQLで確認
MySQLに接続して、データが正しく作成されているか確認します。
mysql -u root -p -e "SELECT * FROM testdb.users;"
パスワードを入力すると、以下の結果が表示されます。
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | Suzuki | 20 |
| 2 | Tanaka | 30 |
| 3 | Suzuki | 25 |
+----+--------+------+
Pythonから実行したINSERTが正しく反映されていることが確認できます。
7. SELECTの実行(全件取得)
データベースとテーブルの準備ができたので、次はSELECT文を実行してレコードを取得します。まずはfetchall()を使って、全件を一括で取得する方法を学びます。SELECTを実行するには、コネクションからカーソルを取得し、execute()でSQLを実行した後、fetchall()で結果を取得します。
cursor = connection.cursor()
cursor.execute("SELECT文")
rows = cursor.fetchall()
7.1 Pythonコードの修正
実際に試してみましょう。main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
# カーソルを取得
cursor = connection.cursor()
# SELECTの実行
cursor.execute("SELECT id, name, age FROM users")
# 結果の取得
rows = cursor.fetchall()
# 結果を1件ずつ出力
for row in rows:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
# リソースの解放
cursor.close()
connection.close()
追加したコードを解説します。
cursor.execute("SELECT id, name, age FROM users")
cursor.execute()にSELECT文を渡して、usersテーブルからid、name、ageの3つのカラムを取得しています。
rows = cursor.fetchall()
cursor.fetchall()は、SQLの実行結果をすべてのレコードのリストとして一括で返却します。各レコードはタプル形式で格納されており、インデックスでカラムの値にアクセスできます。
for row in rows:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
fetchall()で取得したレコードのリストを、forループで1件ずつ出力しています。各レコードはタプル形式のため、row[0]でid、row[1]でname、row[2]でageの値にアクセスしています。
7.2 Pythonの実行確認
main.py を実行します。以下のように、usersテーブルの全レコードが表示されれば成功です。
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
8. SELECTの実行(1件ずつ取得)
前のセクションではfetchall()を使って全件を一括で取得しましたが、レコード件数が多い場合はメモリを大量に消費してしまう可能性があります。そのような場合は、fetchone()を使って1件ずつ取得する方法が有効です。
fetchone()は呼び出すたびに次の1件を返却し、すべてのレコードを取得し終えるとNoneを返します。この仕組みを利用して、whileループで1件ずつ処理できます。
cursor = connection.cursor()
cursor.execute("SELECT文")
row = cursor.fetchone()
while row is not None:
# rowを処理
row = cursor.fetchone()
8.1 Pythonコードの修正
実際に試してみましょう。main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
# カーソルを取得
cursor = connection.cursor()
# SELECTの実行
cursor.execute("SELECT id, name, age FROM users")
# 結果を1件ずつ取得
row = cursor.fetchone()
while row is not None:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
row = cursor.fetchone()
# リソースの解放
cursor.close()
connection.close()
追加したコードを解説します。
row = cursor.fetchone()
cursor.fetchone()は、SQLの実行結果から1件だけレコードを取得します。取得したレコードはfetchall()と同様にタプル形式です。
while row is not None:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
row = cursor.fetchone()
fetchone()はすべてのレコードを取得し終えるとNoneを返すため、while row is not Noneの条件でループを制御しています。ループの中で1件を出力した後、再度fetchone()を呼び出して次のレコードを取得しています。
| 💡 ポイント |
|---|
fetchall()はすべてのレコードを一度にメモリに読み込むため、少量のデータを扱う場合に適しています。一方、fetchone()は1件ずつ取得するため、大量のデータを扱う場合にメモリの消費を抑えることができます。用途に応じて使い分けましょう。 |
8.2 Pythonの実行確認
main.py を実行します。fetchall()の場合と同じ結果が表示されれば成功です。
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
9. UPDATEの実行
次に、UPDATE文を実行して既存のレコードを更新します。テーブル作成セクションでINSERTを実行した際と同様に、更新系のSQLでは実行後にconnection.commit()を呼び出して変更を確定させる必要があります。
9.1 Pythonコードの修正
main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
# カーソルを取得
cursor = connection.cursor()
# UPDATEの実行
cursor.execute(
"UPDATE users SET age = %s WHERE id = %s",
(25, 1)
)
# 変更を確定
connection.commit()
print(f"影響を受けた行数: {cursor.rowcount}")
# リソースの解放
cursor.close()
connection.close()
追加したコードを解説します。
cursor.execute(
"UPDATE users SET age = %s WHERE id = %s",
(25, 1)
)
UPDATE文で、idが1のレコード(Suzuki)のageを20から25に更新しています。SQL中の%sはプレースホルダと呼ばれ、2つ目の引数で指定したタプルの値が順番に埋め込まれます。プレースホルダについてはこの後のセクションで詳しく説明します。
| 📝 connection.commit()の重要性 |
|---|
INSERT、UPDATE、DELETEなどの更新系SQLを実行した後は、必ずconnection.commit()を呼び出してください。commit()を呼ばないと、変更がデータベースに反映されません。これをトランザクションと呼びます。 |
print(f"影響を受けた行数: {cursor.rowcount}")
cursor.rowcountは、直前のSQL実行で影響を受けた行数を返します。UPDATEが成功していれば1が表示されます。
9.2 Pythonの実行確認
main.py を実行します。
影響を受けた行数: 1
cursor.rowcountが1であるため、1件のレコードが更新されたことがわかります。
9.3 MySQLで確認
MySQLに接続して、レコードが更新されているか確認してみましょう。
mysql -u root -p -e "SELECT * FROM testdb.users;"
パスワードを入力すると、以下の結果が表示されます。idが1のSuzukiのageが20から25に更新されていることがわかります。
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | Suzuki | 25 |
| 2 | Tanaka | 30 |
| 3 | Suzuki | 25 |
+----+--------+------+
10. DELETEの実行
次に、DELETE文を実行してレコードを削除します。
10.1 Pythonコードの修正
main.pyを以下の内容に書き換えます。
import mysql.connector
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
# カーソルを取得
cursor = connection.cursor()
# DELETEの実行
cursor.execute(
"DELETE FROM users WHERE id = %s",
(3,)
)
# 変更を確定
connection.commit()
print(f"影響を受けた行数: {cursor.rowcount}")
# リソースの解放
cursor.close()
connection.close()
追加したコードを解説します。
cursor.execute(
"DELETE FROM users WHERE id = %s",
(3,)
)
DELETE FROM文で、idが3のレコードを削除しています。パラメータが1つの場合でも、タプルとして渡す必要があります。
| 💡 ポイント |
|---|
DELETEのパラメータ指定で(3,)のようにカンマがついていますが、これはPythonでタプルを作成するための記法です。要素が1つのタプルを作成する場合、末尾にカンマが必要です。カンマがないと(3)は整数の3と解釈されてしまいます。 |
10.2 Pythonの実行確認
main.py を実行します。
影響を受けた行数: 1
cursor.rowcountが1であるため、1件のレコードが削除されたことがわかります。
10.3 MySQLで確認
MySQLに接続して、レコードが削除されているか確認してみましょう。
mysql -u root -p -e "SELECT * FROM testdb.users;"
パスワードを入力すると、以下の結果が表示されます。idが3のレコードが削除されていることがわかります。
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | Suzuki | 25 |
| 2 | Tanaka | 30 |
+----+--------+------+
11. エラー処理
データベース操作では、様々な動的なエラー(実行しないと検知できないエラー)が発生する可能性があります。
- MySQLが未起動で接続できない
- 指定したデータベースやテーブルが存在しない
- SQLの文法が誤っている
- 型が合わない値を挿入しようとした
これらのエラーに適切に対処するため、try/except文でエラー処理を実装します。main.pyを以下の内容に書き換えます。
import mysql.connector
try:
# データベースに接続
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
)
# カーソルを取得
cursor = connection.cursor()
# SELECTの実行
cursor.execute("SELECT id, name, age FROM users")
# 結果の取得と出力
rows = cursor.fetchall()
for row in rows:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
except mysql.connector.Error as e:
print(f"データベース操作でエラーが発生しました: {e}")
finally:
# リソースの解放
if 'cursor' in dir() and cursor is not None:
cursor.close()
if 'connection' in dir() and connection.is_connected():
connection.close()
print("接続を閉じました")
追加したコードを解説します。
try:
tryブロックには、エラーが発生する可能性のある処理を記述します。データベース接続、SQL実行、結果の読み込みなどの処理がこれに該当します。
except mysql.connector.Error as e:
print(f"データベース操作でエラーが発生しました: {e}")
exceptブロックには、エラーが発生した場合の処理を記述します。mysql.connector.Errorを指定することで、MySQL関連のエラーをキャッチできます。変数eにエラーの詳細情報が格納されます。
finally:
if 'cursor' in dir() and cursor is not None:
cursor.close()
if 'connection' in dir() and connection.is_connected():
connection.close()
print("接続を閉じました")
finallyブロックには、エラーの有無にかかわらず必ず実行したい処理を記述します。リソースの解放処理をここに書くことで、エラーが発生した場合でも確実にリソースが解放されます。if 'cursor' in dir()で変数が存在するかを確認してからclose()を呼んでいるのは、接続自体が失敗した場合にカーソルが作成されていない可能性があるためです。
11.1 エラーの動作確認
エラー処理が正しく動作するか確認するために、わざと誤ったパスワードを指定してプログラムを実行してみます。上記のコードのpasswordを以下のように書き換えてください。
password="wrong_password"
main.py を実行します。以下のようなエラーメッセージが表示されます。
データベース操作でエラーが発生しました: 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
Access deniedというメッセージから、パスワードが誤っているために接続が拒否されたことがわかります。try/exceptにより、プログラムがクラッシュせずにエラーメッセージを表示できていることを確認してください。
確認が終わったら、passwordを正しい値に戻しておきます。
password="your_password"
12. リソースの解放 : with文の活用
先ほどのtry/finallyによるリソースの解放は正しく動作しますが、Pythonではwith文を使用することで、より簡潔にリソース管理を行うことができます。with文の基本的な構文は以下のとおりです。
with mysql.connector.connect(...) as connection:
with connection.cursor() as cursor:
cursor.execute("SQL文")
with文を使うと、ブロックを抜ける際に自動的にclose()が呼ばれるため、手動でクローズ処理を書く必要がなくなります。
実際に試してみましょう。main.pyを以下の内容に書き換えます。
import mysql.connector
try:
# データベースに接続
with mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
) as connection:
# カーソルを取得
with connection.cursor() as cursor:
# SELECTの実行
cursor.execute("SELECT id, name, age FROM users")
# 結果の取得と出力
rows = cursor.fetchall()
for row in rows:
print(f"id: {row[0]}, name: {row[1]}, age: {row[2]}")
except mysql.connector.Error as e:
print(f"データベース操作でエラーが発生しました: {e}")
追加したコードを解説します。
with mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
) as connection:
with文でmysql.connector.connect()を囲むことで、withブロックを抜ける際に自動的にconnection.close()が呼ばれます。これにより、手動でクローズ処理を書く必要がなくなります。
with connection.cursor() as cursor:
同様に、カーソルの取得にもwith文を使っています。withブロックを抜けると自動的にcursor.close()が実行されます。
このように、with文を使うことでfinallyブロックが不要になり、コードが簡潔になります。
| 💡 ポイント |
|---|
with文は、Pythonのコンテキストマネージャという仕組みを利用しています。withブロックを抜ける際(正常終了でもエラー発生時でも)、自動的にリソースの解放処理が実行されます。ファイル操作やデータベース接続など、リソースの管理が必要な場面で広く使われるPythonのベストプラクティスです。 |
13. パラメータ化クエリ
SQLを実行する際に、変数の値をSQLに埋め込みたい場合があります。このとき、文字列の結合でSQLを組み立てるのは非常に危険です。
13.1 SQLインジェクションとは
例えば、以下のように文字列結合でSQLを組み立てるコードを考えます。
# 危険な例(絶対にやってはいけない)
user_input = "'; DROP TABLE users; --"
sql = "SELECT * FROM users WHERE name = '" + user_input + "'"
cursor.execute(sql)
この場合、user_inputに悪意のある文字列が入ると、意図しないSQLが実行されてしまいます。この攻撃手法をSQLインジェクションと呼びます。上記の例では、usersテーブルが削除されてしまう可能性があります。
13.2 パラメータ化クエリによる対策
パラメータ化クエリ(プレースホルダ)を使用することで、SQLインジェクションを防止できます。基本的な構文は以下のとおりです。
cursor.execute("SELECT * FROM テーブル名 WHERE カラム名 = %s", (値,))
%sの部分がプレースホルダで、2つ目の引数で指定した値が安全に埋め込まれます。ライブラリが適切なエスケープ処理を行うため、SQLインジェクションのリスクを排除できます。
実際に試してみましょう。
# 安全な例(パラメータ化クエリ)
name = "Suzuki"
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
| 📝 パラメータ化クエリの注意点 |
|---|
プレースホルダには必ず%sを使用してください(データ型に関係なく%sです)。Pythonの文字列フォーマット(f"...{変数}"や"...%s" % 変数)とは異なる仕組みです。また、2つ目の引数はタプルで渡す必要があります。パラメータが1つの場合でも(name,)のようにカンマをつけてタプルにしてください。 |
14. 完成版のコード
ここまで学んだ内容をまとめた、完成版のコードです。main.pyを以下の内容に書き換えます。
import mysql.connector
def setup_test_data(cursor, connection):
"""テストデータを初期化する"""
cursor.execute("TRUNCATE TABLE users")
cursor.execute("INSERT INTO users (name, age) VALUES ('Suzuki', 20)")
cursor.execute("INSERT INTO users (name, age) VALUES ('Tanaka', 30)")
cursor.execute("INSERT INTO users (name, age) VALUES ('Suzuki', 25)")
connection.commit()
print("テストデータを初期化しました")
print()
def select_all_users(cursor):
"""全ユーザを取得して表示する"""
cursor.execute("SELECT id, name, age FROM users")
rows = cursor.fetchall()
print("=== 全ユーザ一覧 ===")
for row in rows:
print(f" id: {row[0]}, name: {row[1]}, age: {row[2]}")
print()
def insert_user(cursor, connection, name, age):
"""ユーザを追加する"""
cursor.execute(
"INSERT INTO users (name, age) VALUES (%s, %s)",
(name, age)
)
connection.commit()
print(f"ユーザを追加しました - name: {name}, age: {age}")
print(f" 挿入されたID: {cursor.lastrowid}")
print()
def update_user_age(cursor, connection, user_id, new_age):
"""ユーザの年齢を更新する"""
cursor.execute(
"UPDATE users SET age = %s WHERE id = %s",
(new_age, user_id)
)
connection.commit()
print(f"ユーザを更新しました - id: {user_id}, new_age: {new_age}")
print(f" 影響を受けた行数: {cursor.rowcount}")
print()
def delete_user(cursor, connection, user_id):
"""ユーザを削除する"""
cursor.execute(
"DELETE FROM users WHERE id = %s",
(user_id,)
)
connection.commit()
print(f"ユーザを削除しました - id: {user_id}")
print(f" 影響を受けた行数: {cursor.rowcount}")
print()
def main():
try:
# データベースに接続
with mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password",
database="testdb"
) as connection:
with connection.cursor() as cursor:
# 0. テストデータの初期化
print("--- 0. テストデータの初期化 ---")
setup_test_data(cursor, connection)
# 1. 現在のデータを確認
print("--- 1. 現在のデータを確認 ---")
select_all_users(cursor)
# 2. レコードを追加
print("--- 2. レコードを追加 ---")
insert_user(cursor, connection, "Takahashi", 23)
# 3. 追加後のデータを確認
print("--- 3. 追加後のデータを確認 ---")
select_all_users(cursor)
# 4. レコードを更新
print("--- 4. レコードを更新 ---")
update_user_age(cursor, connection, 4, 15)
# 5. 更新後のデータを確認
print("--- 5. 更新後のデータを確認 ---")
select_all_users(cursor)
# 6. レコードを削除
print("--- 6. レコードを削除 ---")
delete_user(cursor, connection, 4)
# 7. 削除後のデータを確認
print("--- 7. 削除後のデータを確認 ---")
select_all_users(cursor)
except mysql.connector.Error as e:
print(f"データベース操作でエラーが発生しました: {e}")
if __name__ == "__main__":
main()
14.1 コードの構成
このコードは、以下の関数で構成されています。
| 関数名 | 説明 |
|---|---|
setup_test_data() |
テストデータを初期化する(テーブルをクリアして3件のレコードを挿入) |
select_all_users() |
usersテーブルの全レコードを取得して表示する |
insert_user() |
指定した名前と年齢のレコードを追加する |
update_user_age() |
指定したIDのユーザの年齢を更新する |
delete_user() |
指定したIDのユーザを削除する |
main() |
上記の関数を順番に呼び出し、CRUD操作を実行する |
VS Codeのファイルツリーは以下の状態です。
database_test/
└── main.py
15. 実行と動作確認
完成した main.py を実行します。以下のような出力が表示されれば成功です。
--- 0. テストデータの初期化 ---
テストデータを初期化しました
--- 1. 現在のデータを確認 ---
=== 全ユーザ一覧 ===
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
--- 2. レコードを追加 ---
ユーザを追加しました - name: Takahashi, age: 23
挿入されたID: 4
--- 3. 追加後のデータを確認 ---
=== 全ユーザ一覧 ===
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
id: 4, name: Takahashi, age: 23
--- 4. レコードを更新 ---
ユーザを更新しました - id: 4, new_age: 15
影響を受けた行数: 1
--- 5. 更新後のデータを確認 ---
=== 全ユーザ一覧 ===
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
id: 4, name: Takahashi, age: 15
--- 6. レコードを削除 ---
ユーザを削除しました - id: 4
影響を受けた行数: 1
--- 7. 削除後のデータを確認 ---
=== 全ユーザ一覧 ===
id: 1, name: Suzuki, age: 20
id: 2, name: Tanaka, age: 30
id: 3, name: Suzuki, age: 25
INSERT、UPDATE、DELETEの各操作が正しく実行され、それぞれの操作後にSELECTでデータの変化を確認できました。
| 💡 ポイント |
|---|
setup_test_data()でTRUNCATE TABLEを実行しているため、何度実行しても同じ結果が得られます。TRUNCATE TABLEはテーブルの全レコードを削除し、AUTO_INCREMENTのカウンタもリセットするため、常にidが1から始まります。 |
16. クリーンアップ
ハンズオンが完了したら、Pythonからデータベースを削除します。main.pyを以下の内容に書き換えて実行します。
import mysql.connector
# MySQLに接続(データベースを指定しない)
connection = mysql.connector.connect(
host="localhost",
port=3306,
user="root",
password="your_password"
)
cursor = connection.cursor()
# データベースの削除
cursor.execute("DROP DATABASE testdb")
print("データベース testdb を削除しました")
cursor.close()
connection.close()
データベース testdb を削除しました
DROP DATABASEにより、データベースtestdbとその中のテーブル・データがすべて削除されます。
16.1 MySQLで確認
MySQLに接続して、データベースが削除されているか確認してみましょう。
mysql -u root -p -e "SHOW DATABASES;"
パスワードを入力すると、以下のようにデータベースの一覧が表示されます。testdbが一覧に存在しないことが確認できます。
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
17. まとめ
このハンズオンでは、PythonからMySQLデータベースに接続し、データの操作を体験しました。
- PythonからMySQLに接続するには、mysql-connector-pythonライブラリを使用する
mysql.connector.connect()で接続を行い、ホスト、ポート、ユーザ名、パスワードを指定する- Pythonから
CREATE DATABASEやCREATE TABLEを実行してデータベースやテーブルを作成できる - SELECT文の実行は
cursor.execute()でSQLを実行し、cursor.fetchall()で結果をリストとして取得する - INSERT / UPDATE / DELETE文の実行後は、
connection.commit()で変更を確定させる必要がある try/except文でmysql.connector.Errorをキャッチし、データベース操作のエラーに対処するwith文を使用することで、リソースの解放(close())を自動的に行うことができる- SQL文に変数を埋め込む場合は、文字列結合ではなくパラメータ化クエリ(
%sプレースホルダ)を使用してSQLインジェクションを防止する