Ruslan Polupan
Ruslan PolupanҚар. 20, 2018, 7:42 Т.Ж.

Qt - QThread ішіндегі QSqlQuery дерекқорынан деректерді таңдау және оның негізінде QAbstractTableModel құру

Возникла необходимость получить большой объем данных из базы данных для последующей работы пользователя с ними (Справочник товаров в определенном магазине).

За код сильно не пинайте. Программирование для меня это хобби в качестве зарядки для ума.

Запрос сам по себе выполняет достаточно долго. По этому  делаем его в потоке, пользователю отображаем бесконечный ProgressBar с предложением "Откинутся на спинку стула и подождать." :-)


Данные получаемые в запросе храним в векторе объектов класса Articles.

Articles

Класс для хранения одной записи результирующего запроса. Для хранения всех записей используем вектор.

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;           // Количетсво параметров. необходимое для последующего создания модели
    int artileID;           // Код товара
    QString shortname;      // Наименование
    float amount;           // Количество
    float 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;
}

Для логирования и отладки использовал методы описанные в этой статье Логирование событий Qt приложения в текстовый файл .

ListArticles

Класс для осуществления выборки из базы данных

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:
    // Сигнал дя отправки данных в основной поток для дальнейшей их обработки
    void signalSendArticlesList(QVector<Articles>);
    // Сигнал об окончании выборки данных
    void finish();

public slots:
    // Слот для начала выбоки данных, вызываемый из основного потока
    void createListGoods();

private:
    int m_terminalID;               // Номер магазина, параметр для запроса
    int m_shiftID;                  // Номер смены, параметр для запроса
    QVector<Articles> goods;        // Вектор в котором будем хранить результаты запроса и передадим в основной поток
    QSqlRecord connRec;             // Запись выбранная из другой модели содержащщая параметры подключения к базе данных

};

#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()
{
    // Создаем объект для хранения одной записи
    Articles ar;

    // регистрируем тип для передачи его через механизм сигнал слот.
    typedef QVector<Articles> vek;
    qRegisterMetaType<vek>("vektor");

    // Из Асистента
    // "Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.
    // Поэтому создаем новое соединение

    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 << "Не могу подключится к центральной базе"
                                 << endl << dbth.lastError().text();
        return;
    }
    // Привязыем запрос к соединению
    QSqlQuery q = QSqlQuery(dbth);

    // Формируем строку с запросом.
    // Такого типа запросы предпочитаю создавать ввиде строки и тестировать их в непосредственно в менеджере базы данных

    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);
    // Выполняем запрос
    if(!q.exec(strSQL)) {
        qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
        emit finish();
    }
    // Цикл получения записей, и добавления их в вектор
    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);
    }
    //Передаем результат в основной поток
    emit signalSendArticlesList(goods);
    //Поток закончил работу
    emit finish();
}

Запуск потока выборки данных

// Выбор данных происходит при переходе на определенную странцу QWizardPage
void ArticlePage::initializePage()
{
    // Создаем объект класса и передаем ему параметры
    ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
    // Создаем поток в которм будут производиться наша выборка
    QThread *thread = new QThread();
    // Перемещаем объект класса в поток
    lsArticles->moveToThread(thread);

    //// Сигналы и слоты для взаимидействия с потоком

    // при старте потока выполняем некоторые действия в текущем потоке. 
    // В моем случае на просто засекаю начало выбоки данных
    connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
    // При старте потока начинаем выборку данных
    connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);

    // Передача результирующего объекта QVertor из дочернего потока в основной
    connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
    // Окончание работы потока по завершению выбрки данных
    connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
    // Удаляем объект в потоке
    connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
    // Вы полняем действия по в основном потоке после завершения дочернего
    connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
    // Прощаемся с дочерним потоком
    connect(thread,&QThread::finished,thread,&QThread::deleteLater);

    // Запускаем поток
    thread->start();
}

Слоты обмена данными с потоком

void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
{
    //Получаем вектор с результами из потока
    goods = ls;
}

void ArticlePage::slotStartArticlesList()
{
    qInfo(logInfo()) << "Начали получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");

}

void ArticlePage::slotFinishArticlesList()
{
    // на основании полученного вектора с данными создаем модель данных типа VectorModel и выводим ее в QTableView

    qInfo(logInfo()) << "Закончили получать список товаров" << 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();
    // Минимальная высота строк в QTableView
    ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());

}

