PyQt

【PyQt】QtSqlのQSqlQueryModelの使い方

【PyQt】QtSqlのQSqlQueryModelの使い方

PythonのGUIツールキットであるPyQtで提供されているQtSqlQSqtQueryModelの使用方法について紹介します。

QSqlQueryModel

PythonのGUIツールキットであるPyQtでは、ベースとなるGUI開発機能の他にも非常に多くの機能が提供されています。その中でデータベース処理について提供されているのがQtSqlです。

QtSqlの中には様々な機能がありますが、QSqlQueryModelは、データベースに対してクエリを発行したデータをモデルデータとして簡単に扱えるようになっているもので、QTableViewというビューとつなげることで簡単にデータベース内容をテーブルとして画面表示できるようになります。

本記事では、QtSqlの中のQSqlQueryModelの使用方法について説明します。

Note

PyQtではModel-Viewの考え方に基づく実装ができるようになっており、QSqlQueryModelも便利に使用できるモデルの一つです。Model-Viewの考え方の概要については「Model-Viewを使ったアプリケーション開発」でまとめていますので興味があれば参考にしてください。

SQLiteデータベースでの使用例

SQLiteは軽量で優れたオープンソースのリレーショナルデータベースです。Pythonでも標準ライブラリとして使用可能になっています。SQLiteをPythonで扱う基本は「SQLiteの基本的な使い方」でまとめていますので興味があれば参考にしてください。

以降では、SQLiteデータベースの内容を読み込みテーブルに表示する簡単な例を用いてQSqlQueryModelの基本的な使い方を説明します。

サンプルとなるSQLiteのデータは以下プログラム(make_testdb.py)を実行して、test.dbという名前のファイルで用意しておくことにします。このDBは、product_infoという製品情報を持った非常にシンプルなテーブルの想定です。なお、他の任意のDBを用意して以降のプログラムを適宜読み替えて試してもらって構いません。

import sqlite3

conn = sqlite3.connect("test.db")
curs = conn.cursor()

print("===== CREATE TABLE")
# テーブルを作成する
curs.execute(
    "CREATE TABLE product_info ("
    "id INTEGER PRIMARY KEY AUTOINCREMENT"
    ", product_code VARCHAR"
    ", client VARCHAR"
    ", price INTEGER"
    ", create_date_time TIMESTAMP DEFAULT (datetime(CURRENT_TIMESTAMP,'localtime')))"
)
# 作成したテーブルをコミットする
conn.commit()

print("===== INSERT")
insert_sql = (
    "INSERT INTO product_info(product_code, client, price) VALUES(?, ?, ?)"
)
curs.execute(insert_sql, ("A", "顧客1", 1000))
curs.execute(insert_sql, ("A", "顧客2", 2000))
curs.execute(insert_sql, ("B", "顧客1", 1000))
curs.execute(insert_sql, ("B", "顧客3", 4000))
curs.execute(insert_sql, ("C", "顧客1", 2000))
# コミットする
conn.commit()

print("===== SELECT")
# データを検索する
curs.execute("SELECT * FROM product_info")
rows = curs.fetchall()
print(rows)

# カーソルとコネクションをクローズする
curs.close()
conn.close()

実装例

SQLiteデータベースに対してQSqlQueryModeを使用したデータ表示のサンプルプログラムは以下の通りです。

import sys

from PyQt6 import QtCore as qtc
from PyQt6 import QtGui as qtg
from PyQt6 import QtSql as qts
from PyQt6 import QtWidgets as qtw


