見出し画像

データベース・SQLite3の事を1㍉も知らないのでChatGPTに聞いてみた

はじめに

タイトル通りデータベースに関する知識ゼロの私がPythonでSQLite3を使えるようになる為には何を学べばいいか、とChatGPTに聞いてみました。その返答を1ページにまとめたものです。
※目次以下の記載はすべてChatGPTの回答そのままなので、内容の妥当性は未確認です。



目次

  1. SQLite3の概要を学ぶ

    • SQLite3とは何か

    • SQLite3の特徴やメリット

    • SQLite3の制限や欠点

  2. SQLite3の基本操作を学ぶ

    • データベースの作成と削除

    • テーブルの作成と削除

    • テーブルへのデータの挿入、更新、削除

    • テーブルのデータの参照

  3. PythonからSQLite3にアクセスする方法を学ぶ

    • Pythonの標準ライブラリに含まれるsqlite3モジュールの使い方

    • SQLite3データベースに接続する方法

    • テーブルの作成、データの挿入、更新、削除、参照をPythonで行う方法

  4. SQLite3の応用的な操作を学ぶ

    • データベースのバックアップと復元

    • トランザクションの処理

    • インデックスの作成と利用

    • 外部キー制約の設定

    • 複数のテーブルを結合してデータを参照する方法



1.SQLite3の概要を学ぶ


SQLite3とは何か

SQLite3は、組み込みデータベース管理システムであり、C言語で実装されています。SQLite3は、パブリックドメインのソフトウェアであり、誰でも自由に利用することができます。また、軽量で、高速で、シングルユーザー専用のデータベースとして、様々なプラットフォームで利用されています。SQLite3は、ファイルベースのデータベースであり、すべてのデータは単一のファイルに格納されます。このため、非常に簡単にデプロイできます。


SQLite3の特徴やメリット

  • 軽量で、シングルユーザー専用のデータベースであるため、インストールや設定が非常に簡単です。

  • ファイルベースのデータベースであるため、様々なプラットフォームで利用することができます。

  • データベースの中身がファイルに格納されるため、データベースを転送する場合やバックアップする場合にも非常に簡単です。

  • SQLの標準構文をサポートしているため、標準的なSQLを用いてデータベースを操作することができます。

  • トランザクションや外部キー制約など、高度なデータベース機能をサポートしています。

  • パブリックドメインのソフトウェアであるため、商用利用も含めて自由に利用することができます。



SQLite3の制限や欠点

SQLite3は優れた特徴を持っていますが、いくつかの制限や欠点もあります。

  • 大規模なデータベースには向いていません。SQLite3はメモリ上で完結するため、大規模なデータベースの場合、性能の問題が生じることがあります

  • 複数のユーザーが同時にアクセスする場合、性能の問題が生じることがあります。SQLite3はシングルユーザー専用のデータベースのため、同時に複数のユーザーがデータベースを操作することはできません。

  • ファイルロックの制限: SQLite3はファイルベースのデータベースシステムであるため、ファイルロックの問題が発生する可能性があります。たとえば、ファイルがロックされている場合には、他のユーザーがそのファイルにアクセスできなくなる場合があります。

  • ストレージの制限: SQLite3はディスクスペースを節約するため、データを圧縮して格納する場合があります。これは、大量のデータを処理する場合には効果的ですが、処理に時間がかかる場合があります。

  • セキュリティの制限: SQLite3は、パスワード保護や暗号化などの高度なセキュリティ機能を提供していません。したがって、データの保護が必要な場合には、別のデータベースシステムを使用する必要があります。



2.SQLite3の基本操作を学ぶ


データベースの作成と削除

まず、SQLite3を使うにはsqlite3モジュールをインポートする必要があります。

import sqlite3

次に、データベースを作成するには、sqlite3.connect()関数を使用します。この関数には、作成するデータベースのファイル名を指定します。以下のコードは、example.dbという名前のデータベースを作成します。

conn = sqlite3.connect('example.db')

このコードを実行すると、example.dbというファイルが作成され、その中にデータベースが保存されます。

データベースを作成するときに指定するパラメータはありませんが、connect()関数にはいくつかのオプションがあります。たとえば、isolation_levelオプションを使用すると、トランザクションのレベルを指定することができます。

