Удобный способ создания автономных/отключенных данных из QtSQL?

Я пытаюсь понять, как получить 2D-данные из базы данных и вставить их в виджеты. Эти данные квазистатичны — как только я их получу, нет необходимости оставаться на связи с базой данных. Кроме того, если я оставлю соединение открытым, но время ожидания истечет, это может привести к сбою моего приложения. Я хотел бы знать, есть ли в QtSql какой-то автономный контейнер данных или функциональность, о которой я не знаю.

Насколько я понимаю, Qt предлагает только QsqlTableModel и QAbstractTableModel в качестве контейнеров для данных. Я не нашел никаких способов сохранения данных в QsqlTableModel при разрыве соединения. А QAbstractTableModel нельзя использовать сам по себе; вы должны подклассифицировать его. Вполне вероятно, что я в конечном итоге пойду по пути создания подклассов, если не смогу найти более простое или элегантное решение. Здесь есть пример подкласса.

В качестве примеров кода приведенный ниже код заполняет два поля со списком из базы данных SQL Server. При нажатии второй кнопки и заполнении второго поля со списком первое поле со списком прерывается, и приложение прерывается. Я надеюсь, что есть простой способ получить данные и сохранить их в локальном контейнере, отключенном от базы данных.

from PyQt5.QtWidgets import (QApplication, QMainWindow, QComboBox, QPushButton,
                             QTableView, QTableView)
from PyQt5.QtSql import (QSqlQuery, QSqlQueryModel, QSqlDatabase)
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()

        self.setGeometry(300, 300, 600, 350)

        self.db = QSqlDatabase.addDatabase("QODBC", 'MyDB')
        self.db.setDatabaseName('Driver={SQL Server};Server=MyServer;Database=MyDB;Trusted_Connection=Yes;')

        self.cb1 = QComboBox(parent=self)
        self.cb1.setGeometry(25,25, 250, 50)

        self.cb2 = QComboBox(parent=self)
        self.cb2.setGeometry(300,25, 250, 50)

        self.button1 = QPushButton('^^^ Fill Table 1 ^^^', parent=self)
        self.button1.setGeometry(55,290, 200, 30)
        self.button1.clicked.connect(self.fillTable1)

        self.button2 = QPushButton('^^^ Fill Table 2 ^^^', parent=self)
        self.button2.setGeometry(320, 290, 200, 30)
        self.button2.clicked.connect(self.fillTable2)

    def fillTable1(self):
        print('self.db.open() ', self.db.open())
        sql = 'select * from aaa.car limit 10'
        query = QSqlQuery(self.db)
        print("query.exec_(sql) ", query.exec_(sql))

        self.t1model = QSqlQueryModel(parent = self)
        self.t1model.setQuery(query)
        self.cb1.setModel(self.t1model)
        self.cb1.setModelColumn(0)
        self.cb1.setView(QTableView(self.cb1))

    def fillTable2(self):
        print('self.db.open() ', self.db.open())
        sql = 'select * from aaa.car limit 10'
        query = QSqlQuery(self.db)
        print("query.exec_(sql) ", query.exec_(sql))
        self.t2model = QSqlQueryModel(parent = self)
        self.t2model.setQuery(query)
        self.cb2.setModel(self.t2model)
        self.cb2.setModelColumn(0)
        self.cb2.setView(QTableView(self.cb2))


app = QApplication(sys.argv)
main = MainWindow(None)
main.show()
sys.exit(app.exec_())

person bfris    schedule 09.06.2020    source источник
comment
Почему бы вам не создать копию таблицы SQL-сервера в sqlite и использовать sqlite?   -  person eyllanesc    schedule 09.06.2020
comment
Фу. Это одно из решений, но оно имеет много накладных расходов. Мне пришлось бы СОЗДАВАТЬ новые таблицы для каждого запроса, который я хочу запустить. Возможно, создание подкласса QAbstractTableModel требует наименьшего объема работы/накладных расходов.   -  person bfris    schedule 09.06.2020
comment
Моя логика такова: создайте зеркальную копию таблиц и обновляйте ее при необходимости (например, если исходная таблица изменяется)   -  person eyllanesc    schedule 09.06.2020


Ответы (1)


Вот базовый подкласс QAbstractTableModel. Вы передаете ему QSqlQuery в методе fillFromQuery, и он извлекает данные из результата запроса. Как написано, это плохо работает с QCompleter. Отредактировано. Теперь работает с QCompleter

from PyQt5.QtCore import Qt, QAbstractTableModel
from PyQt5.QtSql import QSqlQueryModel

class OfflineTableModel(QAbstractTableModel):
def __init__(self, parent, inputQuery=None):
    QAbstractTableModel.__init__(self, parent)
    self.mylist = []
    self.header = []
    
    if inputQuery is not None:
        self.fillFromQuery(inputQuery)

def fillFromQuery(self, inputQuery):
    # don't know how to get row/column count except from a QSqlQueryModel
    bogusModel = QSqlQueryModel()
    bogusModel.setQuery(inputQuery)
    rowCount = bogusModel.rowCount()
    colCount = bogusModel.columnCount()

    inputQuery.first()
    
    self.header = []
    for col in range(colCount):
        self.header.append(inputQuery.record().fieldName(col))

    self.mylist = []
    for row in range(rowCount):
        innerList = []
        for col in range(colCount):
            innerList.append(inputQuery.value(col))
        self.mylist.append(tuple(innerList))
        inputQuery.next()

def rowCount(self, parent=None):
    return len(self.mylist)
def columnCount(self, parent=None):
    return len(self.mylist[0])
def data(self, index, role):
    if not index.isValid():
        return None
    elif role not in (Qt.EditRole, Qt.DisplayRole):
        return None
    return self.mylist[index.row()][index.column()]
def dataRowCol(self, row, col):
    return self.mylist[row][col]
def headerData(self, col, orientation, role):
    if orientation == Qt.Horizontal and role not in (Qt.EditRole, Qt.DisplayRole):
        return self.header[col]
    return None

Преимущество этого метода по сравнению с созданием локальной базы данных Sqlite заключается в том, что OfflineTableModel будет предоставлять автономные данные виджету представления из любого запроса SQL SELECT.

Когда эта модель OfflineTable применяется к OP, исходную базу данных QSqlDatabase (db) можно открывать и закрывать несколько раз, не нарушая работу виджетов, которые уже загружены автономными данными. 41 строка кода. Похоже, должен быть более простой способ сделать то же самое. . .

person bfris    schedule 09.06.2020