class MainWindow(qtw.QWidget):
    """メインウィンドウ"""

    def __init__(self):
        super().__init__()
        # 画面タイトルの設定
        self.setWindowTitle("QSqlQueryModel")
        # 画面サイズの設定
        self.resize(640, 360)
        # DB接続
        self.db = None
        # レイアウト
        layout = qtw.QVBoxLayout()
        self.setLayout(layout)

        # データベース接続
        self.db_connect()
        # 必要テーブルの存在チェック
        required_tables = {"product_info"}
        self.table_check(required_tables)

        # モデル(QSqlQueryModel)の作成
        self.product_info_model = qts.QSqlQueryModel()
        # モデルが取得するクエリを設定
        self.product_info_model.setQuery(
            "SELECT * FROM product_info ORDER BY id"
        )

        # 列名を設定
        column_names = ["ID", "製品コード", "顧客", "価格", "作成日付"]
        self.set_column_names(column_names)

        # ビュー(QTableView)を作成
        self.product_list_view = qtw.QTableView()
        # 作成したモデルを設定
        self.product_list_view.setModel(self.product_info_model)

        # ビューをレイアウトに追加
        self.layout().addWidget(self.product_list_view)

        # 画面表示
        self.show()

    def db_connect(self):
        """DB接続"""
        # データベースへの接続
        self.db = qts.QSqlDatabase.addDatabase("QSQLITE")
        print(f"DBドライバー一覧: {self.db.drivers()}")
        self.db.setDatabaseName("test.db")
        if not self.db.open():
            error = self.db.lastError().text()
            qtw.QMessageBox.critical(
                None,
                "DB接続エラー",
                "データベースファイルを開けませんでした。",
            )
            sys.exit(1)

    def table_check(self, required_tables):
        """テーブルが対象DBに存在するかをチェックする

        Args:
            required_tables: 必要テーブルの集合(set)
        """
        # データベースの情報を取得して対象テーブルの存在をチェック
        tables = self.db.tables()
        print(f"テーブル一覧: {tables}")
        missing_tables = required_tables - set(tables)
        if missing_tables:
            qtw.QMessageBox.critical(
                None,
                "DB存在確認エラー",
                f"必要なデータが存在しません: {missing_tables}",
            )
            sys.exit(1)

    def set_column_names(self, columns):
        """列名設定

        Args:
            columns: 列名リスト
        """
        for i, name in enumerate(columns):
            self.product_info_model.setHeaderData(
                i, qtc.Qt.Orientation.Horizontal, name
            )


def main():
    """メイン関数"""
    app = qtw.QApplication(sys.argv)
    mv = MainWindow()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

【実行結果】

QSqlQueryModelサンプル SQLiteデータベース

上記の結果を見てもわかるようにプログラムは、SQLiteデータベースのproduct_infoテーブルのデータを全て取得してきてテーブルとして表示するだけのシンプルなプログラムです。

詳細説明

以降で上記プログラム内のポイントについて抜粋しながら内容を説明をしていきます。なお、プログラムの全体構造については「QWidgetを継承した画面開発のテンプレート」をもとにしていますので説明は省略します。

コンストラクタ __init__()
class MainWindow(qtw.QWidget):
    """メインウィンドウ"""

    def __init__(self):
        super().__init__()
        # 画面タイトルの設定
        self.setWindowTitle("QSqlQueryModel")
        # 画面サイズの設定
        self.resize(640, 360)
        # DB接続
        self.db = None
        # レイアウト
        layout = qtw.QVBoxLayout()
        self.setLayout(layout)

        # データベース接続
        self.db_connect()
        # 必要テーブルの存在チェック
        required_tables = {"product_info"}
        self.table_check(required_tables)

        # モデル(QSqlQueryModel)の作成
        self.product_info_model = qts.QSqlQueryModel()
        # モデルが取得するクエリを設定
        self.product_info_model.setQuery(
            "SELECT * FROM product_info ORDER BY id"
        )

        # 列名を設定
        column_names = ["ID", "製品コード", "顧客", "価格", "作成日付"]
        self.set_column_names(column_names)

        # ビュー(QTableView)を作成
        self.product_list_view = qtw.QTableView()
        # 作成したモデルを設定
        self.product_list_view.setModel(self.product_info_model)

        # ビューをレイアウトに追加
        self.layout().addWidget(self.product_list_view)

        # 画面表示
        self.show()

