Ruslan Polupan
Nov. 20, 2018, 6:42 p.m.

Qt - Selecting data from a QSqlQuery database in a QThread stream and creating a QAbstractTableModel model based on it

There was a need to obtain a large amount of data from the database for the subsequent work of the user with them (Reference book of goods in a particular store).

For the code do not kick much. Programming for me is a hobby for the mind.

The query itself takes a long time. For this, we do it in the stream, display the endless ProgressBar to the user with the sentence "Lean back and wait." :-)


The data received in the request is stored in a vector of objects of the class Articles.

Articles

Class for storing a single record of the resulting query. For storage of all records we use a vector.

articles.h

#ifndef ARTICLES_H
#define ARTICLES_H

#include <QString>

class Articles
{
public:
    Articles();
    void setColParam(int _colParam);
    void setID(int _articleID);
    void setShortName(QString _shortName);
    void setAmount(float _amount);
    void setPrice(float _price);
    int getColParam();
    int getID();
    QString getShortName();
    float getAmount();
    float getPrice();
private:
    int colParam;           // The number of parameters. necessary to create a model later
    int artileID;           // Product code
    QString shortname;      // Name
    float amount;           // Amount
    float price;            // Price
};

#endif // ARTICLES_H

articles.cpp

#include "articles.h"

Articles::Articles()
{
    setColParam(4);
}

void Articles::setColParam(int _colParam)
{
    colParam = _colParam;
}

int Articles::getColParam()
{
    return colParam;
}

void Articles::setID(int _articleID)
{
    artileID = _articleID;
}

void Articles::setShortName(QString _shortName)
{
    shortname = _shortName;
}

void Articles::setAmount(float _amount)
{
    amount = _amount;
}

void Articles::setPrice(float _price)
{
    price = _price;
}

int Articles::getID()
{
    return artileID;
}

QString Articles::getShortName()
{
    return shortname;
}

float Articles::getAmount()
{
    return amount;
}
float Articles::getPrice()
{
    return price;
}

For logging and debugging I used the methods described in this article Logging Qt application events to a text file .

ListArticles

The class for sampling from the database

listarticles.h

#ifndef LISTARTICLES_H
#define LISTARTICLES_H

#include "articles.h"

#include <QObject>
#include <QSqlQuery>
#include <QSqlError>
#include <QVector>
#include <QSqlRecord>


class ListArticles : public QObject
{
    Q_OBJECT
public:
    explicit ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent = nullptr);

signals:
    // Signal to send data to the main stream for further processing
    void signalSendArticlesList(QVector<Articles>);
    // Signal about the end of data sampling
    void finish();

public slots:
    // Slot for starting data sampling, called from the main stream
    void createListGoods();

private:
    int m_terminalID;               // Store number, parameter for request
    int m_shiftID;                  // Shift number, parameter for request
    QVector<Articles> goods;        // A vector in which we will store the results of the query and transfer them to the main stream.
    QSqlRecord connRec;             // Record selected from another model containing database connection parameters

};

#endif // LISTARTICLES_H

listarticles.cpp

#include "listarticles.h"
#include "loggingcategories.h"

ListArticles::ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent) : QObject(parent)
{
    // Получаем входные данные
    m_terminalID = terminal_D;
    m_shiftID = shift;
    connRec =rec;
}

void ListArticles::createListGoods()
{
    // Create an object to store one record.
    Articles ar;

    // we register type for transfer it through the mechanism a signal slot.
    typedef QVector<Articles> vek;
    qRegisterMetaType<vek>("vektor");

    // From Assistant
    // "A connection can only be used within the thread that created it. Moving connections between threads and creating requests to another thread is not supported."
    // Therefore, we are creating a new connection.

    QSqlDatabase dbth = QSqlDatabase::addDatabase("QIBASE","thcentr");

    dbth.setHostName(connRec.value("conn_host").toString());
    dbth.setDatabaseName(connRec.value("conn_db").toString());
    dbth.setUserName(connRec.value("conn_user").toString());
    dbth.setPassword(connRec.value("conn_pass").toString());


    if(!dbth.open()) {
        qCritical(logCritical()) << Q_FUNC_INFO << "I can not connect to the central database"
                                 << endl << dbth.lastError().text();
        return;
    }
    // Bind request to connection
    QSqlQuery q = QSqlQuery(dbth);

    // We form line with request.
    // I prefer to create queries of this type as strings and test them directly in the database manager.

    QString strSQL = QString("SELECT A.GARTICLE_ID, GA.SHORTNAME, SL.AMOUNT, "
                                 "(SELECT FIRST 1 NEWPRICE FROM HISTORY_PRICES HP "
                                  "WHERE HP.TERMINAL_ID = SL.TERMINAL_ID "
                                  "AND HP.GARTICLE_ID = A.GARTICLE_ID "
                                  "AND HP.DATEOP < SH.DATCLOSE "
                                  "ORDER BY HP.DATEOP DESC) AS PRICE "
                               "FROM GET_ASALDOS (%1, %2, NULL, 0) AS SL "
                               "INNER JOIN ARTICLES A ON A.TERMINAL_ID = SL.TERMINAL_ID AND A.ARTICLE_ID = SL.ARTICLE_ID "
                               "LEFT JOIN SHIFTS SH ON SH.TERMINAL_ID = SL.TERMINAL_ID AND SH.SHIFT_ID = SL.SHIFT_ID "
                               "LEFT JOIN GARTICLES GA ON GA.GARTICLE_ID = A.GARTICLE_ID "
                               "WHERE SL.AMOUNT > 0 "
                               "ORDER BY A.GARTICLE_ID" )
                .arg(m_terminalID)
                .arg(m_shiftID);
    // Execute the request
    if(!q.exec(strSQL)) {
        qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
        emit finish();
    }
    // The cycle of receiving records, and adding them to the vector
    while (q.next()){
        ar.setID(q.value("GARTICLE_ID").toInt());
        ar.setShortName(q.value("SHORTNAME").toString().trimmed());
        ar.setAmount(q.value("AMOUNT").toFloat());
        ar.setPrice(q.value("PRICE").toFloat());
        goods.append(ar);
    }
    //Passing the result to the main thread.
    emit signalSendArticlesList(goods);
    //Flow finished work
    emit finish();
}

Run the data stream of query

// Data selection occurs when you go to a specific QWizardPage page.
void ArticlePage::initializePage()
{
    // Создаем объект класса и передаем ему параметры
    ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
    // Create a stream in which our sample will be made.
    QThread *thread = new QThread();
    // Moving a class object to a stream
    lsArticles->moveToThread(thread);

    //// Signals and slots for interacting with the stream

    // When starting a thread, we perform some actions in the current thread.
    // In my case, I simply mark the beginning of the data sample.
    connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
    // At the start of the stream, we start sampling data
    connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);

    // Passing the resulting QVector object from the child to the main thread
    connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
    // The end of the flow of data sampling
    connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
    // Remove the object in the stream
    connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
    // Perform actions on the main thread after the completion of the child
    connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
    // We say goodbye to the child thread
    connect(thread,&QThread::finished,thread,&QThread::deleteLater);

    // We start a flow
    thread->start();
}

Data exchange slots with stream

void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
{
    //Get the vector with the results from the stream
    goods = ls;
}

void ArticlePage::slotStartArticlesList()
{
    qInfo(logInfo()) << "Began to receive a list of goods" << QTime::currentTime().toString("hh:mm:ss.zzz");

}

void ArticlePage::slotFinishArticlesList()
{
    // based on the received vector with data, create a data model of the VectorModel type and output it in QTableView

    qInfo(logInfo()) << "Finished receiving a list of goods" << QTime::currentTime().toString("hh:mm:ss.zzz");
    ui->frameProgress->hide();
    ui->groupBoxAdd->show();

    modelArticles = new VektorModel(goods);

    ui->tableView->setModel(modelArticles);
    ui->tableView->verticalHeader()->hide();
    ui->tableView->setAlternatingRowColors(true);
    ui->tableView->resizeColumnsToContents();
    // Minimum row height in QTableView
    ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());

}

VektorModel

The class derived from QAbstractTableModel creates a model for displaying request data.

vektormodel.h

#ifndef VEKTORMODEL_H
#define VEKTORMODEL_H

#include "articles.h"
#include <QObject>
#include <QAbstractTableModel>
#include <QVariant>



class VektorModel : public QAbstractTableModel
{
    Q_OBJECT
    QVector<Articles>  ar;

public:
    VektorModel(const QVector<Articles> vek);
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
};

#endif // VEKTORMODEL_H

vectormodel.cpp

#include "vektormodel.h"
#include "articles.h"

VektorModel::VektorModel(const QVector<Articles> vek)
{
    ar = vek;
}
// data mapping model
QVariant VektorModel::data(const QModelIndex &index, int role) const
{
    if ( !index.isValid() ) { return QVariant(); }
        Articles a = ar[index.row()];
    switch (role) {
    case Qt::DisplayRole:
        switch (index.column()) {
        case 0: return a.getID();                           // 1-й column ID
        case 1: return a.getShortName();                    // 2-й column Name
        case 2: return a.getAmount();                       // 3-й column Amount
        case 3: return QString::number(a.getPrice(),'f',2); // 4-й column Price
        default: break;
        }
      break;

    // align the 2nd and 3rd column to the right
    case Qt::TextAlignmentRole:
        if(index.column() == 2 || index.column() == 3 )
            return Qt::AlignRight;
        break;
    default:
        break;
    }

    return QVariant();

}

QVariant VektorModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    //Creating model column headers
    if( role != Qt::DisplayRole ) {
        return QVariant();
    }

    if( orientation == Qt::Vertical ) {
        return section;
    }

    switch( section ) {
    case 0:
        return tr( "Гл.Код" );
    case 1:
        return tr( "Наименование" );
    case 2:
        return tr( "Доступно" );
    case 3:
        return tr( "Цена" );
    }

    return QVariant();
}

int VektorModel::rowCount(const QModelIndex &parent) const
{
    // number of lines
    return ar.size();
}

int VektorModel::columnCount(const QModelIndex &parent) const
{
    // Number of columns
    Articles a;
    return a.getColParam();
}

The result is the following:

By article asked0question(s)

3

Do you like it? Share on social networks!

Ruslan Polupan
  • Nov. 27, 2018, 5:36 p.m.

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта QSqlQuery в вектор выполняется достаточно медленно.

    // Цикл получения записей, и добавления их в вектор
    while (q.next()){
        ar.setID(q.value("GARTICLE_ID").toInt());
        ar.setShortName(q.value("SHORTNAME").toString().trimmed());
        ar.setAmount(q.value("AMOUNT").toFloat());
        ar.setPrice(q.value("PRICE").toFloat());
        goods.append(ar);
    }

Проблема решилась замена названий в value на номера.

    while (q.next()){
        ar.setID(q.value(0).toInt());
        ar.setShortName(q.value(1).toString().trimmed());
        ar.setAmount(q.value(2).toFloat());
        ar.setPrice(q.value(3).toFloat());
        goods << ar;
        i++;
    }



s
  • Nov. 29, 2020, 3:27 p.m.
  • (edited)

Добрый день, вывел в поток ресурсоемкую функцию, интерфейс работает не нарадуешься, но после завершения потока теряется соединение с базой данных в основоном, не могу сообразить куда смотреть. В потоке создается свое соединение с базой данных.

Ruslan Polupan
  • Nov. 30, 2020, 1:32 p.m.

В потоке надо создавать свое соединение с БД с другим именем.

s
  • Nov. 30, 2020, 10:40 p.m.

Если в:
QSQLDatabase db_thread = QSQLDatabase::addDatabase("MYSQL","db_new_name");

крашится после запуска сразу

Ruslan Polupan
  • Dec. 1, 2020, 1:43 p.m.

Вы полностью создаете новое соединение?
И при создании объекта QSqlQuery или модели указываете алиас подключения?

s
  • Dec. 2, 2020, 12:21 a.m.

Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция

#ifndef FUNCTIONS_H
#define FUNCTIONS_H

#include <QTextCodec>
#include <QDesktopServices>
#include <QUrl>
#include <QAxObject>
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QVariant>

extern QStringList gsf_GetRecoredsetFields(const QString & m_sql);
extern QList<QStringList> gsf_GetRecoredsetData(const QString & m_sql);
extern QString     gsf_GetRecordsetValueString(const QString & m_sql);
extern QByteArray  gsf_GetRecordsetBinary(const QString & m_sql);
extern bool        gsf_ExecuteSQL(const QString & m_sql);
extern bool        gsf_RecordIsValid(const QString & m_sql);

#endif // FUNCTIONS_H
bool gsf_ExecuteSQL(const QString & m_sql)
{
    QSqlQuery query(m_sql);

    if(!query.exec()){
        QString m_message = QString("Запрос завершился с ошибкой: %1, обратитесь к Администратору.")
                .arg(query.lastError().text());

        QMessageBox::warning(nullptr, "Ошибка!", m_message);

        return false;
    }

    return true;
}

Как пример

s
  • Dec. 3, 2020, 3:10 a.m.

Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается

Ruslan Polupan
  • Dec. 3, 2020, 12:03 p.m.

Соединение в потоке используется только в потоке. Т.е выбирает данные сохраняете в какой-нибудь контейнер и передаете его основному потоку.

s
  • Dec. 4, 2020, 1:13 a.m.

Я наверное слишком туп, можете пример привести как Вы это делаете?

Ruslan Polupan
  • Dec. 4, 2020, 8:27 p.m.

Так эта статья как раз об этом.

s
  • Dec. 4, 2020, 9:11 p.m.

Кстати сегодня почему-то все заработало :)

Ruslan Polupan
  • Dec. 7, 2020, 12:43 p.m.

Луна стала в нужную фазу :-)

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • AK
    April 24, 2025, 12:04 p.m.
    UPD: Переписал логику воспроизведения через стороннюю библиотеку BASS. Там выбрать можно
  • Evgenii Legotckoi
    April 16, 2025, 5:08 p.m.
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    April 12, 2025, 5:12 p.m.
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    April 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…