conn = sqlite3.connect('example.db', isolation_level=None)

データベースを削除するには、以下のコードを使用します。

import os

if os.path.exists('example.db'):
    os.remove('example.db')
else:
    print("The file does not exist")

このコードでは、os.path.exists()関数を使用して、example.dbファイルが存在するかどうかを確認しています。ファイルが存在する場合は、os.remove()関数を使用してファイルを削除します。ファイルが存在しない場合は、エラーメッセージを出力します。



テーブルの作成と削除

以下のようなコードでテーブルを作成することができます。

import sqlite3

# データベースに接続
conn = sqlite3.connect('example.db')

# カーソルを取得
c = conn.cursor()

# テーブルを作成
c.execute('''CREATE TABLE users
             (id INTEGER PRIMARY KEY AUTOINCREMENT,
              name TEXT NOT NULL,
              age INTEGER NOT NULL,
              email TEXT)''')

# コミット
conn.commit()

# 接続を閉じる
conn.close()

上記の例では、usersという名前のテーブルを作成しています。テーブルの列はidnameageemailの4つで、それぞれのデータ型や制約を指定しています。id列にはPRIMARY KEY制約が設定されており、自動的にユニークな値が生成されるようになっています。

次に、テーブルの削除方法です。以下のようなコードでテーブルを削除することができます。

import sqlite3

# データベースに接続
conn = sqlite3.connect('example.db')

# カーソルを取得
c = conn.cursor()

# テーブルを削除
c.execute('''DROP TABLE users''')

# コミット
conn.commit()

# 接続を閉じる
conn.close()

上記の例では、DROP TABLE文を使ってusersテーブルを削除しています。削除する前に必ずコミットを行ってから接続を閉じるようにしましょう。



テーブルへのデータの挿入、更新、削除

【データの挿入方法】 データを挿入するには、INSERT文を使用します。以下は、employeesテーブルに従業員のデータを挿入する例です。

import sqlite3

# データベースに接続
conn = sqlite3.connect('example.db')

# カーソルオブジェクトを作成
c = conn.cursor()

# データを挿入する
c.execute("INSERT INTO employees VALUES ('John Doe', 'john@example.com', 'Manager')")

# 変更をコミット
conn.commit()

# データベースとの接続を閉じる
conn.close()

上記の例では、employeesテーブルに従業員の名前、メールアドレス、役職を挿入しています。VALUES句には、挿入するデータの値をカンマ区切りで指定します。

【データを挿入するときに指定するパラメータ】 データを挿入するときに、変数から値を取得して挿入する場合は、プレースホルダーを使用します。以下は、プレースホルダーを使用してデータを挿入する例です。

import sqlite3

# データベースに接続
conn = sqlite3.connect('example.db')

# カーソルオブジェクトを作成
c = conn.cursor()

# 変数から値を取得して挿入する
name = 'Jane Doe'
email = 'jane@example.com'
title = 'Assistant Manager'
c.execute("INSERT INTO employees VALUES (?, ?, ?)", (name, email, title))

# 変更をコミット
conn.commit()

# データベースとの接続を閉じる
conn.close()

上記の例では、VALUES句にプレースホルダーを使用しています。その後のexecute()メソッドの引数には、SQL文の後ろに指定されたプレースホルダーに入る値を、タプルで指定しています。

【データの更新方法】
データを更新するには、UPDATE文を使用します。例えば、以下のようなemployeesテーブルがあるとします。

CREATE TABLE employees (
  id INTEGER PRIMARY KEY,
  name TEXT,
  age INTEGER,
  salary INTEGER
);

このテーブルのデータを更新するには、以下のようなUPDATE文を実行します。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# カーソルを取得する
cur = conn.cursor()

# データを更新する
cur.execute("UPDATE employees SET salary = ? WHERE name = ?", (50000, 'Alice'))

# 変更をコミットする
conn.commit()

# 接続を閉じる
conn.close()

この例では、employeesテーブルの中でnameが'Alice'のデータのsalaryを50000に更新しています。UPDATE文では、SET句で更新したいカラムと値を指定し、WHERE句で更新するレコードを条件で指定します。