コンストラクタでの画面表示までの流れとして以下のようになっています。

  1. データベースへ接続:db_connect
  2. 必要テーブルの存在をチェック:table_check
  3. モデル(QSqlQueryModel)の生成と検索用クエリ(SQL)の設定
  4. 表示列名を設定:set_column_names
  5. ビュー(QTableView)の生成と生成したモデル(QSqlQueryModel)のビューへの設定
  6. ビューをレイアウトに追加して画面表示

上記の中で、db_connect、table_check、set_column_namesは、処理を分割して独自に作成しているメソッドですので以降で詳細内容を説明します。

データベースへ接続:db_connect
    def db_connect(self):
        """DB接続"""
        # データベースへの接続
        self.db = qts.QSqlDatabase.addDatabase("QSQLITE")
        print(f"DBドライバー一覧: {self.db.drivers()}")
        self.db.setDatabaseName("test.db")
        if not self.db.open():
            error = self.db.lastError().text()
            qtw.QMessageBox.critical(
                None,
                "DB接続エラー",
                "データベースファイルを開けませんでした。",
            )
            sys.exit(1)

データベースへの接続をしているのがdb_connectメソッドです。

        # データベースへの接続
        self.db = qts.QSqlDatabase.addDatabase("QSQLITE")
        print(f"DBドライバー一覧: {self.db.drivers()}")

データベースへアクセスするためにはドライバを設定して準備をします。”QSQLITE”の部分でSQLiteのドライバを指定しています。driversメソッドを使うとドライバ―として何が使用できるかが確認できます。私の環境でprintしてみたところ以下の結果でした。

DBドライバー一覧: ['QSQLITE', 'QODBC', 'QPSQL']

なお、Qtの公式ドキュメントのこちらを見る限りでは以下のようなドライバーがあるようです。私の環境だと3つしか出てこなかったため種類によっては別途環境の準備が必要なのかもしれません。また別途調べてみようかと思います。

ドライバ名内容
QDB2IBM DB2
QMYSQL / MARIADBMySQL or MariaDB
QOCIOracle
QODBCOpen Database Connectivity (ODBC)接続
QPSQLPostgreSQL
QSQLITESQLite
        self.db.setDatabaseName("test.db")
        if not self.db.open():
            error = self.db.lastError().text()
            qtw.QMessageBox.critical(
                None,
                "DB接続エラー",
                "データベースファイルを開けませんでした。",
            )
            sys.exit(1)

データベースをオープンする場合には、setDatabaseNameでデータベースファイルを指定し、open()でデータベースをオープンします。

データベースのオープンに失敗した場合にはif文の中に入ります。この時、エラー情報文字列はlastError().text()で取得できるので、QMessageBox.criticalでダイアログ表示し、exit(1)でプログラムを終了しています。

必要テーブルの存在をチェック:table_check
    def table_check(self, required_tables):
        """テーブルが対象DBに存在するかをチェックする

        Args:
            required_tables: 必要テーブルの集合(set)
        """
        # データベースの情報を取得して対象テーブルの存在をチェック
        tables = self.db.tables()
        print(f"テーブル一覧: {tables}")
        missing_tables = required_tables - set(tables)
        if missing_tables:
            qtw.QMessageBox.critical(
                None,
                "DB存在確認エラー",
                f"必要なデータが存在しません: {missing_tables}",
            )
            sys.exit(1)

処理の対象とするテーブルの存在チェックをしているのがtable_checkメソッドです。引数として使用するテーブル名の集合(set)を受け取ります。

        # データベースの情報を取得して対象テーブルの存在をチェック
        tables = self.db.tables()
        print(f"テーブル一覧: {tables}")

tablesメソッドを使用するとデータベースにあるテーブルの一覧を取得できます。取得結果をprintしてみていますが、既に作成してあるテーブル(product_info)とシーケンステーブル(sqlite_sequence)が取得できます。

テーブル一覧: ['product_info', 'sqlite_sequence']

