Evgenii Legotckoi
Aug. 15, 2015, 10:31 p.m.

Qt/C++ - Lesson 005. QSqlRelationalTableModel - The work with relational tables

In Qt to represent table fields, which involve foreign keys to other tables database, QSqlRelationalTableModel can be used, which is a more advanced version QSqlTableModel class, which was considered in the previous article .

This class allows you to set relationships between tables and substitute values in the representation of the table formed by the values of the connected tables.

This article discusses the option of two tables. One table contains information about the devices (host name and IP address), and the second table ID of the device, which in the second table to be substituted host name and IP address of the device, respectively.

Project Structure for QSqlRelationalTableModel

Структура проекта The project for this tutorial is a modified version of the preceding Article and remains unchanged.


mainwindow.ui

When creating molds are added to a table that will be specified device. The names of the following tables:

  • tableView
  • tableViewDevice

DataBase.pro

This file remains the same as in the previous article.

main.cpp

The file used in the project, being created by default.

  1. #include "mainwindow.h"
  2. #include <QApplication>
  3.  
  4. int main(int argc, char *argv[])
  5. {
  6. QApplication a(argc, argv);
  7. MainWindow w;
  8. w.show();
  9.  
  10. return a.exec();
  11. }

mainwindow.h

Instead QSqlTableModel library we connect QSqlRelationalTableModel library. Also is added an additional method, which is responsible for initializing the devices table view model.

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include <QSqlRelationalTableModel>
  6. #include <QSqlRelationalDelegate>
  7. #include <QSqlRelation>
  8.  
  9. /* Connect the header file for work with the database */
  10. #include "database.h"
  11.  
  12. namespace Ui {
  13. class MainWindow;
  14. }
  15.  
  16. class MainWindow : public QMainWindow
  17. {
  18. Q_OBJECT
  19.  
  20. public:
  21. explicit MainWindow(QWidget *parent = 0);
  22. ~MainWindow();
  23.  
  24. private:
  25. Ui::MainWindow *ui;
  26. /* The project uses objects to interact with the database and model representation database table
  27. * */
  28. DataBase *db;
  29. QSqlRelationalTableModel *modelMain;
  30. QSqlRelationalTableModel *modelDevice;
  31.  
  32. private:
  33. /* Also present are two methods that form the model and appearance TableView
  34. * */
  35. void setupMainModel(const QString &tableName, const QStringList &headers);
  36. void setupDeviceModel(const QString &tableName, const QStringList &headers);
  37. void createUI();
  38. };
  39.  
  40. #endif // MAINWINDOW_H

mainwindow.cpp

Basically, the original file initialize the data model representation and appearance of the main window. In the method for initializing the main table will be installed communication for which data from the devices of the table will be selected.

