问题描述
我有一个 QTableView,其模型为 QSortFilterProxyModel,其模型为 QsqlRelationalTableModel。
当我通过 QSortFilterProxyModel 对查看的数据进行排序或过滤时,只有获取到的数据会受到影响。
我设法通过使用从表中获取所有数据来使其工作
if model.canfetchMore():
model.fetchMore()
问题是表中有大量记录(200,000 条记录)。
有没有其他方法可以在不从数据库中获取所有数据的情况下对代理模型进行排序和过滤?
解决方法
您可能使用 Sqlite,因为您必须使用 fetchMore()
。
当您处理大量记录时,一次从数据库加载大约 200.000 条记录的速度很慢,这不是问题 fetchMore()
,只是从数据库加载该数量的数据很慢。即使您在不需要使用 fetchMore()
的情况下使用 PostgreSQL,它也很慢。所以我使用一个技巧来获得所有记录,但不必等待太久。我将(同一个)表分成两个视图,我称之为父子视图:
import sys
import random
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtSql import *
class Window(QWidget):
def __init__(self,parent=None):
#QWidget.__init__(self,parent)
super(Window,self).__init__(parent)
self.search_label = QLabel("Name:")
self.search_lineedit = QLineEdit('')
self.parent_model = QSqlQueryModel(self)
self.refresh_parent()
self.parent_proxy_model = QSortFilterProxyModel()
self.parent_proxy_model.setSourceModel(self.parent_model)
self.parent_view = QTableView()
self.parent_view.setModel(self.parent_proxy_model)
self.parent_view.setSelectionMode(QTableView.SingleSelection)
self.parent_view.setSelectionBehavior(QTableView.SelectRows)
self.parent_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.parent_view.horizontalHeader().setStretchLastSection(True)
self.parent_view.verticalHeader().setVisible(False)
self.parent_view.setSortingEnabled(True)
self.parent_view.horizontalHeader().setSortIndicator(0,Qt.AscendingOrder)
self.parent_view.setAlternatingRowColors(True)
self.parent_view.setShowGrid(False)
#self.parent_view.verticalHeader().setDefaultSectionSize(24)
self.parent_view.setStyleSheet("QTableView::item:selected:!active { selection-background-color:#BABABA; }")
for i,header in enumerate(self.parent_headers):
self.parent_model.setHeaderData(i,Qt.Horizontal,self.parent_headers[self.parent_view.horizontalHeader().visualIndex(i)])
self.parent_view.resizeColumnsToContents()
self.child_model = QSqlQueryModel(self)
self.refresh_child()
self.child_proxy_model = QSortFilterProxyModel()
self.child_proxy_model.setSourceModel(self.child_model)
self.child_view = QTableView()
self.child_view.setModel(self.child_proxy_model)
self.child_view.setSelectionMode(QTableView.SingleSelection)
self.child_view.setSelectionBehavior(QTableView.SelectRows)
self.child_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.child_view.horizontalHeader().setStretchLastSection(True)
self.child_view.verticalHeader().setVisible(False)
self.child_view.setSortingEnabled(True)
self.child_view.horizontalHeader().setSortIndicator(1,Qt.AscendingOrder)
self.child_view.setAlternatingRowColors(True)
self.child_view.setShowGrid(False)
#self.child_view.verticalHeader().setDefaultSectionSize(24)
self.child_view.setStyleSheet("QTableView::item:selected:!active { selection-background-color:#BABABA; }")
for i,header in enumerate(self.child_headers):
self.child_model.setHeaderData(i,self.child_headers[self.child_view.horizontalHeader().visualIndex(i)])
self.child_view.resizeColumnsToContents()
search_layout = QHBoxLayout()
search_layout.addStretch()
search_layout.addWidget(self.search_label)
search_layout.addWidget(self.search_lineedit)
view_layout = QHBoxLayout()
view_layout.addWidget(self.parent_view)
view_layout.addWidget(self.child_view)
layout = QVBoxLayout(self)
layout.addLayout(search_layout)
layout.addLayout(view_layout)
#self.setLayout(layout)
self.parent_view.selectionModel().currentRowChanged.connect(self.parent_changed)
self.search_lineedit.textChanged.connect(self.search_changed)
self.parent_view.setCurrentIndex(self.parent_view.model().index(0,0))
self.parent_view.setFocus()
def refresh_parent(self):
self.parent_headers = ['Category']
query_string = "SELECT category FROM parent"
query = QSqlQuery()
query.exec(query_string)
self.parent_model.setQuery(query)
while self.parent_model.canFetchMore():
self.parent_model.fetchMore()
def refresh_child(self,category=''):
self.child_headers = ['Category','Name','Description']
query_string = ("SELECT category,name,description FROM child "
"WHERE child.category = '{category}'").format(category = category)
query = QSqlQuery()
query.exec(query_string)
self.child_model.setQuery(query)
while self.child_model.canFetchMore():
self.child_model.fetchMore()
def parent_changed(self,index):
if index.isValid():
index = self.parent_proxy_model.mapToSource(index)
record = self.parent_model.record(index.row())
self.refresh_child(record.value("parent.category"))
#self.child_view.scrollToBottom() # if needed
def search_changed(self,text):
query_string = ("SELECT category,name FROM child WHERE name = '{name}'").format(name = text)
query = QSqlQuery()
query.exec(query_string)
if query.next():
category = query.value('category')
start = self.parent_proxy_model.index(0,0)
matches = self.parent_proxy_model.match(start,Qt.DisplayRole,category,1,Qt.MatchExactly) # Qt.MatchExactly # Qt.MatchStartsWith
if matches:
print('parent matches')
index = matches[0]
self.parent_view.selectionModel().select(index,QItemSelectionModel.Select)
self.parent_view.setCurrentIndex(index)
self.refresh_child(category)
#------------------------------------------------
start = self.child_proxy_model.index(0,1)
matches = self.child_proxy_model.match(start,text,Qt.MatchExactly) # Qt.MatchExactly # Qt.MatchStartsWith
if matches:
print('child matches')
index = matches[0]
self.child_view.selectionModel().select(index,QItemSelectionModel.Select)
self.child_view.setCurrentIndex(index)
self.child_view.setFocus()
def create_fake_data():
categories = []
#import random
query = QSqlQuery()
query.exec("CREATE TABLE parent(category TEXT)")
for i in range(1,201):
category = str(i).zfill(3)
categories.append(category)
query.prepare("INSERT INTO parent (category) VALUES(:category)")
query.bindValue(":category",category)
query.exec()
query.exec("CREATE TABLE child(category TEXT,name TEXT,description TEXT)")
counter = 1
for category in categories:
for i in range(1,1001):
name = str(counter).zfill(6)
description = str(random.randint(1,100)).zfill(3)
counter += 1
query.prepare("INSERT INTO child (category,description) VALUES(:category,:name,:description)")
query.bindValue(":category",category)
query.bindValue(":name",name)
query.bindValue(":description",description)
query.exec()
def create_connection():
db = QSqlDatabase.addDatabase("QSQLITE")
#db.setDatabaseName('test.db')
db.setDatabaseName(":memory:")
db.open()
create_fake_data()
print('database is full,now starting a program...')
app = QApplication(sys.argv)
create_connection()
window = Window()
#window.resize(800,600)
#window.show()
window.showMaximized()
app.exec()
只有当您有一个属性(或一个属性的前缀,或多个属性)可以用作“类别”时才有可能。
这个程序加载缓慢,但只有数据库输入。一旦数据库已满,程序就会快速运行。这只是一个示例,在实际应用中,您的数据库已满。
使用此技巧,您可以处理 1.000.000 条记录,只需使用 1.000 x 1.000(或 200 x 5.000,或其他组合)。问题是搜索和过滤更难以这种方式实现。我展示了一种实现搜索的方法。
关于“为什么不使用SQL实现过滤和排序”注释,那是因为sql只能以一种顺序和一种(固定)过滤器返回数据,并且带有QSortFilterProxyModel
一旦将数据放入模型,您就可以按照自己喜欢的方式对数据进行额外排序和过滤,而且比使用 sql 更快。