Qt - QTreeView and SQL - Performance advice - c++

I have an app where I want to show SQL query results in a hierarchical structure. I have something work that is based on this example.
The main part of my code where the tree nodes are created currently looks like this:
void TreeModel::setupModelData(TreeItem *parent)
{
QList<TreeItem*> parents;
QList<int> indentations;
parents << parent;
QList<QVariant> columnData;
QVector<QString> vecFileNames = getFileNames();
QVector<QString> vecTableNames = getTableNames();
for(int i = 0; i < vecFileNames.size(); i++)
{
columnData.clear();
columnData << vecFileNames[i];
parents.last()->appendChild(new TreeItem(columnData, parents.last()));
int childCount = parents.last()->childCount() - 1;
parents << parents.last()->child(childCount); //add the current parent's last child as a parent
for(int j = 0; j < vecTableNames.size(); j++)
{
columnData.clear();
columnData << vecTableNames[j];
parents.last()->appendChild(new TreeItem(columnData, parents.last()));
QVector<QString> vecTableValues = getTableValues(&vecTableNames[j]);
int childCount = parents.last()->childCount() - 1;
parents << parents.last()->child(childCount); //add the current parent's last child as a parent
for(int k = 0; k < vecTableValues.size(); k++)
{
columnData.clear();
columnData << vecTableValues[j];
parents.last()->appendChild(new TreeItem(columnData, parents.last()));
}
}
parents.pop_back();
}
}
QVector<QString> TreeModel::getFileNames()
{
db.open();
QVector<QString> vecFileNames;
QSqlQuery query(db);
QString strQuery = "SELECT PK_fileName FROM fileproperties";
query.prepare(strQuery);
if(query.exec() == true)
{
while(query.next())
{
vecFileNames.push_back(query.value(0).toString());
}
}
db.close();
return vecFileNames;
}
However, it is incredibly slow retrieving 2000 queries worth of data.
Can anyone suggest another approach to the one I'm using now?

You should implement function hasChildren() and use lazy population of model data. You should basically read this article and documentation of QAbstractItemModel class (especially canFetchMore() function).

I would guess that the perfomance suffers by inserting 2000 entries individually, triggering 2000 view updates, maybe 2000 sorts etc... you should provide a way of adding data to your model that takes "batches" of items and only signals changes once...

For MS SQL Servers I always use QSqlQuery::setForward(true) to speed up the queries by up to 10 times.
This "forward" mode just disables the row caches and forces the sql driver to request all results as one fat reply, instead of getting the query results as multiple parts (one ore more rows).
I discovered the problem with a MS SQL Server 2005+2008 with more than 10 million entries, where I searched for only 200-400 entries for a special day to display them inside a QTableView using an QSqlTableModel.
With the forward mode enabled my query times went from over 10s to only 200-300 milliseconds - on a database with over 10 million entries!
Example:
QSqlQuery query(database);
query.setForwardOnly(m_bForwardOnly);
query.exec(statement);
if ( query.lastError().isValid() || database.lastError().isValid() ) {
...evaluate the results...
}

Related

QtableView and model in an infinite while loop

I have a QTableview I have a model attached to it. I have a infinite while loop in which I send and receive messages from a server. When I receive a message, I want to add a row to my table.
My problem is that I can't get this idea work. When I do while (true) it doesn't work.
i would be appriciate it if someone could help me.
Here is the piece of code that I am talking about:
model = new QStandardItemModel(4,1,this);
ui->tableView->setModel(model);
ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
while (true)
{
// Server response
reciever_input.wait_for_recieve();
std::vector<std::string> res = decode_msg(_serverResponse.front());
unsigned int it;
it = 8;
if (res[0] == "Y")
{
int row = 0;
//here I want to add rows in my table
for (; it < res.size(); it++, row++)
{
std::string temp = res[it]; // this is the string that I want to add
QString qtemp = QString::fromUtf8(temp.c_str());
QModelIndex index = model->index(row,0,QModelIndex());
model->setData(index,qtemp);
}
}
if (res[0] == "L")
break;
}
Have you tried to make a neu Thread for the server connection? I think you should run it as a second thread in the background and in case of neu datas update your model.
you can find qt doc here
you can also find also good examples in youtube
additional idea:
MarKS has right, something is missing in your code. Use this code to insert row at the end of model:
int rowNr;
rowNr = model->rowCount();
model->insertRow(rowNr); //insert new row
model->setData(model->index(rowNr, 1), qtemp ); //in Column 1 - your string qtemp
model->submitAll(); //to hand over
You are missing insertRow() inside your for loop. Please read through the documentation here.
I haven't tested the code myself, but it should look something similar to the code below:
while (true)
{
// Server response
reciever_input.wait_for_recieve();
std::vector<std::string> res = decode_msg(_serverResponse.front());
unsigned int it;
it = 8;
if (res[0] == "Y")
{
int row = 0;
//here I want to add rows in my table
for (; it < res.size(); it++, row++)
{
std::string temp = res[it]; // this is the string that I want to add
QString qtemp = QString::fromUtf8(temp.c_str());
// insert row into the model
model->insertRow(row, new QStandardItem(qtemp));
}
}
if (res[0] == "L")
break;
}
EDIT 1:
Setting int row = 0 will overwrite the rows every time you receive a new std::vector<std::string>. It is upto you how you want to use this piece of code.

How to get the values in QModelIndexList in qt?

I have a QTableView that enables MultiSelection selectionMode following a SelectRows behavior as follow:
QSqlQueryModel model = db_manager->get_all();
ui->tableView->setModel(model);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableView->setSelectionMode(QAbstractItemView::MultiSelection);
ui->tableView->show();
The selection is working properly before which the indexes of theses selected rows are put into QModelIndexList as follow:
selectedRowsIndexesList = ui->tableView->selectionModel()->selectedRows();
Now I need to extract the information where these indexes are pointing to. I do this at the moment manually as follow:
qDebug() << model->index(0,0).data().toString();
I change the first "0" manually. But I need to automate the process through a for-loop statement like this:
for (int i = 0; i < selectedRowsIndexesList.size(); i++){
qDebug() << model->index(??,0).data().toString();
}
How can I do that ?
You already have the indexes in a list, so why go back to the model?
You can simply access the data using the indexes you've stored:
for (int i = 0; i < selectedRowsIndexesList.size(); i++){
qDebug() << selectedRowsIndexesList[i].data().toString();
}
The QModelIndexList is just a typedef as per the documentation. It is just a synonym for QList<QModelIndex> and you can iterate through the indexes as you would do through any QList variables.
for(int i = 0; i < mindex_list.size(); i++)
qDebug() << mindex_list.at(i).toString();
Or something similar.

Fastest way to get data from QSqlTableModel and put in QVector<QPointF>

I need to copy two columns from an QSqlTableModel and put into a QVector.
I'm trying this:
ParameterData pd;
QSqlTableModel *m = mapTbModels.value(table);
QList<QSqlField> parameterList = getFields(table);
for (int j = 0; j <parameterList.size(); j++) {
QSqlField f = parameterList[j];
QVector<QPointF> v;
if (f.type() != QVariant::Int)
continue;
else {
pd.name = f.name();
timer.start();
for (int i = 0; i < m->rowCount(); i++)
v << value(m, i, f.name());
qDebug() << "Database" << timer.elapsed();
}
pd.data = v;
pd.table = table;
emit data(pd);
emit status(QString::number(j*100/parameterList.size()));
QCoreApplication::processEvents();
}
What's the fastest way?
I once made some performance tests with Qt 5.2 and postgres to check the qsql features. It turned out, that using QSqlTableModel is around 1000 times slower than using own SQL queries like
QSqlQuery query("",db);
query.prepare("SELECT a.fueltype_longname, coalesce(a.bunkerlog_amount,0) FROM (\
SELECT f.fueltype_longname, b.bunkerlog_amount, rank() OVER (PARTITION BY b.bunkerlog_fueltype ORDER BY b.bunkerlog_id DESC) FROM data.t_bunkerlog2 b\
RIGHT JOIN data.t_fueltype f ON b.bunkerlog_fueltype = f.fueltype_longname \
) a WHERE a.rank=1");
query.exec();
So for a data intensive app use query/prepare/exec. If you just want so show simple tables to the user, use QSqlTableModel etc. for convenience.

QTableView export to .csv number of rows fetched is limited to only 256

I wrote a gui program which will connect to an oracle db an retrieve data after a query is typed. the retrieved data is shown in a QTableView table model widget. and later the result of the QTableView is exported to a .csv file
QString MyQuery = ui->lineQuery->text();
db.open();
QSqlQuery query(MyQuery,db);
if(query.exec())
{
qDebug()<<QDateTime::currentDateTime()<<"QUERY SUCCESS ";
ui->queryButton->setStyleSheet("QPushButton {background-color: rgb(0, 255, 0);}");
this->model=new QSqlQueryModel();
model->setQuery(MyQuery);
ui->tableViewOra->setModel(model);
QString textData;
int rows=model->rowCount();
int columns=model->columnCount();
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
textData += model->data(model->index(i,j)).toString();
textData += ", "; // for .csv file format
}
textData += "\n"; // (optional: for new line segmentation)
}
QFile csvfile("/home/aj/ora_exported.csv");
if(csvfile.open(QIODevice::WriteOnly|QIODevice::Truncate))
{
QTextStream out(&csvfile);
out<<textData;
}
csvfile.close();
}
now the problem is, during a query I observed there are 543 rows visible in the QTableView (which is correct cause there are 543 entries). But when the .csv file is exported, only 256 rows are there.
Is there any variable size constraint that I am unaware about ???
Which variables should be taken care of if I want to export .csv files of upto 1000 rows approx ??? thanks.
I think when you first read model->rowCount() the model has not fetched all the results completely. Although it will fetch more later when table view is displayed resulting in a full display of rows in the table view.
Try to use QSqlQueryModel::fetchMore before reading the row count :
while (model->canFetchMore())
model->fetchMore();
int rows=model->rowCount();
int columns=model->columnCount();
[Additional information from Tay2510]:
You could just change the file open flag
if(csvfile.open(QIODevice::WriteOnly|QIODevice::Truncate))
to
if(csvfile.open(QIODevice::WriteOnly))
The former will overwrite the same file while the latter append data on it.