When you run the program in a database introduced three devices, then 9 records with random assignment device ID from 1 to 3. The first time you start the program creates the database and devices are introduced into an empty table with the device ID from one to three. The second run will be the ID other than 1, 2 or 3 but as this case study, we restrict ourselves to the first one you run the application.

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. MainWindow::MainWindow(QWidget *parent) :
  5. QMainWindow(parent),
  6. ui(new Ui::MainWindow)
  7. {
  8. ui->setupUi(this);
  9.  
  10. /* The first step is to create an object for the database and initialize the database connection
  11. * */
  12. db = new DataBase();
  13. db->connectToDataBase();
  14.  
  15. /* After that produce content database tables
  16.   * this content will displayed in the tableView and tableViewDevice
  17. * */
  18. for(int i = 1; i < 4; i++){
  19. QVariantList data;
  20. data.append("Device " + QString::number(i));
  21. data.append("192.168.0." + QString::number(i));
  22. db->inserIntoDeviceTable(data);
  23. }
  24.  
  25. for(int i = 0; i < 10; i++){
  26. QVariantList data;
  27. QString random = QString::number(qrand() % ((4 + 1) - 1) + 1);
  28. data.append(QDate::currentDate());
  29. data.append(QTime::currentTime());
  30. data.append(random);
  31. data.append(random);
  32. db->inserIntoMainTable(data);
  33. }
  34.  
  35. /* Initialize the model to represent the data indicating the names of the columns
  36. * */
  37. this->setupMainModel(TABLE,
  38. QStringList() << trUtf8("id")
  39. << trUtf8("Дата")
  40. << trUtf8("Время")
  41. << trUtf8("Имя хоста")
  42. << trUtf8("IP адрес")
  43. );
  44.  
  45. this->setupDeviceModel(DEVICE,
  46. QStringList() << trUtf8("id")
  47. << trUtf8("Имя хоста")
  48. << trUtf8("IP адрес")
  49. );
  50. /* Initialize the appearance of a table with data
  51. * */
  52. this->createUI();
  53. }
  54.  
  55. MainWindow::~MainWindow()
  56. {
  57. delete ui;
  58. }
  59.  
  60. void MainWindow::setupMainModel(const QString &tableName, const QStringList &headers)
  61. {
  62. /* Initializes the data model representation with the installation name
  63.   * in the database table, on which will be accessed in the table
  64. * */
  65. modelMain = new QSqlRelationalTableModel(this);
  66. modelMain->setTable(tableName);
  67. /* Set the connection device table, which will be made data substitution.
  68.   * The method setRelation specified column number in which substitution is made,
  69.   * as well as through QSqlRelation class name of the table,
  70.   * the option for which the sample line and column
  71. * from which the data will be taken will be made
  72. * */
  73. modelMain->setRelation(3, QSqlRelation(DEVICE, "id", DEVICE_HOSTNAME));
  74. modelMain->setRelation(4, QSqlRelation(DEVICE, "id", DEVICE_IP));
  75.  
  76. /* Set the columns names in a table with sorted data
  77. * */
  78. for(int i = 0, j = 0; i < modelMain->columnCount(); i++, j++){
  79. modelMain->setHeaderData(i,Qt::Horizontal,headers[j]);
  80. }
  81. // Set Sort Ascending column zero data
  82. modelMain->setSort(0,Qt::AscendingOrder);
  83. modelMain->select(); // Делаем выборку данных из таблицы
  84. }
  85.  
  86. void MainWindow::setupDeviceModel(const QString &tableName, const QStringList &headers)
  87. {
  88. /* Initializes the data model representation
  89.   * with the installation name in the database table,
  90.   * on which will be accessed in the table
  91. * */
  92. modelDevice = new QSqlRelationalTableModel(this);
  93. modelDevice->setTable(tableName);
  94.  
  95. for(int i = 0, j = 0; i < modelDevice->columnCount(); i++, j++){
  96. modelDevice->setHeaderData(i,Qt::Horizontal,headers[j]);
  97. }
  98.  
  99. modelDevice->setSort(0,Qt::AscendingOrder);
  100. modelDevice->select();
  101. }
  102.  
  103. void MainWindow::createUI()
  104. {
  105. ui->tableView->setModel(modelMain); // We set the model on the TableView
  106. ui->tableView->setColumnHidden(0, true); // Hide the column id Records
  107. ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
  108. ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
  109. ui->tableView->resizeColumnsToContents();
  110. ui->tableView->setItemDelegate(new QSqlRelationalDelegate(ui->tableView));
  111. ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
  112. ui->tableView->horizontalHeader()->setStretchLastSection(true);
  113.  
  114. modelMain->select(); // Fetches the data from the table
  115.  
  116. ui->tableViewDevice->setModel(modelDevice);
  117. ui->tableViewDevice->setColumnHidden(0, true);
  118. ui->tableViewDevice->setSelectionBehavior(QAbstractItemView::SelectRows);
  119. ui->tableViewDevice->setSelectionMode(QAbstractItemView::SingleSelection);
  120. ui->tableViewDevice->resizeColumnsToContents();
  121. ui->tableViewDevice->setItemDelegate(new QSqlRelationalDelegate(ui->tableViewDevice));
  122. ui->tableViewDevice->setEditTriggers(QAbstractItemView::NoEditTriggers);
  123. ui->tableViewDevice->horizontalHeader()->setStretchLastSection(true);
  124.  
  125. modelDevice->select();
  126. }

database.h

Compared with the example about QSqlTableModel in this example adds two new methods related to the devices table, namely the creation table with createDeviceTable() and insert records into the table of devices with insertIntoDeviceTable() . As well as adding new devices to the directive define tables and processed guidelines for the main table.

  1. #ifndef DATABASE_H
  2. #define DATABASE_H
  3.  
  4. #include <QObject>
  5. #include <QSql>
  6. #include <QSqlQuery>
  7. #include <QSqlError>
  8. #include <QSqlDatabase>
  9. #include <QFile>
  10. #include <QDate>
  11. #include <QDebug>
  12.  
  13. #define DATABASE_HOSTNAME "ExampleDataBase"
  14. #define DATABASE_NAME "DataBase.db"
  15.  
  16. #define TABLE "MainTable"
  17. #define TABLE_DATE "Date"
  18. #define TABLE_TIME "Time"
  19. #define TABLE_IP "IP"
  20. #define TABLE_HOSTNAME "Hostname"
  21.  
  22. #define DEVICE "DeviceTable"
  23. #define DEVICE_IP "IP"
  24. #define DEVICE_HOSTNAME "Hostname"
  25.  
  26. class DataBase : public QObject
  27. {
  28. Q_OBJECT
  29. public:
  30. explicit DataBase(QObject *parent = 0);
  31. ~DataBase();
  32. /* Methods to work directly with the class.
  33.   * Connect to the database and insert records into the table
  34. * */
  35. void connectToDataBase();
  36. bool inserIntoMainTable(const QVariantList &data);
  37. bool inserIntoDeviceTable(const QVariantList &data);
  38.  
  39. private:
  40. QSqlDatabase db;
  41.  
  42. private:
  43. bool openDataBase();
  44. bool restoreDataBase();
  45. void closeDataBase();
  46. bool createMainTable();
  47. bool createDeviceTable();
  48. };
  49.  
  50. #endif // DATABASE_H

database.cpp

Only part of the methods as well as adding two new methods compared with the previous article have undergone changes in this file.

  1. #include "database.h"
  2.  
  3. DataBase::DataBase(QObject *parent) : QObject(parent)
  4. {
  5.  
  6. }
  7.  
  8. DataBase::~DataBase()
  9. {
  10.  
  11. }
  12.  
  13. void DataBase::connectToDataBase()
  14. {
  15. /* см. статью про QSqlTableModel */
  16. }
  17.  
  18. bool DataBase::restoreDataBase()
  19. {
  20. if(this->openDataBase()){
  21. if((!this->createMainTable()) || (!this->createDeviceTable())){
  22. return false;
  23. } else {
  24. return true;
  25. }
  26. } else {
  27. qDebug() << "Не удалось восстановить базу данных";
  28. return false;
  29. }
  30. return false;
  31. }
  32.  
  33. bool DataBase::openDataBase()
  34. {
  35. /* cm. article QSqlTableModel */
  36. }
  37.  
  38. void DataBase::closeDataBase()
  39. {
  40. db.close();
  41. }
  42.  
  43. bool DataBase::createMainTable()
  44. {
  45. QSqlQuery query;
  46. if(!query.exec( "CREATE TABLE " TABLE " ("
  47. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  48. TABLE_DATE " DATE NOT NULL,"
  49. TABLE_TIME " TIME NOT NULL,"
  50. TABLE_HOSTNAME " INTEGER NOT NULL,"
  51. TABLE_IP " INTEGER NOT NULL"
  52. " )"
  53. )){
  54. qDebug() << "DataBase: error of create " << TABLE;
  55. qDebug() << query.lastError().text();
  56. return false;
  57. } else {
  58. return true;
  59. }
  60. return false;
  61. }
  62.  
  63. bool DataBase::createDeviceTable()
  64. {
  65. QSqlQuery query;
  66. if(!query.exec( "CREATE TABLE " DEVICE " ("
  67. "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  68. DEVICE_HOSTNAME " VARCHAR(255) NOT NULL,"
  69. DEVICE_IP " VARCHAR(16) NOT NULL"
  70. " )"
  71. )){
  72. qDebug() << "DataBase: error of create " << DEVICE;
  73. qDebug() << query.lastError().text();
  74. return false;
  75. } else {
  76. return true;
  77. }
  78. return false;
  79. }
  80.  
  81. bool DataBase::inserIntoMainTable(const QVariantList &data)
  82. {
  83.  
  84. QSqlQuery query;
  85. query.prepare("INSERT INTO " TABLE " ( " TABLE_DATE ", "
  86. TABLE_TIME ", "
  87. TABLE_HOSTNAME ", "
  88. TABLE_IP " ) "
  89. "VALUES (:Date, :Time, :Hostname, :IP )");
  90. query.bindValue(":Date", data[0].toDate());
  91. query.bindValue(":Time", data[1].toTime());
  92. query.bindValue(":Hostname", data[2].toInt());
  93. query.bindValue(":IP", data[3].toInt());
  94.  
  95. if(!query.exec()){
  96. qDebug() << "error insert into " << TABLE;
  97. qDebug() << query.lastError().text();
  98. return false;
  99. } else {
  100. return true;
  101. }
  102. return false;
  103. }
  104.  
  105. bool DataBase::inserIntoDeviceTable(const QVariantList &data)
  106. {
  107.  
  108. QSqlQuery query;
  109. query.prepare("INSERT INTO " DEVICE " ( " DEVICE_HOSTNAME ", "
  110. DEVICE_IP " ) "
  111. "VALUES (:Hostname, :IP )");
  112. query.bindValue(":Hostname", data[0].toString());
  113. query.bindValue(":IP", data[1].toString());
  114. // После чего выполняется запросом методом exec()
  115. if(!query.exec()){
  116. qDebug() << "error insert into " << DEVICE;
  117. qDebug() << query.lastError().text();
  118. return false;
  119. } else {
  120. return true;
  121. }
  122. return false;
  123. }

Result

As a result of the work done and application will contains two tables. The basic table from which that will contain the link to the recording device table and a second table that will contain entries for devices, respectively.

QSqlRelationTableModel Application

Do you like it? Share on social networks!

Ruslan Polupan
  • Feb. 9, 2017, 7:32 p.m.

Небольшое дополнение, столкнулся вот. При вызове setFilter к модели созданной на основе QSqlRelationalTableModel нужно указывать полный фильтр

myModel->setFilter("tableName.fielsName>100");
Evgenii Legotckoi
  • Feb. 10, 2017, 12:23 p.m.

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

Хотя для QSqlTableModel можно и без такой конкретизации обойтись.

z
  • May 21, 2017, 11:11 p.m.

Вставки в базу выругались "" Parameter count mismatch""

Evgenii Legotckoi
  • May 21, 2017, 11:26 p.m.

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

z
  • May 22, 2017, 1:55 a.m.

https://www.dropbox.com/sh/vhxcx0iyq0j4578/AACwgWPnZwNqGBndKESiXfFqa?dl=0

Evgenii Legotckoi
  • May 23, 2017, 12:26 p.m.

В классе DataBase указывается путь к базе данных. В данном случае C:/example/ и т.д. Так вот, у вас есть каталог example ?

z
  • May 27, 2017, 2:14 p.m.

Есть, там была база. Я ее удалил, теперь DeviceTable отрабатывает. На остальное матерится error insert into MainTable " Parameter count mismatch"

Evgenii Legotckoi
  • May 28, 2017, 7:23 p.m.

Изменяли колонки? Названия колонок? Обычно такая ошибка возникает в том случае, если в запросе на добавление не хватает информации, или есть излишняя информация. Не совпадает количество колонок и т.д.

JS
  • March 11, 2019, 3:46 p.m.
  • (edited)

Есть таблица инредиенты с dishesId которая через setRelation получает название блюд из dishesTable, есть productId которая через setRelation получает название продукта из productTable, и столбец количества.

Вопрос: как добавлять в модель ещё один столбец с единицей измерения количества, хочется productId связать второй раз через setRelation с measureId в productTable? Надо в дальнейшем применять setFilter.
Из прешедшего в голову добавлять ещё один столбец в model и копировать в него productId и setRelation на него.