【データの削除方法】
DELETE文を使用します。例えば、以下のようなemployeesテーブルがあるとします。

CREATE TABLE employees (
  id INTEGER PRIMARY KEY,
  name TEXT,
  age INTEGER,
  salary INTEGER
);

このテーブルのデータを削除するには、以下のようなDELETE文を実行します。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# カーソルを取得する
cur = conn.cursor()

# データを削除する
cur.execute("DELETE FROM employees WHERE name = ?", ('Alice',))

# 変更をコミットする
conn.commit()

# 接続を閉じる
conn.close()

この例では、employeesテーブルの中でnameが'Alice'のデータを削除しています。DELETE文では、FROM句で削除するテーブルを指定し、WHERE句で削除するレコードを条件で指定します。



テーブルのデータの参照

まず、データベースに接続します。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

次に、テーブルのデータを参照するために、SELECT文を実行します。以下のコードでは、SELECT文でbooksテーブルの全てのデータを取得しています。

# カーソルを取得する
cur = conn.cursor()

# SELECT文を実行する
cur.execute('SELECT * FROM books')

# 取得したデータを表示する
for row in cur:
    print(row)

次に、参照するデータを指定する方法です。SELECT文のWHERE句を使って、条件に合致するデータだけを取得します。以下の例では、booksテーブルからtitleが「Python入門」のデータを取得しています。

# titleが「Python入門」のデータを取得する
cur.execute('SELECT * FROM books WHERE title = ?', ('Python入門',))

# 取得したデータを表示する
for row in cur:
    print(row)

次に、テーブルのデータを条件によって絞り込む方法です。SELECT文のWHERE句を使って、条件に合致するデータだけを取得します。以下の例では、booksテーブルからpriceが1,000円以上のデータを取得しています。

# priceが1,000円以上のデータを取得する
cur.execute('SELECT * FROM books WHERE price >= ?', (1000,))

# 取得したデータを表示する
for row in cur:
    print(row)

最後に、テーブルのデータをソートする方法です。SELECT文のORDER BY句を使って、指定したカラムの値でソートします。以下の例では、booksテーブルからpriceで昇順にソートされたデータを取得しています。

# priceで昇順にソートされたデータを取得する
cur.execute('SELECT * FROM books ORDER BY price')

# 取得したデータを表示する
for row in cur:
    print(row)

これらのコードを実行することで、SQLite3でテーブルのデータを参照する方法を学ぶことができます。



3.PythonからSQLite3にアクセスする方法を学ぶ


Pythonの標準ライブラリに含まれるsqlite3モジュールの使い方


・モジュールのインポート方法

まずは、sqlite3モジュールをインポートします。以下のように書きます。

import sqlite3


・データベースへの接続方法

次に、SQLite3データベースに接続するために、connect()メソッドを使います。このメソッドは、データベースファイルへのパスを引数に取り、データベースに接続します。

pythonCopy codeconn = sqlite3.connect('example.db')

この例では、example.dbという名前のデータベースファイルに接続しています。

・SQLクエリの実行方法

接続したデータベースに対して、SQLクエリを実行するには、接続オブジェクトからcursor()メソッドを呼び出してカーソルオブジェクトを取得します。カーソルオブジェクトを使って、SQLクエリを実行します。以下は、テーブルの作成を行う例です。

c = conn.cursor()
c.execute('''CREATE TABLE stocks
             (date text, trans text, symbol text, qty real, price real)''')


・結果の取得方法

SQLクエリの実行結果を取得するには、fetchone()fetchall()fetchmany()メソッドを使います。fetchone()メソッドは、結果セットのうち1つの行を取得します。fetchall()メソッドは、全ての行を取得します。fetchmany()メソッドは、指定した数だけ行を取得します。
以下は、fetchall()メソッドを使って、テーブルの内容を取得する例です。

for row in c.execute('SELECT * FROM stocks'):
    print(row)


・トランザクションの扱い方

トランザクションは、複数のSQL文を一つの処理としてまとめる機能で、処理の中途でエラーが発生した場合に自動的にロールバック(処理を中断して元の状態に戻す)することができます。Pythonの標準ライブラリに含まれるsqlite3モジュールでもトランザクションをサポートしており、以下のように使用することができます。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')
cur = conn.cursor()

