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

  1. #ifndef ARTICLES_H
  2. #define ARTICLES_H
  3.  
  4. #include <QString>
  5.  
  6. class Articles
  7. {
  8. public:
  9. Articles();
  10. void setColParam(int _colParam);
  11. void setID(int _articleID);
  12. void setShortName(QString _shortName);
  13. void setAmount(float _amount);
  14. void setPrice(float _price);
  15. int getColParam();
  16. int getID();
  17. QString getShortName();
  18. float getAmount();
  19. float getPrice();
  20. private:
  21. int colParam; // The number of parameters. necessary to create a model later
  22. int artileID; // Product code
  23. QString shortname; // Name
  24. float amount; // Amount
  25. float price; // Price
  26. };
  27.  
  28. #endif // ARTICLES_H

articles.cpp

  1. #include "articles.h"
  2.  
  3. Articles::Articles()
  4. {
  5. setColParam(4);
  6. }
  7.  
  8. void Articles::setColParam(int _colParam)
  9. {
  10. colParam = _colParam;
  11. }
  12.  
  13. int Articles::getColParam()
  14. {
  15. return colParam;
  16. }
  17.  
  18. void Articles::setID(int _articleID)
  19. {
  20. artileID = _articleID;
  21. }
  22.  
  23. void Articles::setShortName(QString _shortName)
  24. {
  25. shortname = _shortName;
  26. }
  27.  
  28. void Articles::setAmount(float _amount)
  29. {
  30. amount = _amount;
  31. }
  32.  
  33. void Articles::setPrice(float _price)
  34. {
  35. price = _price;
  36. }
  37.  
  38. int Articles::getID()
  39. {
  40. return artileID;
  41. }
  42.  
  43. QString Articles::getShortName()
  44. {
  45. return shortname;
  46. }
  47.  
  48. float Articles::getAmount()
  49. {
  50. return amount;
  51. }
  52. float Articles::getPrice()
  53. {
  54. return price;
  55. }

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

  1. #ifndef LISTARTICLES_H
  2. #define LISTARTICLES_H
  3.  
  4. #include "articles.h"
  5.  
  6. #include <QObject>
  7. #include <QSqlQuery>
  8. #include <QSqlError>
  9. #include <QVector>
  10. #include <QSqlRecord>
  11.  
  12.  
  13. class ListArticles : public QObject
  14. {
  15. Q_OBJECT
  16. public:
  17. explicit ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent = nullptr);
  18.  
  19. signals:
  20. // Signal to send data to the main stream for further processing
  21. void signalSendArticlesList(QVector<Articles>);
  22. // Signal about the end of data sampling
  23. void finish();
  24.  
  25. public slots:
  26. // Slot for starting data sampling, called from the main stream
  27. void createListGoods();
  28.  
  29. private:
  30. int m_terminalID; // Store number, parameter for request
  31. int m_shiftID; // Shift number, parameter for request
  32. QVector<Articles> goods; // A vector in which we will store the results of the query and transfer them to the main stream.
  33. QSqlRecord connRec; // Record selected from another model containing database connection parameters
  34.  
  35. };
  36.  
  37. #endif // LISTARTICLES_H

