SQLiteは、MySQLのようなサーバー・クライアント方式ではなく、アプリケーションに直接組み込んで使用することができる関係データベース管理システム (RDBMS) です。
身近なところでは、Google ChromeやMozilla Firefoxなどの多くのアプリケーションで使用されており、Google AndroidではOSに組み込まれています。
これにより、SQLiteは世界で最もインストールベースの多いRDBMSの1つとして認知されています。
__TOC__
SQLiteは、高速かつ軽量なリレーショナルデータベース管理システムです。SQLiteは、コンパクトなライブラリとして提供され、モバイルデバイス、デスクトップアプリケーション、Webブラウザ、組み込みシステムなど、あらゆるプラットフォームで使用されています。
SQLiteは、標準のSQLをサポートし、トランザクション処理、データの完全性維持、およびACID（原子性、一貫性、分離性、耐久性）の保証を提供します。また、多くの主要なプログラミング言語（C、Java、Python、PHPなど）で利用可能であり、簡単に統合することができます。
SQLiteの最も重要な特徴の一つは、ファイルベースのデータベースであることです。つまり、データはファイルに保存され、ファイルを別の場所にコピーするだけで、データを移行したりバックアップしたりすることができます。また、複数のプロセスが同時にデータベースにアクセスすることができるため、アプリケーションのパフォーマンスが向上します。
SQLiteは、他のリレーショナルデータベース管理システムと比較して、データベースが比較的小規模である場合に最適です。大量のデータを処理する必要がある場合や、高い同時アクセスが必要な場合には、より高機能なデータベースシステムを検討する必要があります。
ただし、SQLiteは非常に高速であるため、多くの場合、大量のデータを処理する必要がある場合でも、十分なパフォーマンスを発揮します。SQLiteはまた、カスタム拡張機能を提供することができ、標準のSQLコマンド以外の機能を簡単に実装することができます。
SQLiteは、プログラマにとって非常に使いやすく、学びやすいです。SQLの基本構文を理解しているだけで、すぐにSQLiteを利用することができます。また、SQLiteは非常に広く利用されているため、オンラインで豊富な情報を見つけることができます。
本書では、SQLite version3の保守管理用のコマンドラインインターフェース sqlite3の操作を中心に、SQLiteの機能の解説を行いますので、まずsqlite3が、ご自身の環境にインストールされているか確認します。
コマンドラインでsqlite3のバージョンを確認（インストールされている場合）:
$ sqlite3 -version
3.45.0 2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b4alt1 (64-bit)
-versionの前の -（ハイフン）は１つです。
バージョンは異なるかもしれませんが、この書式で１行表示されたら sqlite3 はインストールされています。
その場合は、追加のインストール手順は不要ですが、２フィールド目の日付が年オーダーで古い場合は、よりあらたしいバージョンの入手も検討してください。
コマンドラインでsqlite3のバージョンを確認（インストールされていない場合）:
$ sqlite3 -version
bash: sqlite3: command not found
メッセージの細部は異なるかもしれませんが、”command not found” や ”コマンドまたはファイル名が違います” のようなメッセージが返された場合は、 sqlite3 はインストールされていません。
その場合は、下記のインストール手順が必要です。
SQLite公式（[https://www.sqlite.org/download.html ダウンロードページ]）の Precompiled Binaries for Windows にある sqlite-tools-win-x64-3450000.zip(4.77 MiB)をダウンロードします（3450000の部分はバージョンによって変わります。この場合は、3.42.0）。
sqlite-tools-win-x64-3450000.zipは、ZIP書庫で
Archive:  sqlite-tools-win-x64-3450000.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
  3730432  2024-01-16 02:32   sqlite3.exe
  3092992  2024-01-16 02:32   sqldiff.exe
  4365824  2024-01-16 02:33   sqlite3_analyzer.exe
---------                     -------
 11189248                     3 files
と３つの実行形式があります。
これらはインストーラーではなく、コマンドそのものです。
このまま実行できます。
2022年11月現在サポートが継続されている macOS には、最初からインストールされています。
Terminalを起動してバージョンを確認してください。
% sqlite3 --version
3.28.0 2019-04-15 14:49:49 378230ae7f4b721c8b8d83c8ceb891449685cd23b1702a57841f1be40b5daapl
-versionの前の -（ハイフン）は１つです。
ports/pkg のdatabases/sqlite3 にあります。
自分でビルド:
% sudo make -C /usr/ports/databases/sqlite3/ all install clean
...
% sqlite3 -version
3.44.0 2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad8alt1 (64-bit)
-versionの前の -（ハイフン）は１つです。
ビルド済パッケージを導入:
% sudo pkg install sqlite3-3.42.0,1
...
% sqlite3 -version
3.42.0 2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf3alt1
GNU/Linuxの場合は、ディストーションやパッケージマネージャーによってパッケージの名前やインストール方法が異なるので以下は一例です。
Chromebrewは、sqlite の名前で SQLite のパッケージが用意されています。
$ crew install -sv sqlite
...
$ sqlite3 -version
3.42.0 2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf3alt1
-versionの前の -（ハイフン）は１つです。
$ sudo dnf install sqlite
...
$ sqlite3 -version
3.36.0 2021-06-18 18:36:39 5c9a6c06871cb9fe42814af9c039eb6da5427a6ec28f187af7ebfb62eafaalt1
-versionの前の -（ハイフン）は１つです。
SQLiteのデータベースは、１つの通常のファイルなので、バックアップはファイルをコピーするだけです。
また、sqlite3のコマンドに、.backup コマンドと .restore が用意されています。
バックアップはファイルレベルのコピーでできますが、障害発生に備えてのバックアップとしては、テキストファイルへのダンプが有用です。
ダンプは、SQLiteのコマンドライン管理インターフェース sqlite3を使います。
操作と結果概観で作成したデータベース periodic.dbの テーブル elementsをテキストファイルperiodic.txt にダンプする例です。
$ sqlite3
SQLite version 3.40.0 2022-11-16 12:10:08
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open periodic.db
sqlite> .table
elements
sqlite> .dump elements
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE elements( number, name, symbol );
INSERT INTO elements VALUES(1,'Hydrogen','H');
COMMIT;
sqlite> .output periodic.txt
sqlite> .dump elements
sqlite> .output stdout
sqlite> .quit
$ cat periodic.txt
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE elements( number ,name ,symbol );
INSERT INTO elements VALUES(1,'Hydrogen','H');
COMMIT;
$ _
sqlite> .open periodic.db
.open コマンドで、データベース periodic.dbを開いています。
$ sqlite3 periodic.db
と、最初にコマンドラインからデータベースを指定して開くこともできます。
sqlite> .table
elements
.table コマンドで、開いているデータベースに含まれるテーブル名を表示します。
.dump elements
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE elements( number ,name ,symbol );
INSERT INTO elements VALUES(1,'Hydrogen','H');
COMMIT;
.dump コマンドで、テーブルelementsをダンプしています。既定の出力先は標準出力です。
sqlite> .output periodic.txt
出力先をファイルperiodic.txtに変更。
sqlite> .dump elements
sqlite> .output stdout
出力先を標準出力に戻します（直後で終了しているので不要とは言えます）。
sqlite> .quit
sqlite3 を終了します。
.dumpコマンドでダンプしたデータを現在のデータベースにリストアするには .read コマンドを使います。
$ sqlite3
SQLite version 3.40.0 2022-11-16 12:10:08
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open restore.db
sqlite> .read periodic.txt
sqlite> SELECT * FROM elements;
1|Hydrogen|H
SQLiteでは、テーブルにデータが追加されると、データベースファイルのサイズは徐々に大きくなるが、テーブルからデータを削除しても、データベースファイルのサイズはすぐには小さくなりません。
sqlite3では、VACUUMコマンドで空き領域の開放を行います。
$ sqlite3
SQLite version 3.40.0 2022-11-16 12:10:08
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open restore.db
sqlite> VACUUM ;
SQLiteはアプリケーションに組込まれ使われますが、ここでは Python からの利用の例を紹介します。
Pythonでは、[https://peps.python.org/pep-0249/ PEP 249 -- Python Database API Specification v2.0] でデータベースAPIが標準化されており、sqlite3 モジュールでSQLite3をサポートしています。
[https://paiza.io/projects/Q90iBzW93QoWtWbokaBG_A?language=python3 都道府県コード一覧表（Python版）]:
import sqlite3
with sqlite3.connect(":memory:") as conn:
    cur = conn.cursor()
    cur.execute('CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)')
    prefs = """\
01	北海道	Hokkaido
02	青森県	Aomori
03	岩手県	Iwate
04	宮城県	Miyagi
05	秋田県	Akita
06	山形県	Yamagata
07	福島県	Fukushima
08	茨城県	Ibaraki
09	栃木県	Tochigi
10	群馬県	Gumma
11	埼玉県	Saitama
12	千葉県	Chiba
13	東京都	Tokyo
14	神奈川県	Kanagawa
15	新潟県	Niigata
16	富山県	Toyama
17	石川県	Ishikawa
18	福井県	Fukui
19	山梨県	Yamanashi
20	長野県	Nagano
21	岐阜県	Gifu
22	静岡県	Shizuoka
23	愛知県	Aichi
24	三重県	Mie
25	滋賀県	Shiga
26	京都府	Kyoto
27	大阪府	Osaka
28	兵庫県	Hyogo
29	奈良県	Nara
30	和歌山県	Wakayama
31	鳥取県	Tottori
32	島根県	Shimane
33	岡山県	Okayama
34	広島県	Hiroshima
35	山口県	Yamaguchi
36	徳島県	Tokushima
37	香川県	Kagawa
38	愛媛県	Ehime
39	高知県	Kochi
40	福岡県	Fukuoka
41	佐賀県	Saga
42	長崎県	Nagasaki
43	熊本県	Kumamoto
44	大分県	Oita
45	宮崎県	Miyazaki
46	鹿児島県	Kagoshima
47	沖縄県	Okinawa
"""
    cur.executemany('INSERT INTO 都道府県コード一覧表 VALUES(?, ?, ?)',
        (line.split("\t") for line in prefs.splitlines()))
    print('SELECT * FROM 都道府県コード一覧表')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表'):
        print(*row, sep=',')
    print()
    print('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22'):
        print(*row, sep=',')
    print()
    print('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード >= 2 and 都道府県コード <= 7')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード >= 2 and 都道府県コード <= 7'):
        print(*row, sep=',')
    print()
import sqlite3
with sqlite3.connect(":memory:") as conn:
with 文を使うと open関数同様、スコープを抜けると自動的にclose（とcommit）が行われるので、commit忘れを防止するためにも with を使うべきです。
try/except で例外にも対応すべきかもしれません。
単一のファイル（かインコアＤＢ）で構成されるSQLite3に connect するのは奇異に感じますが、これは Python Database API 2 が汎用的に設計されているためで、MySQLのようなサーバークライアントモデルを採用したRDBMSではまさにDBサーバーへの接続（認証付き）で「接続」を行いますので、その意味論に揃えた形です。
    cur = conn.cursor()
    cur.execute('CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)')
    prefs = """\
01	北海道	Hokkaido
02	青森県	Aomori
03	岩手県	Iwate
︙
47	沖縄県	Okinawa
"""
    cur.executemany('INSERT INTO 都道府県コード一覧表 VALUES(?, ?, ?)',
        (line.split("\t") for line in prefs.splitlines()))
.executemany()は、プレースホルダー(?)を含むSQL文にリストの内容を順に渡します。
プレースホルダーを使うこのやり方は、SQLインジェクションへの耐性を上げることができると考えられています。
    print('SELECT * FROM 都道府県コード一覧表')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表'):
        print(*row, sep=',')
    print()
    print('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22'):
        print(*row, sep=',')
    print()
    print('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード >= 2 and 都道府県コード <= 7')
    for row in cur.execute('SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード >= 2 and 都道府県コード <= 7'):
        print(*row, sep=',')
[https://paiza.io/projects/xAoitZVydOzwPdZK9k4DcQ?language=python3 周期律表（Python版）]:
import sqlite3
with sqlite3.connect("periodic.db") as conn:
    cur = conn.cursor()
    cur.execute('CREATE TABLE 周期律表 (原子番号 INTEGER PRIMARY KEY AUTOINCREMENT, 和名 STRING, 英名 STRING, 元素記号 STRING)')
    #cur.execute('INSERT INTO 周期律表 (name, symbol) VALUES("H", "Hydrogen")')
    cur.executemany('INSERT INTO 周期律表(和名,英名,元素記号) VALUES(?, ?, ?)', [
        ("水素", "Hydrogen", "H"),
        ("ヘリウム", "Helium", "He"),
        ("リチウム", "Lithium", "Li"),
        ("ベリリウム", "Beryllium", "Be"),
        ("硼素", "Boron", "B"),	
        ("炭素", "Carbon", "C"),
        ("窒素", "Nitrogen","N"),
        ("酸素", "Oxygen", "O"),
        ("弗素", "Fluorine", "F"),
        ("ネオン", "Neon", "Ne"),
        ("ナトリウム", "Sodium", "Na"),
        ("マグネシウム", "Magnesium", "Mg"),
    ])
    cur.execute('INSERT INTO 周期律表 VALUES(null, "アルミニウム", "Al", "Aluminium")')
    cur.execute('INSERT INTO 周期律表 VALUES(null, "珪素", "Si", "Silicon")')
with sqlite3.connect("periodic.db") as conn2:
    cur = conn2.cursor()
    print("テーブルスキーマーは、PRAGMA table_info(TABLE) で取得できます。")
    for row in cur.execute('PRAGMA table_info("周期律表") '):
        print(*row, sep=',')
    print('SELECT * FROM 周期律表')
    print(*(row[1] for row in cur.execute('PRAGMA table_info("周期律表") ')),sep=',')
    for row in cur.execute('SELECT * FROM 周期律表'):
        print(*row, sep=',')
    print("VIEW は、仮想的なテーブルで検索式に名前をつけたような働きをします")
    cur.execute('CREATE VIEW 原子番号４未満の元素 AS SELECT 英名, 元素記号 FROM 周期律表 WHERE 原子番号 < 4')
    for row in cur.execute('SELECT * FROM 原子番号４未満の元素'):
        print(*row, sep=',')
    print("INDEX は、検索性能が向上する一方レコード追加時にオーバーヘッドが生じるというトレードオフがあります。")
    cur.execute('CREATE INDEX 英名インデックス ON 周期律表(英名)')
    print("UNIQUE INDEX は、同一のテーブル内で値の重複のないフィールドを使ったインデックスです。")
    cur.execute('CREATE UNIQUE INDEX 原子番号英名ユニークインデックス ON 周期律表(原子番号,英名)')
    
    print("sqlite_master はデータベース毎にあるテーブルの台帳です。")
    for row in cur.execute('SELECT * FROM sqlite_master'):
        print(*row, sep=',')
    print("----")
    for row in cur.execute('SELECT name,sql FROM sqlite_master WHERE type="table"'):
        print(*row, sep=',')
    for row in cur.execute('PRAGMA database_list'):
        print(*row, sep=',')
    print(cur.execute('SELECT sqlite_version()').fetchone())
    print("----")
    for line in conn2.iterdump():
        print(line)
実行結果:
テーブルスキーマーは、PRAGMA table_info(TABLE) で取得できます。
0,原子番号,INTEGER,0,None,1
1,和名,STRING,0,None,0
2,英名,STRING,0,None,0
3,元素記号,STRING,0,None,0
SELECT * FROM 周期律表
原子番号,和名,英名,元素記号
1,水素,Hydrogen,H
2,ヘリウム,Helium,He
3,リチウム,Lithium,Li
4,ベリリウム,Beryllium,Be
5,硼素,Boron,B
6,炭素,Carbon,C
7,窒素,Nitrogen,N
8,酸素,Oxygen,O
9,弗素,Fluorine,F
10,ネオン,Neon,Ne
11,ナトリウム,Sodium,Na
12,マグネシウム,Magnesium,Mg
13,アルミニウム,Al,Aluminium
14,珪素,Si,Silicon
VIEW は、仮想的なテーブルで検索式に名前をつけたような働きをします
Hydrogen,H
Helium,He
Lithium,Li
INDEX は、検索性能が向上する一方レコード追加時にオーバーヘッドが生じるというトレードオフがあります。
UNIQUE INDEX は、同一のテーブル内で値の重複のないフィールドを使ったインデックスです。
sqlite_master はデータベース毎にあるテーブルの台帳です。
table,周期律表,周期律表,2,CREATE TABLE 周期律表 (原子番号 INTEGER PRIMARY KEY AUTOINCREMENT, 和名 STRING, 英名 STRING, 元素記号 STRING)
table,sqlite_sequence,sqlite_sequence,3,CREATE TABLE sqlite_sequence(name,seq)
view,原子番号４未満の元素,原子番号４未満の元素,0,CREATE VIEW 原子番号４未満の元素 AS SELECT 英名, 元素記号 FROM 周期律表 WHERE 原子番号 < 4
index,英名インデックス,周期律表,4,CREATE INDEX 英名インデックス ON 周期律表(英名)
index,原子番号英名ユニークインデックス,周期律表,5,CREATE UNIQUE INDEX 原子番号英名ユニークインデックス ON 周期律表(原子番号,英名)
----
周期律表,CREATE TABLE 周期律表 (原子番号 INTEGER PRIMARY KEY AUTOINCREMENT, 和名 STRING, 英名 STRING, 元素記号 STRING)
sqlite_sequence,CREATE TABLE sqlite_sequence(name,seq)
0,main,/workspace/periodic.db
('3.31.1',)
----
BEGIN TRANSACTION;
DELETE FROM "sqlite_sequence";
INSERT INTO "sqlite_sequence" VALUES('周期律表',14);
CREATE TABLE 周期律表 (原子番号 INTEGER PRIMARY KEY AUTOINCREMENT, 和名 STRING, 英名 STRING, 元素記号 STRING);
INSERT INTO "周期律表" VALUES(1,'水素','Hydrogen','H');
INSERT INTO "周期律表" VALUES(2,'ヘリウム','Helium','He');
INSERT INTO "周期律表" VALUES(3,'リチウム','Lithium','Li');
INSERT INTO "周期律表" VALUES(4,'ベリリウム','Beryllium','Be');
INSERT INTO "周期律表" VALUES(5,'硼素','Boron','B');
INSERT INTO "周期律表" VALUES(6,'炭素','Carbon','C');
INSERT INTO "周期律表" VALUES(7,'窒素','Nitrogen','N');
INSERT INTO "周期律表" VALUES(8,'酸素','Oxygen','O');
INSERT INTO "周期律表" VALUES(9,'弗素','Fluorine','F');
INSERT INTO "周期律表" VALUES(10,'ネオン','Neon','Ne');
INSERT INTO "周期律表" VALUES(11,'ナトリウム','Sodium','Na');
INSERT INTO "周期律表" VALUES(12,'マグネシウム','Magnesium','Mg');
INSERT INTO "周期律表" VALUES(13,'アルミニウム','Al','Aluminium');
INSERT INTO "周期律表" VALUES(14,'珪素','Si','Silicon');
CREATE VIEW 原子番号４未満の元素 AS SELECT 英名, 元素記号 FROM 周期律表 WHERE 原子番号 < 4;
CREATE INDEX 英名インデックス ON 周期律表(英名);
CREATE UNIQUE INDEX 原子番号英名ユニークインデックス ON 周期律表(原子番号,英名); 
COMMIT;
SQLiteはアプリケーションに組込まれ使われますが、ここでは PHP からの利用の例を紹介します。
PHPでは、PDOでデータベースAPIが抽象化/標準化されており、SQLiteもPDO経由で使用できます。
[https://paiza.io/projects/2u1WkYJObi_VqdSH4bAVcQ?language=php 都道府県コード一覧表（PHP/PDO版）]:
exec(
    "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)"
);
$insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)");
$db->beginTransaction();
try {
    foreach (
        array_map(
            fn($line): array => explode("\t", $line),
            explode(
                "\n",
                <<bindParam(":n", $pref[0]);
        $insert->bindParam(":ja", $pref[1]);
        $insert->bindParam(":en", $pref[2]);
        $insert->execute();
    }
    $db->commit();
} catch (PDOException $e) {
    echo $e;
    $db->rollback();
    throw $e;
}
$insert = null;
$query = "PRAGMA table_info('都道府県コード一覧表')";
echo "$query", PHP_EOL;
foreach ($db->query($query)->fetchAll(PDO::FETCH_ASSOC) as $assoc) {
    $fields[] = $assoc['name'];
}
$assoc = null;
echo implode(',',$fields),PHP_EOL;
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22;";
echo "$query", PHP_EOL;
foreach ($db->query($query)->fetchAll(PDO::FETCH_ASSOC) as $assoc) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表 WHERE 都道府県 LIKE '%京都%';";
echo "$query", PHP_EOL;
foreach ($db->query($query)->fetchAll(PDO::FETCH_ASSOC) as $assoc) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
$assoc = null;
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表;";
echo "$query", PHP_EOL;
foreach ($db->query($query)->fetchAll(PDO::FETCH_ASSOC) as $assoc) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
$assoc = null;
echo PHP_EOL;
実行結果:
PRAGMA table_info('都道府県コード一覧表')
都道府県コード,都道府県,prefectures
SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22;
都道府県コード:22, 都道府県:静岡県, prefectures:Shizuoka
SELECT * FROM 都道府県コード一覧表 WHERE 都道府県 LIKE '%京都%';
都道府県コード:13, 都道府県:東京都, prefectures:Tokyo
都道府県コード:26, 都道府県:京都府, prefectures:Kyoto
SELECT * FROM 都道府県コード一覧表;
都道府県コード:1, 都道府県:北海道, prefectures:Hokkaido
都道府県コード:2, 都道府県:青森県, prefectures:Aomori
都道府県コード:3, 都道府県:岩手県, prefectures:Iwate
都道府県コード:4, 都道府県:宮城県, prefectures:Miyagi
都道府県コード:5, 都道府県:秋田県, prefectures:Akita
都道府県コード:6, 都道府県:山形県, prefectures:Yamagata
都道府県コード:7, 都道府県:福島県, prefectures:Fukushima
都道府県コード:8, 都道府県:茨城県, prefectures:Ibaraki
都道府県コード:9, 都道府県:栃木県, prefectures:Tochigi
都道府県コード:10, 都道府県:群馬県, prefectures:Gumma
都道府県コード:11, 都道府県:埼玉県, prefectures:Saitama
都道府県コード:12, 都道府県:千葉県, prefectures:Chiba
都道府県コード:13, 都道府県:東京都, prefectures:Tokyo
都道府県コード:14, 都道府県:神奈川県, prefectures:Kanagawa
都道府県コード:15, 都道府県:新潟県, prefectures:Niigata
都道府県コード:16, 都道府県:富山県, prefectures:Toyama
都道府県コード:17, 都道府県:石川県, prefectures:Ishikawa
都道府県コード:18, 都道府県:福井県, prefectures:Fukui
都道府県コード:19, 都道府県:山梨県, prefectures:Yamanashi
都道府県コード:20, 都道府県:長野県, prefectures:Nagano
都道府県コード:21, 都道府県:岐阜県, prefectures:Gifu
都道府県コード:22, 都道府県:静岡県, prefectures:Shizuoka
都道府県コード:23, 都道府県:愛知県, prefectures:Aichi
都道府県コード:24, 都道府県:三重県, prefectures:Mie
都道府県コード:25, 都道府県:滋賀県, prefectures:Shiga
都道府県コード:26, 都道府県:京都府, prefectures:Kyoto
都道府県コード:27, 都道府県:大阪府, prefectures:Osaka
都道府県コード:28, 都道府県:兵庫県, prefectures:Hyogo
都道府県コード:29, 都道府県:奈良県, prefectures:Nara
都道府県コード:30, 都道府県:和歌山県, prefectures:Wakayama
都道府県コード:31, 都道府県:鳥取県, prefectures:Tottori
都道府県コード:32, 都道府県:島根県, prefectures:Shimane
都道府県コード:33, 都道府県:岡山県, prefectures:Okayama
都道府県コード:34, 都道府県:広島県, prefectures:Hiroshima
都道府県コード:35, 都道府県:山口県, prefectures:Yamaguchi
都道府県コード:36, 都道府県:徳島県, prefectures:Tokushima
都道府県コード:37, 都道府県:香川県, prefectures:Kagawa
都道府県コード:38, 都道府県:愛媛県, prefectures:Ehime
都道府県コード:39, 都道府県:高知県, prefectures:Kochi
都道府県コード:40, 都道府県:福岡県, prefectures:Fukuoka
都道府県コード:41, 都道府県:佐賀県, prefectures:Saga
都道府県コード:42, 都道府県:長崎県, prefectures:Nagasaki
都道府県コード:43, 都道府県:熊本県, prefectures:Kumamoto
都道府県コード:44, 都道府県:大分県, prefectures:Oita
都道府県コード:45, 都道府県:宮崎県, prefectures:Miyazaki
都道府県コード:46, 都道府県:鹿児島県, prefectures:Kagoshima
都道府県コード:47, 都道府県:沖縄県, prefectures:Okinawa
メモリー上にデータベースを構築し、問合わせと応答を表示しています。
PDO::prepare() を使うことで、プレースホルダー経由でSQLを実行し、SQLインジェクションの驚異を低減しています。
$変数 = null; を随所で行っているのは、PHPでは変数のスコープをループに閉じ込めることができないためで、特にループから脱走したリファレンスが関係したバグは原因の特定が困難になりがちです。
PDOとは別に、SQLite専用のSQLite3クラスも用意されています。
[https://paiza.io/projects/QhNiBQZ4tToKX73tuYbaMQ?language=php 都道府県コード一覧表（PHP/ベンダー固有コード版）]:
exec(
    "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)"
);
$insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)");
try {
    foreach (
        array_map(
            fn($line): array => explode("\t", $line),
            explode(
                "\n",
                <<bindParam(":n", $pref[0], SQLITE3_INTEGER);
        $insert->bindParam(":ja", $pref[1], SQLITE3_TEXT);
        $insert->bindParam(":en", $pref[2], SQLITE3_TEXT);
        $insert->execute();
    }
} catch (Exception $e) {
    echo "Caught exception: " . $e->getMessage();
}
$insert = null;
$query = "PRAGMA table_info('都道府県コード一覧表')";
echo "$query", PHP_EOL;
for (
    $result = $db->query($query);
    ($assoc = $result->fetchArray(SQLITE3_ASSOC));
    $fields[] = $assoc["name"]
) {}
$assoc = null;
echo implode(",", $fields), PHP_EOL;
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表 WHERE 都道府県コード = 22;";
echo "$query", PHP_EOL;
for (
    $result = $db->query($query);
    ($assoc = $result->fetchArray(SQLITE3_ASSOC));
) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
$assoc = null;
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表 WHERE 都道府県 LIKE '%京都%';";
echo "$query", PHP_EOL;
for (
    $result = $db->query($query);
    ($assoc = $result->fetchArray(SQLITE3_ASSOC));
) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
$assoc = null;
echo PHP_EOL;
$query = "SELECT * FROM 都道府県コード一覧表;";
echo "$query", PHP_EOL;
for (
    $result = $db->query($query);
    ($assoc = $result->fetchArray(SQLITE3_ASSOC));
) {
    array_walk($assoc, function(&$v, $k) { $v = "$k:$v"; });
    echo implode(",", $assoc), PHP_EOL;
}
$assoc = null;
echo PHP_EOL;