Добавление столбца и setData на него не сработала, хотя в уже имевшиеся стобцы работает.

JS
  • March 12, 2019, 3:29 p.m.
  • (edited)

Ещё вопрос как удалять строки в QSqlRelationalTableModel? Например работает QSqlTableModel, но не в QSqlRelationalTableModel:

  1. QItemSelectionModel *ingredientSelectionModel = ui->ingredientsView->selectionModel();
  2. if (!ingredientSelectionModel->currentIndex().isValid())
  3. {
  4. qDebug() << "ingredientSelectionModel->currentIndex().isValid() error";
  5. return;
  6. }
  7. ingredientsModel->removeRows(ingredientSelectionModel->currentIndex().row(), 1);
  8. ingredientsModel->select();
Evgenii Legotckoi
  • March 12, 2019, 3:41 p.m.

У вас единицы измерения храняться где-то отдельно в базе данных? Если так то можете через setRelation также попытаться сделать.

Но если честно, я в итоге всегда приходил к QSqlQueryModel. Она конечно readOnly и потом приходилось реализовывать все редактирования и удаления. Но по крайней мере за счёт формирования запроса вручную можно было достаточно сложные выборки реализовывать.

Вот пример с QSqlQueryModel

Evgenii Legotckoi
  • March 12, 2019, 3:42 p.m.

Видите ли, думаю, что здесь ограничение из-за нескольких связей, и не удаётся правильно обработать удаление.

Вы можете сделать удаление вручную через SQL запрос, а потом просто обновить выборку в таблице.

Вот пример такого запроса в комментарии под одной из статей

JS
  • March 12, 2019, 3:47 p.m.

Единицы измерения лежат там же где и названия продуктов. Просто в таблице ингредиенты нет ещё одного столбца, на который можно было бы установить setRelation. Я в итоге в базе создал ещё один пустой столбец и его редактировал c setData в который получал инфу через QSqlQueryModel.
Похоже всё проще делать через QSqlQueryModel.

Evgenii Legotckoi
  • March 12, 2019, 3:51 p.m.

Да вы правы. На самом деле проще через QSqlQueryModel, сколько не пытался использовать эти дженерики типо QSqlTableModel и QSqlRelationalTableModel, то всегда упирался в какие-то их ограничения. В итоге проще написать недостающий функционал для QSqlQueryModel для манипуляции с данными (поскольку она сама по себе readOnly), чем пытаться обойти какие-то ограничениях в дженерик-моделях. Они недостаточно гибкие.

JS
  • March 12, 2019, 4:19 p.m.

Большое спасибо за разъяснения!

s
  • Aug. 3, 2019, 5:33 p.m.

А можно ли сделать связную таблицу для QTableWidget?

Evgenii Legotckoi
  • Aug. 3, 2019, 5:51 p.m.

Нет. Используйте QTableView

s
  • Aug. 3, 2019, 7:44 p.m.

А как там тогда checkbox сделать? Через BLOB?

Evgenii Legotckoi
  • Aug. 5, 2019, 2:54 p.m.

BLOB здесь вообще не при чём. Если вам нужен checkbox, то почитайте статью про делегаты

IscanderChe
  • Dec. 6, 2019, 3:29 p.m.

Для чего нужны в коде вот эти строки?

  1. ui->tableView->setItemDelegate(new QSqlRelationalDelegate(ui->tableView));
  2. ...
  3. ui->tableViewDevice->setItemDelegate(new QSqlRelationalDelegate(ui->tableViewDevice));
Павел Дорофеев
  • Jan. 31, 2023, 2:46 p.m.

Хочу поделится, мы сделали свой вариант QSqlRelationalTableModel и заодно к нему новое развитие QTableView, и получилась готовая таблица PblTableDlg, у которой реализованы внешние связи, комбобоксы, чекбоксы, основные кнопки, поиск и чего там уже только нет... Настраивается все элементарно.

Открытый проект github

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • 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
  • A
    Oct. 19, 2024, 5:19 p.m.
    Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html