How to efficiently select a subset of rows of a QTableView that match certain criteria?

I have a QTableView using a QSqlTableModel.
In the underlying database (postgresql) table there is a timestamp column.
How can I select all rows in the selection model where the underlying timestamp column is NULL?
Pointers in the right direction would help.
UPDATE:
The main issue I have been having is performance. Each method I have tried results in two performance issues.
The first is that the call to selectionModel()->select(selection) takes 30 seconds for about 5,000 selected records. It seems to be emitting the selection changed signal for each row. Even if the signal handlers are disabled it still takes 10 seconds.
The second performance issue is that even after the view is updated with the selected rows, trying to scroll the view is very slow and lags. My guess is that the selection model is made up of 5,000 individual selections rather than just the minimum number of selection ranges.
In the data that I am experimenting on the selection is contiguous; so it should be able to be represented as a single selection range. If I simply call tableView->selectAll(), then this is very fast.
What I was wondering is if there is a canonical, efficient way of selecting a bunch of matching rows. Or perhaps there is a flaw in my code that is causing the performance regression. Is there a way to use a QSortFilterProxyModel as suggested by count0 to accomplish this? I would like the view to display all rows, but have the matching ones selected.
Here is the code snippet for the last method that I tried:
void MainWindow::selectNullTimestamp()
{
QModelIndex start = model->index(0, TIMESTAMP_COLUMN);
QModelIndexList indexes = model
->match(start, Qt::DisplayRole,
QVariant(QString("")),
-1,
Qt::MatchFixedString);
QItemSelectionModel* selection_model = ui->tableView->selectionModel();
QItemSelection selection;
foreach(QModelIndex index, indexes) {
QModelIndex left =
model->index(index.row(), 0);
QModelIndex right =
model->index(index.row(),
NUM_COLUMNS - 1);
QItemSelection sel(left, right);
selection.merge(sel, QItemSelectionModel::Select);
}
selection_model->select(selection, QItemSelectionModel::Select);
}
May be too late, but it can always help!
I faced this issue, and I think that using QItemSelection can efficiently solve the problem
Example:
assuming that your table view is called myTableView and you want to select even rows [0, 2, 4, 6, ...]
myTableView->clearSelection();
QItemSelection selection;
for (int i = 0 ; i < myTableView->rowCount(); i += 1)
{
QModelIndex leftIndex = myTableView->model()->index(i, 0);
QModelIndex rightIndex = myTableView->model()->index(i, myTableView->columnCount() -1);
QItemSelection rowSelection(leftIndex, rightIndex);
selection.merge(rowSelection, QItemSelectionModel::Select);
}
myTableView->selectionModel()->select(selection, QItemSelectionModel::Select);
Use QSortFilterProxyModel inbetween you model and the view.
To avoid confusion TripsListView is a QTableView in conjunction with a QSqlTableModel.
This is a query approach. As for performance it does only what it has to do.
QModelIndexList indexesAll;
QItemSelection selection;
int i = 0;
QSqlQuery query;
query.exec(qs);
salect = true;
while ( query.next() ) {
if ( salect ) {
TripsListView->clearSelection ();
TripsListView->setSelectionMode( QAbstractItemView::MultiSelection );
TripsListView->selectRow(0);
indexesAll = TripsListView->selectionModel()->selectedIndexes ();
salect = false;
}
for ( i = i; i < TripsModel->rowCount(); i++ ) {
if ( TripsModel->data(indexesAll[0].sibling(i, 0), Qt::DisplayRole).toInt() == query.value(0).toInt() ) {
QModelIndex left = TripsModel->index( i, 0 );
// I like this selection code.
QModelIndex right = TripsModel->index( i, 12 );
QItemSelection sel(left, right);
selection.merge(sel, QItemSelectionModel::Select);
break;
}
}
}
if ( salect ) {
}
else {
TripsListView->clearSelection ();
SearchTripsSelMod->select(selection, QItemSelectionModel::Select);
}
You'll need to determine which rows to select. To do so, loop through the entire model and determine which rows have NULL as the timestamp. To access the model's data, you can use QSqlQueryModel::record().
You'll need to create a custom selection. The current selection is represented by the QItemSelectionModel class. You can access the table view's slection via QAbstractItemView::selectionModel() and QAbstractItemView::setSelection().
In response to your code update, you could do without the loop if you use QAbstractItemView::SelectRows. Just use the selection you received from QAbstractItemModel::match().
As for speed, do check the efficiency of the table on a release build -- I know that debug/release build differences are big for cases like yours. If you're using Qt 4.5 you'll notice a speed-up too, as they did work to improve elements like tables in that release.