テーブルの存在チェックをしているのが以下の部分です。

        missing_tables = required_tables - set(tables)
        if missing_tables:
            qtw.QMessageBox.critical(
                None,
                "DB存在確認エラー",
                f"必要なデータが存在しません: {missing_tables}",
            )
            sys.exit(1)

引数で指定されたテーブルの集合とデータベースにある差集合をとった時にもしテーブルが残る場合にはデータベースには当該テーブルは存在しないことになります。その場合は以降の処理をしても仕方がないためsys.exit(1)で処理を終了します。

モデル(QSqlQueryModel)の生成と検索用クエリ(SQL)の設定
        # モデル(QSqlQueryModel)の作成
        self.product_info_model = qts.QSqlQueryModel()
        # モデルが取得するクエリを設定
        self.product_info_model.setQuery(
            "SELECT * FROM product_info ORDER BY id"
        )

コンストラクタ(__init__)内のコードに戻りますが、本記事の中心であるQSqlQueryModelを生成しているのはこの部分です。QSqlQueryModelを生成し、setQueryでデータを取得するためのSQLを指定します。

今回は例としてSELECT *で全てデータを持ってきていますが、もちろん列名を指定したり、WHEREで条件を指定したりして必要なデータを持ってきてもらえば構いません。

表示列名を設定:set_column_names

QSqlQueryModelで普通に取得すると列名はテーブルの列名になります。テーブルの列名は英語名になっていることがほとんどだと思いますが、日本語にしたい場合があります。列名を後で設定しているのがset_column_namesメソッドです。

    def set_column_names(self, columns):
        """列名設定

        Args:
            columns: 列名リスト
        """
        for i, name in enumerate(columns):
            self.product_info_model.setHeaderData(
                i, qtc.Qt.Orientation.Horizontal, name
            )

このメソッドでは、引数として受け取ったcolumnsリストの列名リストで列名を設定しています。列名を設定しているのはQSqlQueryModelのsetHeaderDataメソッドです。引数として列番号、向き(列方向なのでHorizontal)、列名で指定します。

ビュー(QTableView)の生成と生成したモデル(QSqlQueryModel)のビューへの設定
        # ビュー(QTableView)を作成
        self.product_list_view = qtw.QTableView()
        # 作成したモデルを設定
        self.product_list_view.setModel(self.product_info_model)

コンストラクタ(__init__)内のコードに戻りますが、最後にQTableViewを生成し、上記で作成してきたQSqlQueryModelのモデルデータをsetModelで設定します。

ビューをレイアウトに追加して画面表示
        # ビューをレイアウトに追加
        self.layout().addWidget(self.product_list_view)

        # 画面表示
        self.show()

後は、ビューをレイアウトに追加して画面表示することでデータベースの情報をテーブルとして表示できます。

以上が、QSqlQueryModelとQTableViewを使ったSQLiteデータベースのデータ表示例でした。

PostgreSQLデータベースでの使用例

上記ではファイルでシンプルに管理できるSQLiteデータベースでの使用例を見てきました。他のDBMSでの使用例としてPostgreSQLデータベースでの使用例を紹介します。PostgreSQL以外のDBMSでもドライバ等の環境準備が必要な場合がありますが、同じように使用できるかと思います。

なお、PostgreSQLをPythonで扱う基本は「psycopg2を用いたPostgreSQLデータベースへのアクセス方法」でまとめていますので興味があれば参考にしてください。

以下で紹介するプログラムを動かす前提として以下のようなPostgreSQLデータベースが準備されているものとします。PostgreSQL自体の使い方については本記事では行いませんので別途調べていただければと思います。

  • PostgreSQLがローカルPCにインストールされているものとします。
  • DB「testdb」、スキーマ「work」が作成されているものとし、ポートはデフォルトの「5432」とします。
  • ユーザーとして「test」が作成されており、パスワードは「PAssw0rd」とします。
  • testユーザーは、workスキーマに対して以下のように変更できる権限を設定されているものとします。
    GRANT ALL ON SCHEMA work TO test;