VektorModel

Класс наследник от QAbstractTableModel создающий модель для отображения данных запроса.

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;
}
// отображение данных модели
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-й столбец ID
        case 1: return a.getShortName();                    // 2-й столбец Наименование
        case 2: return a.getAmount();                       // 3-й столбец Количетсво
        case 3: return QString::number(a.getPrice(),'f',2); // 4-й столбец Цена
        default: break;
        }
      break;

    // выравниваем 2-й и 3-й столбез по правому краю
    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
{
    //Cоздаем заголовки столбцов модели
    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
{
    // Количетсво строк
    return ar.size();
}

int VektorModel::columnCount(const QModelIndex &parent) const
{
    // Количество столбцов
    Articles a;
    return a.getColParam();
}

В результате получаем следующее:

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Ruslan Polupan
  • Қар. 27, 2018, 6:36 Т.Ж.

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта 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
  • Қар. 29, 2020, 4:27 Т.Ж.
  • (өңделген)

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

Ruslan Polupan
  • Қар. 30, 2020, 2:32 Т.Ж.

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

s
  • Қар. 30, 2020, 11:40 Т.Ж.

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

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

Ruslan Polupan
  • Жел. 1, 2020, 2:43 Т.Ж.

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

s
  • Жел. 1, 2020, 1:21 Т.Қ.

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

#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
  • Жел. 2, 2020, 4:10 Т.Қ.

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

Ruslan Polupan
  • Жел. 3, 2020, 1:03 Т.Ж.

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

s
  • Жел. 3, 2020, 2:13 Т.Қ.

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

Ruslan Polupan
  • Жел. 4, 2020, 9:27 Т.Ж.

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

s
  • Жел. 4, 2020, 10:11 Т.Ж.

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

Ruslan Polupan
  • Жел. 7, 2020, 1:43 Т.Ж.

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

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
OI
  • Ora Iro
  • Жел. 24, 2024, 3:38 Т.Ж.

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:40ұпай,
  • Бағалау ұпайлары-8
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:50ұпай,
  • Бағалау ұпайлары-4
m
  • molni99
  • Қаз. 25, 2024, 10:37 Т.Қ.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
Соңғы пікірлер
ИМ
Игорь МаксимовҚар. 22, 2024, 8:51 Т.Ж.
Django - Оқулық 017. Теңшелген Django кіру беті Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiҚаз. 31, 2024, 11:37 Т.Ж.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEҚаз. 19, 2024, 5:19 Т.Ж.
Qt Creator көмегімен fb3 файл оқу құралы Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовҚаз. 5, 2024, 4:51 Т.Ж.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5Шілде 5, 2024, 8:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Енді форумда талқылаңыз
p
pimacontrols85Жел. 31, 2024, 6:39 Т.Ж.
Finding the Right Rittal Small Enclosure for Your Needs Rittal is a leading manufacturer of enclosures for industrial and IT applications. Their small enclosures offer a compact and reliable solution for a wide range of needs, from housing electronic…
Donald Randolph
Donald RandolphЖел. 29, 2024, 11:59 Т.Қ.
Personal Injury lawyer Santa Monica As an experienced Santa Monica personal injury lawyer, Donald C. Randolph has recovered over $100 Million in verdicts and settlements for our clients. In severe injury cases, this compensation i…
Nirvana Yoga School
Nirvana Yoga SchoolЖел. 30, 2024, 2:13 Т.Ж.
OAuth2.0 через VK, получение email Nirvana Yoga School is one of the most trusted and reputed traditional Rishikesh yoga courses , India certified by Yoga Alliance, USA. We aim to spread traditional yoga teachings so t…
s
sriparkЖел. 30, 2024, 1:47 Т.Ж.
Mobile app development company in Chennai A Mobile app development company in Chennai focuses on creating personalized mobile applications to meet various business requirements. These companies offer a full range of services,…
a
amit88Жел. 30, 2024, 1:45 Т.Ж.
Excel in Exams with PSLE Maths Tuition Singapore Preparing for the PSLE can be challenging, but the right guidance makes all the difference. PSLE Maths tuition Singapore offers personalized coaching to help students master key concepts, improv…

Бізді әлеуметтік желілерде бақылаңыз