listarticles.cpp

  1. #include "listarticles.h"
  2. #include "loggingcategories.h"
  3.  
  4. ListArticles::ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent) : QObject(parent)
  5. {
  6. // Получаем входные данные
  7. m_terminalID = terminal_D;
  8. m_shiftID = shift;
  9. connRec =rec;
  10. }
  11.  
  12. void ListArticles::createListGoods()
  13. {
  14. // Create an object to store one record.
  15. Articles ar;
  16.  
  17. // we register type for transfer it through the mechanism a signal slot.
  18. typedef QVector<Articles> vek;
  19. qRegisterMetaType<vek>("vektor");
  20.  
  21. // From Assistant
  22. // "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."
  23. // Therefore, we are creating a new connection.
  24.  
  25. QSqlDatabase dbth = QSqlDatabase::addDatabase("QIBASE","thcentr");
  26.  
  27. dbth.setHostName(connRec.value("conn_host").toString());
  28. dbth.setDatabaseName(connRec.value("conn_db").toString());
  29. dbth.setUserName(connRec.value("conn_user").toString());
  30. dbth.setPassword(connRec.value("conn_pass").toString());
  31.  
  32.  
  33. if(!dbth.open()) {
  34. qCritical(logCritical()) << Q_FUNC_INFO << "I can not connect to the central database"
  35. << endl << dbth.lastError().text();
  36. return;
  37. }
  38. // Bind request to connection
  39. QSqlQuery q = QSqlQuery(dbth);
  40.  
  41. // We form line with request.
  42. // I prefer to create queries of this type as strings and test them directly in the database manager.
  43.  
  44. QString strSQL = QString("SELECT A.GARTICLE_ID, GA.SHORTNAME, SL.AMOUNT, "
  45. "(SELECT FIRST 1 NEWPRICE FROM HISTORY_PRICES HP "
  46. "WHERE HP.TERMINAL_ID = SL.TERMINAL_ID "
  47. "AND HP.GARTICLE_ID = A.GARTICLE_ID "
  48. "AND HP.DATEOP < SH.DATCLOSE "
  49. "ORDER BY HP.DATEOP DESC) AS PRICE "
  50. "FROM GET_ASALDOS (%1, %2, NULL, 0) AS SL "
  51. "INNER JOIN ARTICLES A ON A.TERMINAL_ID = SL.TERMINAL_ID AND A.ARTICLE_ID = SL.ARTICLE_ID "
  52. "LEFT JOIN SHIFTS SH ON SH.TERMINAL_ID = SL.TERMINAL_ID AND SH.SHIFT_ID = SL.SHIFT_ID "
  53. "LEFT JOIN GARTICLES GA ON GA.GARTICLE_ID = A.GARTICLE_ID "
  54. "WHERE SL.AMOUNT > 0 "
  55. "ORDER BY A.GARTICLE_ID" )
  56. .arg(m_terminalID)
  57. .arg(m_shiftID);
  58. // Execute the request
  59. if(!q.exec(strSQL)) {
  60. qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
  61. emit finish();
  62. }
  63. // The cycle of receiving records, and adding them to the vector
  64. while (q.next()){
  65. ar.setID(q.value("GARTICLE_ID").toInt());
  66. ar.setShortName(q.value("SHORTNAME").toString().trimmed());
  67. ar.setAmount(q.value("AMOUNT").toFloat());
  68. ar.setPrice(q.value("PRICE").toFloat());
  69. goods.append(ar);
  70. }
  71. //Passing the result to the main thread.
  72. emit signalSendArticlesList(goods);
  73. //Flow finished work
  74. emit finish();
  75. }

Run the data stream of query

  1. // Data selection occurs when you go to a specific QWizardPage page.
  2. void ArticlePage::initializePage()
  3. {
  4. // Создаем объект класса и передаем ему параметры
  5. ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
  6. // Create a stream in which our sample will be made.
  7. QThread *thread = new QThread();
  8. // Moving a class object to a stream
  9. lsArticles->moveToThread(thread);
  10.  
  11. //// Signals and slots for interacting with the stream
  12.  
  13. // When starting a thread, we perform some actions in the current thread.
  14. // In my case, I simply mark the beginning of the data sample.
  15. connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
  16. // At the start of the stream, we start sampling data
  17. connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);
  18.  
  19. // Passing the resulting QVector object from the child to the main thread
  20. connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
  21. // The end of the flow of data sampling
  22. connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
  23. // Remove the object in the stream
  24. connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
  25. // Perform actions on the main thread after the completion of the child
  26. connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
  27. // We say goodbye to the child thread
  28. connect(thread,&QThread::finished,thread,&QThread::deleteLater);
  29.  
  30. // We start a flow
  31. thread->start();
  32. }

Data exchange slots with stream

  1. void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
  2. {
  3. //Get the vector with the results from the stream
  4. goods = ls;
  5. }
  6.  
  7. void ArticlePage::slotStartArticlesList()
  8. {
  9. qInfo(logInfo()) << "Began to receive a list of goods" << QTime::currentTime().toString("hh:mm:ss.zzz");
  10.  
  11. }
  12.  
  13. void ArticlePage::slotFinishArticlesList()
  14. {
  15. // based on the received vector with data, create a data model of the VectorModel type and output it in QTableView
  16.  
  17. qInfo(logInfo()) << "Finished receiving a list of goods" << QTime::currentTime().toString("hh:mm:ss.zzz");
  18. ui->frameProgress->hide();
  19. ui->groupBoxAdd->show();
  20.  
  21. modelArticles = new VektorModel(goods);
  22.  
  23. ui->tableView->setModel(modelArticles);
  24. ui->tableView->verticalHeader()->hide();
  25. ui->tableView->setAlternatingRowColors(true);
  26. ui->tableView->resizeColumnsToContents();
  27. // Minimum row height in QTableView
  28. ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());
  29.  
  30. }

VektorModel

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