try:
    # トランザクションを開始する
    conn.execute('BEGIN')

    # SQL文を実行する
    cur.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Alice', 25))
    cur.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Bob', 30))

    # トランザクションをコミットする
    conn.commit()

except:
    # トランザクションをロールバックする
    conn.rollback()

finally:
    # データベースとの接続を切断する
    conn.close()

この例では、まず BEGIN 文でトランザクションを開始し、その後に複数のSQL文を実行しています。処理が正常に完了した場合は、COMMIT 文でトランザクションをコミットしてデータベースに反映させます。一方、処理の途中で例外が発生した場合は、ROLLBACK 文でトランザクションをロールバックしてデータベースを元の状態に戻します。最後に、finally 文でデータベースとの接続を切断します。

なお、トランザクションの扱い方にはいくつかのパターンがありますが、基本的な考え方は上記の例と同じです。特に、トランザクションを扱う場合は、事前にバックアップを取っておくなど、データの破損を防ぐための注意が必要です。



PythonでSQLite3データベースに接続する方法


・ローカルファイルにあるSQLite3データベースに接続する方法

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# データベースへの操作を行う

# データベースを閉じる
conn.close()

sqlite3.connect()関数を使用して、ローカルファイルにあるSQLite3データベースに接続します。connect()関数の引数には、接続先のデータベースファイルのパスを指定します。上記の例では、example.dbというファイルに接続しています。

・メモリ上に作成したSQLite3データベースに接続する方法

import sqlite3

# メモリ上にSQLite3データベースを作成し、接続する
conn = sqlite3.connect(':memory:')

# データベースへの操作を行う

# データベースを閉じる
conn.close()

sqlite3.connect()関数の引数に、:memory:という文字列を指定することで、メモリ上にSQLite3データベースを作成して接続することができます。メモリ上に作成されたSQLite3データベースは、プログラムが終了すると自動的に破棄されます。



テーブルの作成、データの挿入、更新、削除、参照をPythonで行う方法


まずはSQLite3データベースに接続し、カーソルを取得します。

import sqlite3

conn = sqlite3.connect('example.db')
cur = conn.cursor()

次に、テーブルを作成します。以下の例では、usersテーブルを作成し、idnameageの3つのカラムを定義しています。

cur.execute('''
    CREATE TABLE users (
        id INTEGER PRIMARY KEY,
        name TEXT,
        age INTEGER
    )
''')

データを挿入する場合は、以下のようにINSERT INTOを使って行います。?はプレースホルダーで、後で実際の値を指定します。

cur.execute("INSERT INTO users (name, age) VALUES (?, ?)", ('Alice', 20))

データを更新する場合は、以下のようにUPDATEを使います。WHEREで条件を指定します。

cur.execute("UPDATE users SET age = ? WHERE name = ?", (30, 'Alice'))

データを削除する場合は、以下のようにDELETE FROMを使います。WHEREで条件を指定します。

cur.execute("DELETE FROM users WHERE name = ?", ('Alice',))

データを参照する場合は、以下のようにSELECTを使います。fetchall()で全ての結果を取得します。

cur.execute("SELECT * FROM users")
rows = cur.fetchall()
for row in rows:
    print(row)

SQLクエリにパラメータを渡す場合は、プレースホルダーとして?を使い、実際の値をタプルで指定します。

cur.execute("SELECT * FROM users WHERE age > ?", (25,))

テーブルのJOIN操作を行う場合は、以下のようにJOINを使います。

cur.execute('''
    SELECT *
    FROM users
    JOIN orders ON users.id = orders.user_id
''')

以上がPythonでSQLite3データベースを操作する基本的な方法です。最後に、接続を閉じます。

conn.close()


4.SQLite3の応用的な操作を学ぶ


データベースのバックアップと復元

SQLite3のデータベースをバックアップする方法としては、単純にファイルをコピーする方法や、sqlite3コマンドラインツールを使う方法がありますが、Pythonのsqlite3モジュールでもバックアップが可能です。

以下は、PythonでSQLite3データベースをバックアップする例です。

import sqlite3
import shutil

# バックアップ元のデータベースファイル
db_file = "/path/to/database.db"
# バックアップ先のファイル名
backup_file = "/path/to/backup.db"

# データベースファイルを開く
conn = sqlite3.connect(db_file)

# バックアップファイルを作成する
shutil.copyfile(db_file, backup_file)

# データベースを閉じる
conn.close()

この例では、shutilモジュールのcopyfile関数を使って、単純にデータベースファイルをコピーしています。

一方、SQLite3データベースを復元するには、バックアップファイルを元のファイル名にリネームすればよいです。以下はその例です。

import os

# バックアップ元のファイル名
backup_file = "/path/to/backup.db"
# 復元先のデータベースファイル
db_file = "/path/to/database.db"

# バックアップファイルを元のファイル名にリネームする
os.rename(backup_file, db_file)

この例では、osモジュールのrename関数を使って、バックアップファイルを元のファイル名にリネームしています。注意点としては、復元前に元のデータベースファイルをバックアップしておくことが望ましいです。



トランザクションの処理

SQLite3でトランザクションを扱うには、以下のようにコードを書くことができます。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# カーソルを作成する
cur = conn.cursor()

try:
    # トランザクションを開始する
    conn.execute('BEGIN TRANSACTION')

    # SQLクエリを実行する
    cur.execute('INSERT INTO mytable (name, age) VALUES (?, ?)', ('Alice', 24))

    # コミットする
    conn.commit()

except Exception as e:
    # ロールバックする
    conn.rollback()
    print('Transaction rolled back:', e)

finally:
    # 接続を閉じる
    conn.close()

ここでは、BEGIN TRANSACTIONCOMMITを使用してトランザクションを開始し、コミットする方法を示しています。また、例外が発生した場合には、ROLLBACKを使用してトランザクションをロールバックすることができます。トランザクションをネストするには、同じ方法を使用してトランザクションを開始し、コミットまたはロールバックすることができます。ただし、SQLite3では、トランザクションのネストはサポートされていません。つまり、トランザクションの中で別のトランザクションを開始することはできません。



インデックスの作成と利用

SQLite3では、インデックスを作成してクエリの処理速度を高速化することができます。

まず、インデックスを作成する方法を見ていきましょう。以下の例では、employeesテーブルのlast_name列にインデックスを作成しています。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# カーソルを取得する
cur = conn.cursor()

# employeesテーブルにlast_name列にインデックスを作成する
cur.execute('CREATE INDEX idx_last_name ON employees(last_name)')

# コミットする
conn.commit()

# 接続を閉じる
conn.close()

インデックスを作成するには、CREATE INDEX文を使用します。CREATE INDEX文は、インデックス名、テーブル名、列名を指定する必要があります。

次に、インデックスを利用してクエリを高速化する方法を見ていきましょう。以下の例では、employeesテーブルのlast_name列に対してインデックスを利用したクエリを実行しています。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('example.db')

# カーソルを取得する
cur = conn.cursor()

# インデックスを利用してクエリを実行する
cur.execute('SELECT * FROM employees WHERE last_name = ?', ('Smith',))

# 取得したデータを表示する
for row in cur:
    print(row)

# 接続を閉じる
conn.close()

この例では、SELECT文のWHERE句でlast_name列を対象にインデックスを利用しています。インデックスを利用することで、データベースの全レコードをスキャンする必要がなくなり、クエリの処理速度が向上します。



外部キー制約の設定

SQLite3では、外部キー制約を設定して、テーブル間の整合性を保つことができます。

以下は、例として、employeesdepartmentsという2つのテーブルを作成し、employeesテーブルのdepartment_idカラムにdepartmentsテーブルのidカラムを外部キー制約として設定する方法です。

import sqlite3

# データベースに接続する
conn = sqlite3.connect('sample.db')

# employeesテーブルを作成する
conn.execute('''CREATE TABLE employees
             (id INTEGER PRIMARY KEY,
              name TEXT NOT NULL,
              age INTEGER,
              department_id INTEGER,
              FOREIGN KEY (department_id) REFERENCES departments(id))''')

# departmentsテーブルを作成する
conn.execute('''CREATE TABLE departments
             (id INTEGER PRIMARY KEY,
              name TEXT NOT NULL)''')

# departmentsテーブルにデータを挿入する
conn.execute("INSERT INTO departments (name) VALUES ('Sales')")
conn.execute("INSERT INTO departments (name) VALUES ('Marketing')")
conn.execute("INSERT INTO departments (name) VALUES ('Engineering')")

# employeesテーブルにデータを挿入する
conn.execute("INSERT INTO employees (name, age, department_id) VALUES ('Alice', 25, 1)")
conn.execute("INSERT INTO employees (name, age, department_id) VALUES ('Bob', 30, 2)")
conn.execute("INSERT INTO employees (name, age, department_id) VALUES ('Charlie', 35, 3)")

# 外部キー制約の有効化
conn.execute("PRAGMA foreign_keys = ON")

# テーブルの整合性を確認する
conn.execute("SELECT * FROM employees").fetchall()

上記の例では、employeesテーブルのdepartment_idカラムにFOREIGN KEY制約が設定され、参照先のテーブルはdepartmentsテーブルのidカラムとなっています。また、PRAGMA foreign_keys = ONというコマンドを実行することで、外部キー制約を有効にすることができます。

外部キー制約が有効になっている場合、テーブルの整合性が保たれていない場合は、INSERT、UPDATE、DELETEなどの操作が失敗します。テーブルの整合性を確認するには、上記の例のように、SELECT文でテーブルの内容を取得することができます。



複数のテーブルを結合してデータを参照する方法

まず、以下のように users テーブルと orders テーブルを作成し、それぞれにデータを挿入します。

import sqlite3

conn = sqlite3.connect('example.db')
cur = conn.cursor()

cur.execute('''CREATE TABLE users
               (id INTEGER PRIMARY KEY, name text, age integer)''')

cur.execute("INSERT INTO users (id, name, age) VALUES (?, ?, ?)", (1, 'Alice', 20))
cur.execute("INSERT INTO users (id, name, age) VALUES (?, ?, ?)", (2, 'Bob', 30))
cur.execute("INSERT INTO users (id, name, age) VALUES (?, ?, ?)", (3, 'Charlie', 40))

cur.execute('''CREATE TABLE orders
               (id INTEGER PRIMARY KEY, user_id integer, product text, quantity integer)''')

cur.execute("INSERT INTO orders (id, user_id, product, quantity) VALUES (?, ?, ?, ?)", (1, 1, 'apple', 3))
cur.execute("INSERT INTO orders (id, user_id, product, quantity) VALUES (?, ?, ?, ?)", (2, 1, 'orange', 2))
cur.execute("INSERT INTO orders (id, user_id, product, quantity) VALUES (?, ?, ?, ?)", (3, 2, 'banana', 1))
cur.execute("INSERT INTO orders (id, user_id, product, quantity) VALUES (?, ?, ?, ?)", (4, 3, 'melon', 5))

conn.commit()

これで users テーブルと orders テーブルが作成され、それぞれにデータが挿入されました。

次に、これらのテーブルを INNER JOIN して、ユーザーごとの注文合計数を求めるクエリを例示します。

cur.execute('''SELECT users.name, SUM(orders.quantity)
               FROM users INNER JOIN orders
               ON users.id = orders.user_id
               GROUP BY users.name''')

for row in cur:
    print(row)

このクエリでは、users テーブルと orders テーブルを ON 句で結合しています。結合条件として、users.id = orders.user_id を指定しています。これにより、users テーブルの id カラムと orders テーブルの user_id カラムが一致する行が結合されます。

結果として、以下のように、ユーザーごとの注文合計数が求められます。

('Alice', 5)
('Bob', 1)
('Charlie', 5)

他にも、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN を使って、テーブルを結合することができます。また、複数の条件を指定したり、サブクエリを使ったりすることもできます。



以上です。
ChatGPTの返答をコピペしただけの記事ですが、そこそこ教科書的な仕上がりになっているのではないでしょうか。
主に自分用の記事ですが、今後もChatGPT関連をこのnoteアカウントで書き留めていくかもしれません。


この記事が気に入ったらサポートをしてみませんか?