データは以下プログラム(make_testdb_for_postgresql.py)を実行して、SQLiteの時の例と同じようなwork.product_infoという製品情報テーブルを作成しておきます。なお、他の任意のDBを用意して以降のプログラムを適宜読み替えて試してもらって構いません。

import psycopg2
import psycopg2.extras

host = "localhost"
port = 5432
dbname = "testdb"
user = "test"
password = "PAssw0rd"

# 接続を確立つする
con = psycopg2.connect(
    host=host,
    port=port,
    dbname=dbname,
    user=user,
    password=password,
)

# カーソルを作成する
cursor = con.cursor(cursor_factory=psycopg2.extras.DictCursor)

# テーブルを作成する
print("===== CREATE TABLE work.product_info")
create_sql = (
    "CREATE TABLE IF NOT EXISTS work.product_info"
    "("
    "id serial NOT NULL"
    ", product_code character varying(50)"
    ", client character varying(50)"
    ", price integer"
    ", create_date_time timestamp"
    ", PRIMARY KEY (id)"
    ")"
)
cursor.execute(create_sql)
# コミット
con.commit()

# テストデータを登録する
print("===== INSERT TESTデータ")
insert_sql = (
    "INSERT INTO work.product_info "
    "(product_code, client, price, create_date_time) "
    "VALUES(%s, %s, %s, current_timestamp)"
)
cursor.execute(insert_sql, ("A", "顧客1", 1000))
cursor.execute(insert_sql, ("A", "顧客2", 2000))
cursor.execute(insert_sql, ("B", "顧客1", 1000))
cursor.execute(insert_sql, ("B", "顧客3", 4000))
cursor.execute(insert_sql, ("C", "顧客1", 2000))
# コミット
con.commit()

print("===== SELECT データ登録確認")
# データを検索する
cursor.execute("SELECT * FROM work.product_info")
rows = cursor.fetchall()
result = []
for row in rows:
    result.append(dict(row))
print(result)

# コネクションの削除
cursor.close()
con.close()

実装例

PostgreSQLデータベースに対してQSqlQueryModeを使用したデータ表示のサンプルプログラムは以下の通りです。

import sys

from PyQt6 import QtCore as qtc
from PyQt6 import QtGui as qtg
from PyQt6 import QtSql as qts
from PyQt6 import QtWidgets as qtw


class MainWindow(qtw.QWidget):
    """メインウィンドウ"""

    def __init__(self):
        super().__init__()
        # 画面タイトルの設定
        self.setWindowTitle("QSqlQueryModel")
        # 画面サイズの設定
        self.resize(640, 360)
        # DB接続
        self.db = None
        # レイアウト
        layout = qtw.QVBoxLayout()
        self.setLayout(layout)

        # データベース接続
        self.db_connect()
        # 必要テーブルの存在チェック
        required_tables = {"work.product_info"}
        self.table_check(required_tables)

        # モデル(QSqlQueryModel)の作成
        self.product_info_model = qts.QSqlQueryModel()
        # モデルが取得するクエリを設定
        self.product_info_model.setQuery(
            "SELECT * FROM work.product_info ORDER BY id"
        )

        # 列名を設定
        column_names = ["ID", "製品コード", "顧客", "価格", "作成日付"]
        self.set_column_names(column_names)

        # ビュー(QTableView)を作成
        self.product_list_view = qtw.QTableView()
        # 作成したモデルを設定
        self.product_list_view.setModel(self.product_info_model)

        # ビューをレイアウトに追加
        self.layout().addWidget(self.product_list_view)

        # 画面表示
        self.show()

    def db_connect(self):
        """DB接続"""
        # PostgreSQLデータベースへの接続
        self.db = qts.QSqlDatabase.addDatabase("QPSQL")
        print(f"DBドライバー一覧: {self.db.drivers()}")
        self.db.setDatabaseName("testdb")
        self.db.setHostName("localhost")
        self.db.setPort(5432)
        self.db.setUserName("test")
        self.db.setPassword("PAssw0rd")

        # データベースをオープンし、エラーの場合は終了する
        if not self.db.open():
            error = self.db.lastError().text()
            qtw.QMessageBox.critical(
                None,
                "DB接続エラー",
                "データベースファイルを開けませんでした。",
            )
            sys.exit(1)

    def table_check(self, required_tables):
        """テーブルが対象DBに存在するかをチェックする

        Args:
            required_tables: 必要テーブルの集合(set)
        """
        # データベースの情報を取得して対象テーブルが存在するかチェックする
        tables = self.db.tables()
        print(f"テーブル一覧: {tables}")
        missing_tables = required_tables - set(tables)
        if missing_tables:
            qtw.QMessageBox.critical(
                None,
                "DB存在確認エラー",
                f"必要なデータが存在しません: {missing_tables}",
            )
            sys.exit(1)

    def set_column_names(self, columns):
        """列名設定

        Args:
            columns: 列名リスト
        """
        for i, name in enumerate(columns):
            self.product_info_model.setHeaderData(
                i, qtc.Qt.Orientation.Horizontal, name
            )