vektormodel.h

  1. #ifndef VEKTORMODEL_H
  2. #define VEKTORMODEL_H
  3.  
  4. #include "articles.h"
  5. #include <QObject>
  6. #include <QAbstractTableModel>
  7. #include <QVariant>
  8.  
  9.  
  10.  
  11. class VektorModel : public QAbstractTableModel
  12. {
  13. Q_OBJECT
  14. QVector<Articles> ar;
  15.  
  16. public:
  17. VektorModel(const QVector<Articles> vek);
  18. QVariant data(const QModelIndex &index, int role) const;
  19. QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
  20. int rowCount(const QModelIndex &parent) const;
  21. int columnCount(const QModelIndex &parent) const;
  22. };
  23.  
  24. #endif // VEKTORMODEL_H

vectormodel.cpp

  1. #include "vektormodel.h"
  2. #include "articles.h"
  3.  
  4. VektorModel::VektorModel(const QVector<Articles> vek)
  5. {
  6. ar = vek;
  7. }
  8. // data mapping model
  9. QVariant VektorModel::data(const QModelIndex &index, int role) const
  10. {
  11. if ( !index.isValid() ) { return QVariant(); }
  12. Articles a = ar[index.row()];
  13. switch (role) {
  14. case Qt::DisplayRole:
  15. switch (index.column()) {
  16. case 0: return a.getID(); // 1-й column ID
  17. case 1: return a.getShortName(); // 2-й column Name
  18. case 2: return a.getAmount(); // 3-й column Amount
  19. case 3: return QString::number(a.getPrice(),'f',2); // 4-й column Price
  20. default: break;
  21. }
  22. break;
  23.  
  24. // align the 2nd and 3rd column to the right
  25. case Qt::TextAlignmentRole:
  26. if(index.column() == 2 || index.column() == 3 )
  27. return Qt::AlignRight;
  28. break;
  29. default:
  30. break;
  31. }
  32.  
  33. return QVariant();
  34.  
  35. }
  36.  
  37. QVariant VektorModel::headerData(int section, Qt::Orientation orientation, int role) const
  38. {
  39. //Creating model column headers
  40. if( role != Qt::DisplayRole ) {
  41. return QVariant();
  42. }
  43.  
  44. if( orientation == Qt::Vertical ) {
  45. return section;
  46. }
  47.  
  48. switch( section ) {
  49. case 0:
  50. return tr( "Гл.Код" );
  51. case 1:
  52. return tr( "Наименование" );
  53. case 2:
  54. return tr( "Доступно" );
  55. case 3:
  56. return tr( "Цена" );
  57. }
  58.  
  59. return QVariant();
  60. }
  61.  
  62. int VektorModel::rowCount(const QModelIndex &parent) const
  63. {
  64. // number of lines
  65. return ar.size();
  66. }
  67.  
  68. int VektorModel::columnCount(const QModelIndex &parent) const
  69. {
  70. // Number of columns
  71. Articles a;
  72. return a.getColParam();
  73. }

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 в вектор выполняется достаточно медленно.

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

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

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



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.

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

  1. #ifndef FUNCTIONS_H
  2. #define FUNCTIONS_H
  3.  
  4. #include <QTextCodec>
  5. #include <QDesktopServices>
  6. #include <QUrl>
  7. #include <QAxObject>
  8. #include <QDebug>
  9. #include <QFile>
  10. #include <QMessageBox>
  11. #include <QSqlDatabase>
  12. #include <QSqlQuery>
  13. #include <QSqlRecord>
  14. #include <QSqlError>
  15. #include <QVariant>
  16.  
  17. extern QStringList gsf_GetRecoredsetFields(const QString & m_sql);
  18. extern QList<QStringList> gsf_GetRecoredsetData(const QString & m_sql);
  19. extern QString gsf_GetRecordsetValueString(const QString & m_sql);
  20. extern QByteArray gsf_GetRecordsetBinary(const QString & m_sql);
  21. extern bool gsf_ExecuteSQL(const QString & m_sql);
  22. extern bool gsf_RecordIsValid(const QString & m_sql);
  23.  
  24. #endif // FUNCTIONS_H
  1. bool gsf_ExecuteSQL(const QString & m_sql)
  2. {
  3. QSqlQuery query(m_sql);
  4.  
  5. if(!query.exec()){
  6. QString m_message = QString("Запрос завершился с ошибкой: %1, обратитесь к Администратору.")
  7. .arg(query.lastError().text());
  8.  
  9. QMessageBox::warning(nullptr, "Ошибка!", m_message);
  10.  
  11. return false;
  12. }
  13.  
  14. return true;
  15. }

Как пример

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 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup