12. SQL の基礎¶
第 12 回では、SQL の基本的な操作方法について学びます。
12.1 SQL とは¶
SQL (Structured Query Language) は、データベースを操作するための最も一般的なプログラミング言語です。基本的な構文は比較的理解しやすく、英語に近い形で書けるため、プログラミング初心者でも学習しやすい言語となっています。
SQL は国際標準化されており、さまざまなデータベース管理システム(DBMS: MySQL、PostgreSQL、Oracle など)で使用することができます。膨大なデータを効率的に扱うことのできる SQL は、実践的なシステム開発において欠かせないスキルとなります。
※ 細かい書き方は DBMS ごとに異なることがあります。
12.1.1 SQL の主な機能¶
データベースにおける基本的な 4 つの操作を表す用語として、CRUD (Create/Read/Update/Delete) があります。
C - Create(作成)
- 新しいデータを作成・追加する操作
- SQL では INSERT 文を使用
R - Read(読み取り)
- 既存のデータを取得・表示する操作
- SQL では SELECT 文を使用
U - Update (更新)
- 既存のデータを修正・変更する操作
- SQL では UPDATE 文を使用
D - Delete(削除)
- 不要なデータを削除する操作
- SQL では DELETE 文を使用
CRUD(クラッド)は、データベース操作に限らず、Web アプリケーションや API 設計全体の基本概念としても広く使われています。
12.1.2 SQLite¶
通常のデータベース(MySQL、PostgreSQL など)は、高性能かつ高機能ですが、サーバのセットアップやセキュリティ設定が複雑で、専門的な知識とスキル、さらにはサーバの維持管理費用が必要となります。一方で、小規模なプロジェクトや個人開発においては、サーバが不要な単一ファイルベースの軽量データベースとして、SQLite が選択されることも多くあります。SQLite は設定不要ですぐに使い始められるため、プロトタイプ開発や学習用途にも適しています。本講義では、SQLite を用いた簡単なデータベース操作について学びます。
12.2 Python によるデータベース操作¶
Python から SQLite のデータベースを操作する基本的な流れを見ていきましょう。
12.2.1 SQLite のインポート¶
SQLite は Python に標準でインストールされているため(モジュール名: sqlite3)、インポートするだけで使用することができます。
12.2.2 データベースへの接続¶
SQLite データベースを操作するには、まず sqlite3.connect() 関数でデータベースファイルに接続します。このとき、ファイルが存在しない場合は新規作成されます。SQLite の拡張子としては .db, .sqlite, .sqlite3 などが用いられますが、拡張子無しでも動作します。データベースへの接続・操作後は、sqlite3.Connection.close() メソッドで接続を閉じることを忘れないようにしてください。接続を閉じないと、メモリ使用量の増加や、データの損失などの問題が生じることがあります。
# データベースに接続
conn = sqlite3.connect("SQL/practice.db")
# 接続が成功したことを確認
print("データベースに接続しました")
# 接続を閉じる
conn.close()
CHIKUWA Editor における SQLite の使用について
CHIKUWA Editor ではセキュリティの都合上、各ユーザフォルダ内に設置した SQL フォルダ内においてのみ、SQLite のデータベースファイルへの接続ができるようになっています。そのため、sqlite3.connect() 関数でアクセスするファイルパスは、"SQL/practice.db" のようにしてください。
- Python ファイルが
SQLフォルダ内に置かれている場合は、ファイルパスを"practice.db"のようにすることができます。 - Python ファイルが別のサブフォルダ内にある場合は、ファイルパスを
"../SQL/practice.db"のようにすることでアクセスできます(..は一つ上の階層を意味)。 SQLというフォルダ名は変更することもできます。
メモリ上でのデータベース接続
メモリ上でデータベースを使いたい場合は、sqlite3.connect(:memory:) と書きます。メモリ上での操作はディスクへの読み書きが発生しないため、非常に高速な操作が可能となります。しかし、データがディスクに残らないため、プロセス終了と同時にデータが完全に削除されます。このため、試験的なコードの実行や学習用途、機密データの処理には適していますが、永続化が必要なデータには適していません。本講義では、万が一 SQL フォルダ内でのデータベース操作がうまく実行できない場合に、メモリ上でのデータベース接続を検討してください。
12.2.3 カーソルオブジェクトの作成¶
SQLite では、カーソルと呼ばれるオブジェクト(インタフェース)を通じてデータベース操作を実行します。カーソルオブジェクトは、sqlite3.Connection.cursor() メソッドを用いて以下のように作成することができます。
sqlite3.Cursor と qlite3.Connection オブジェクトの主なメソッド一覧は以下のようになります。SQL 文の実行には Cursor.execute() メソッド、変更の反映には Connection.commit() メソッドという流れを押さえておきましょう。
sqlite3.Cursor オブジェクトの主なメソッド¶
| メソッド名 | 説明 |
|---|---|
execute(sql, params) | 単一の SQL 文を実行(パラメータを使う場合は?プレースホルダを使用) |
executemany(sql, seq) | 複数のデータセットで SQL を繰り返し実行(主に INSERT/UPDATE 時に便利) |
fetchone() | 結果セットから 1 行だけ取得(1 レコード) |
fetchall() | 結果セットから全ての行を取得(全レコード) |
fetchmany(size) | 指定した数の行を取得(部分的に結果を取りたい場合に使用) |
qlite3.Connection オブジェクトの主なメソッド¶
| メソッド名 | 説明 |
|---|---|
cursor() | Cursor オブジェクトを生成 |
commit() | データベースの変更を反映 |
close() | 接続を閉じる(リソースの解放) |
12.2.4 テーブルの作成(CREATE TABLE)¶
データベースのテーブルを作成してみましょう。ここからいよいよ SQL 文を記述し、Cursor.execute() メソッドで実行していくことになります。
例えば、id(ID: 整数型), name(名前:文字列型), age(年齢:整数型)の 3 つの列を持つ students というデータベーステーブルを作成する場合、SQL 文は以下のようになります。
- テーブルの作成は
CREATE TABLE テーブル名 (...)という構文で行います。 IF NOT EXISTSをつけることで、テーブルが存在しない場合にだけ作成を行います。- テーブルの中身は、
(列名 データの型, ...)の形式で記述します。 INTEGERは整数型、TEXTは文字列型であることを意味します。PRIMARY KEYは、各行を一意に識別するための列であることを表し、重複する値が存在しないことを保証します。NOT NULLは、値が入っていること(NULL でないこと)を保証します。
Python で実行する場合は、以下のように Cursor.execute() メソッド内に SQL 文を記述します。
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER
)
""")
データ型の概要¶
SQLite におけるデータ型には大きく以下の 5 つがあります。
INTEGER: 整数REAL: 浮動小数点数TEXT: 文字列BLOB: バイナリデータNUMERIC: 日付・時間・真偽値などさまざまな数値
SQLute には厳密な型があるわけではなく、緩く柔軟な特徴を持ちますが、独特な挙動を示すこともあるので注意が必要です。
12.2.5 データの挿入(INSERT 文)¶
テーブルを作成したら、データを挿入します。データの挿入は以下の INSERT 文を用います。
例えば、students テーブルに (name, age) = ("茨城太郎", 20) のデータを挿入したい場合、以下のようにします。このとき、id には自動的に連番が振られます。
ただし、このように SQL 文の中に直接データを入力することは、安全性の面から推奨されません。意図しないコマンドが実行される危険性(SQL インジェクション)があるためです。この対策として、プレースホルダ ? を使用し、パラメータを渡す方法が一般的に用いられます。以下は、実際に Python でプレースホルダを使用して Cursor.execute() メソッドでデータを挿入するコード例です。
短い SQL 文であればこのように一行に収めてしまっても問題ありませんが、少し長くなる場合は以下のようにトリプルクォートを使い改行を含めるスタイルにすると、構造がわかりやすくなるためおすすめです。
データを挿入(または更新)したら、変更を反映させるために Connection.commit() メソッドを呼び出すことを忘れないようにしましょう。ソースコードの最後に一度だけ実行するだけで問題ありません。
12.2.6 データの取得(SELECT 文)¶
データの挿入をしたら、正しくテーブルが更新されているかどうかを確認してみましょう。データベースのテーブルからデータを取得するには、以下のような形で SELECT 文を用います。
以下では、全項で作成した students テーブルからデータを取得する例を示します。
すべての列を取得¶
すべての列を取得したい場合は、ワールドカード * を用いて以下のように書きます。
特定の列を取得¶
特定の列(例えば name と age)を取得したい場合は、以下のように書きます。
取得条件の指定¶
条件を指定してデータを絞り込みたい場合は、WHERE 句を使用します。例えば、年齢(age)が 20より大きいデータのみを抽出したい場合は、以下のように書きます。
複数の条件を指定したい場合は、AND や OR を使用して条件を組み合わせることができます。
ただし、これらはプレースホルダを使って使用することが推奨されます。
データのソート¶
テーブルからデータを取得する際に、特定の列でソートして表示したい場合は、ORDER BY 句を使用します。
データを age の昇順で表示したい場合は、以下のように ASC キーワードを使用します。
降順の場合は、DESC キーワードを使用します。
Python での SELECT 文の実行¶
Python で SELECT 文を実行してデータを取得したい場合は、これまでと同様 Cursor.execute() メソッドで SQL 文を実行した後、Cursor.fetchall() メソッド(または Cursor.fetchone(), Cursor.fetchmany(n))でデータをフェッチ(取得)します。
以下は、students テーブルからすべてのデータを取得して表示するコード例です。
12.2.7 データの更新(UPDATE 文)¶
既存のデータを更新したい場合は、UPDATE 文を使用します。UPDATE 文の基本構文は以下になります。
Python で students テーブルの id が 1 のデータを対象に、name を更新したい場合は、以下のようにします。
12.2.8 データの削除(DELETE 文)¶
データベースから不要なデータを削除したい場合は、DELETE 文を使用します。DELETE 文の基本構文は以下になります。
Python で students テーブルの id が 1 のデータを削除したい場合は、以下のようにします。
ここで、プレースホルダ ? に渡すデータはタプルである必要があるため、(1) ではなく (1,) としています。
DELETE 文を使う場合の注意
DELETE 文を使う場合は、必ず WHERE 句を使用するようにしてください。WHERE 句を指定しないと、テーブル内のすべてのデータが削除されます。また、間違って重要なデータを削除してしまわないように、データのバックアップを取る習慣をつけましょう。
12.2.9 テーブルの削除(DROP TABLE)¶
テーブル自体を削除したい場合は、DROP TABLE という SQL 文を書きます。
Python で students テーブルを削除したい場合は、以下のようにします。
12.2.10 Python によるデータベース操作のまとめ¶
以下は、Python で SQLite のデータベースに接続し、CRUD の一連の動作を行うコード例です。データベースがどのような流れで作成・変更され、どのような結果が出力されるか、考えてみましょう。
import sqlite3
# データベースに接続
conn = sqlite3.connect("practice.db")
# カーソルオブジェクトの作成
cursor = conn.cursor()
# students テーブルの作成
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER
)
""")
# データの挿入(executemany で一括挿入も可能)
cursor.execute("""
INSERT INTO students (name, age)
VALUES (?, ?)
""", ("茨城太郎", 20))
cursor.execute("""
INSERT INTO students (name, age)
VALUES (?, ?)
""", ("日立次郎", 21))
cursor.execute("""
INSERT INTO students (name, age)
VALUES (?, ?)
""", ("花子", 19))
# データの更新
cursor.execute("""
UPDATE students
SET name = ?
WHERE name = ?
""", ("水戸花子", "花子"))
# データの削除
cursor.execute("""
DELETE FROM students
WHERE id = ?
""", (2,))
# データの取得
cursor.execute("SELECT * FROM students")
rows = cursor.fetchall()
# データの表示
for row in rows:
print(f"{row[0]}: {row[1]} ({row[2]}歳)")
# 変更を保存(練習中はコメントアウトでもOK)
conn.commit()
# 接続を閉じる
conn.close()
このコードでは INSERT 文を 3 回実行していますが、Cursor.executemany() メソッドを用いることで以下のようにデータを一括で挿入することもできます。
students = [("茨城太郎", 20), ("日立次郎", 21), ("花子", 19)]
cursor.executemany("INSERT INTO students (name, age) VALUES (?, ?)", students)
また、Connection.commit() メソッドは変更を保存する重要な処理ですが、INSERT 文の練習中は実行するたびにデータが増えていってしまうため、必要に応じてコメントアウトすることを検討してください。
演習¶
演習 12-1
SQLite を用いて id(INTEGER)、task(TEXT)、done(INTEGER) を持つ todo テーブルを作成し、3つのタスクを登録してください。また、タスクのリストを表示し、done が 0 のときは「❌️」、done が 1 のときは「✅️」を表示してください。
演習 12-2
todo テーブルに対し、未完了のタスクのみを取得する SQL 文を作成してください。また、id を降順に並び替えて表示してください。