def main():
    """メイン関数"""
    app = qtw.QApplication(sys.argv)
    mv = MainWindow()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

【実行結果】

QSqlQueryModelサンプル PostgreSQLデータベース
PostgreSQLのドライバが実行できない場合の確認事項

PostgreSQLの環境ができていてもドライバが動かずデータベースのオープンに失敗する場合があります。その場合は、PostgreSQLのbinが環境変数のPathに設定されているか確認してみてください。

PostgreSQLのインストール先によりますが、例えば「C:\PostgreSQL\13.4\bin」のようなパスを環境変数Pathに設定すれば動作する可能性があります。

詳細説明

上記プログラムを見ていただけると分かりますが、実はSQLiteとほとんど同じで、db_connectメソッドの部分が異なります。そのため、db_connectメソッドの内容のみ説明をします。

データベースへ接続:db_connect
    def db_connect(self):
        """DB接続"""
        # PostgreSQLデータベースへの接続
        self.db = qts.QSqlDatabase.addDatabase("QPSQL")
        print(f"DBドライバー一覧: {self.db.drivers()}")
        self.db.setDatabaseName("testdb")
        self.db.setHostName("localhost")
        self.db.setPort(5432)
        self.db.setUserName("test")
        self.db.setPassword("PAssw0rd")

        # データベースをオープンし、エラーの場合は終了する
        if not self.db.open():
            error = self.db.lastError().text()
            qtw.QMessageBox.critical(
                None,
                "DB接続エラー",
                "データベースファイルを開けませんでした。",
            )
            sys.exit(1)

SQLiteの例との違いは、ホスト名等の指定の部分です。QtSqlのQSqlDatabaseでは各情報設定用のメソッドが用意されています。

メソッド名指定内容
setDatabaseNameデータベース名
setHostNameホスト名
setPortポート名
setUserNameユーザー名
setPasswordパスワード

上記のように各種データベースの情報を指定してアクセスすることによってPostgreSQLに対してもQSqlQueryModelを使用することができます。その他の処理については先ほども述べた通りSQLiteの場合と同じです。

まとめ

PythonのGUIツールキットであるPyQtで提供されているQtSqlQSqtQueryModelの使用方法について紹介しました。

PyQtの中でデータベース処理の機能として提供されているものがQtSqlです。QtSqlの中には他にも様々な機能がありますが、QSqlQueryModelはQTableViewとつなげることで簡単にデータベースの内容をテーブルとして画面表示できるようになります。

本記事では、SQLiteデータベースとPostgreSQLデータベースでの使用例を用いて使い方を説明しました。

なお、QSqlQueryModelは読み取り専用であるため、書き込み等を簡単に扱うためにはQSqlTableModelQSqlRelationalTableModelを使用する必要があります。これらの使い方はまた整理してみようかなと思っています。

PyQtで提供されているQtSqlはデータベース処理を非常に簡単にしてくれるものですので、是非他にも機能を調べて使用してみてもらえるとよいかと思います。