QSqlRelationalTable How to display value from other table in a column with foreign key?

950 Views Asked by At

I have a SQLite database with three tables:

graph(ID int primary key, name varchar(64));
vertex(ID int primary key, graphID int references graph(ID), name varchar(64), x int default 0, y int default 0);
edge(ID int primary key, graphID int references graph(ID), sourceID int references vertex(ID), targetID int references vertex(ID), weight real default 1);

In my desktop app I'm using custom classes for model/view

MyTableView : public QTableView
VertexTableModel : public QSqlTableModel
EdgeTableModel : public QSqlRelationalTableModel

I'm setting them up like this:

GraphyEditor::GraphyEditor(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::GraphyEditor),
        vertexModel(new VertexTableModel(parent)),
        edgeModel(new EdgeTableModel(parent)) {
    ui->setupUi(this);

    vertexModel->setTable("vertex");
    ui->vertices->setModel(vertexModel); //  ui->vertices is *MyTableView

    edgeModel->setTable("edge");
    //TODO find fix to the issue
//    edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
//    edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name"));

    ui->edges->setModel(edgeModel); //  ui->egdes is *MyTableView
}

This code works and displays the data correctly, but I would like to substitute columns 2 and 3 (sourceID and targetID) in edgeModel from vertex.ID to vertex.name

I did some searching and found the setRelation method (the same I commented out in my code), but when I use it the edgeModel table shows no edges.

Is it because of my tables schemas or is there something wrong in my code?

How do I achieve this?

EDIT:

Here are implementations of classes I'm using:

MyTableModel.h/cpp

#include <QtSql/QSqlTableModel>

class MyTableModel : public QSqlTableModel {
Q_OBJECT

public:
    explicit MyTableModel(QObject *parent = nullptr);

    void refresh();

    [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override = 0;

    bool setData(const QModelIndex &index, const QVariant &value, int role) override = 0;

signals:

    void databaseUpdated();

};

#endif

#include "MyTableModel.h"
#include <QDebug>
#include <utility>
#include <database/DBManager.h>

MyTableModel::MyTableModel(QObject *parent) :
        QSqlTableModel(parent) {}

void MyTableModel::refresh() { select(); }


VertexTableModel.h/cpp

#include <model/MyTableModel.h>

class VertexTableModel : public MyTableModel {
Q_OBJECT

public:
    explicit VertexTableModel(QObject *parent = nullptr);

    [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;

    bool setData(const QModelIndex &index, const QVariant &value, int role) override;
};

#endif

bool VertexTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
    // checks if value is valid and updates database
}

Qt::ItemFlags VertexTableModel::flags(const QModelIndex &index) const {
    auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    if (index.column() == 2) flags |= Qt::ItemIsEditable;
    return flags;
}

VertexTableModel::VertexTableModel(QObject *parent) : MyTableModel(parent) {}

EdgeTableModel.h/cpp

class EdgeTableModel : public QSqlRelationalTableModel {
Q_OBJECT

public:
    explicit EdgeTableModel(QObject *parent = nullptr);

    void refresh();

    [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;

    bool setData(const QModelIndex &index, const QVariant &value, int role) override;

signals:

    void databaseUpdated();
};

#endif

EdgeTableModel::EdgeTableModel(QObject *parent) : QSqlRelationalTableModel(parent) {
    refresh();
}

void EdgeTableModel::refresh() { select(); }

bool EdgeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
    // checks if value is valid and updates the database
}

Qt::ItemFlags EdgeTableModel::flags(const QModelIndex &index) const {
    auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    if (index.column() == 4) flags |= Qt::ItemIsEditable;
    return flags;
}

GraphyEditor.h/cpp

#ifndef GRAPHY_EDITOR_H
#define GRAPHY_EDITOR_H

#include <QMainWindow>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtGui/QRegExpValidator>
#include <QtSql/QSqlRelationalDelegate>

QT_BEGIN_NAMESPACE
namespace Ui { class GraphyEditor; }
QT_END_NAMESPACE

class GraphyEditor : public QMainWindow {
Q_OBJECT
    Ui::GraphyEditor *ui;
    QSqlTableModel *vertexModel;
    QSqlRelationalTableModel *edgeModel;
    QSqlRelationalDelegate *delegate;

    QString graphID = "";

public:
    explicit GraphyEditor(QWidget *parent = nullptr);

    void setGraphID(const QString &newGraphID);

    ~GraphyEditor() override;
};

#endif

#include <QtWidgets/QWidget>
#include "GraphyEditor.h"
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtSql/QSqlRelationalDelegate>

GraphyEditor::GraphyEditor(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::GraphyEditor),
//        vertexModel(new VertexTableModel(parent)),
        vertexModel(new QSqlTableModel(parent)),
//        edgeModel(new EdgeTableModel(parent)) {
        edgeModel(new QSqlRelationalTableModel(parent)) {
    ui->setupUi(this);

    vertexModel->setTable("vertex");
    vertexModel->setHeaderData(1, Qt::Horizontal, "Vertex ID");
    vertexModel->setHeaderData(2, Qt::Horizontal, "Vertex Name");

    ui->vertices->setModel(vertexModel);
//    ui->vertices->hideColumn(0);
    ui->vertices->hideColumn(1);
    ui->vertices->hideColumn(3);
    ui->vertices->hideColumn(4);

    edgeModel->setTable("edge");
    //TODO find fix to the issue
//    edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
//    edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name as targetName"));

    edgeModel->setHeaderData(2, Qt::Horizontal, "Source Vertex", Qt::DisplayRole);
    edgeModel->setHeaderData(3, Qt::Horizontal, "Target Vertex", Qt::DisplayRole);
    edgeModel->setHeaderData(4, Qt::Horizontal, "Weight");

    delegate = new QSqlRelationalDelegate(this);
    ui->edges->setModel(edgeModel);
    ui->edges->setItemDelegate(delegate);
//    ui->edges->setItemDelegateForColumn(2, delegate);
//    ui->edges->setItemDelegateForColumn(3, delegate);
    ui->edges->hideColumn(0);
    ui->edges->hideColumn(1);

    ui->canvas->setVertices(vertexModel);
    ui->canvas->setEdges(edgeModel);
}

GraphyEditor::~GraphyEditor() {
    delete ui;
    delete vertexModel;
    delete edgeModel;
    delete delegate;
}

void GraphyEditor::setGraphID(const QString &newGraphID) {
    GraphyEditor::graphID = newGraphID;

    vertexModel->setFilter("graphID = " + newGraphID);
    edgeModel->setFilter("graphID = " + newGraphID);

    ui->canvas->setGraphID(newGraphID);
    ui->canvas->refresh();
}


GraphyCanvas.h/cpp

#ifndef GRAPHY_CANVAS_H
#define GRAPHY_CANVAS_H

#include <QWidget>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>

class GraphyCanvas : public QWidget {
Q_OBJECT

    QSqlTableModel *vertexModel = nullptr;
    QSqlRelationalTableModel *edgeModel = nullptr;

    QString graphID = "";

public:

    void setGraphID(const QString &newGraphID);

    explicit GraphyCanvas(QWidget *parent = nullptr);

    void setVertices(QSqlTableModel *vertexTableModel);

    void setEdges(QSqlRelationalTableModel *edgeTableModel);

public slots:

    void refresh();
};

#endif

#include <QPainter>
#include <QPen>
#include <QDebug>
#include <QtWidgets/QtWidgets>
#include <cmath>
#include "GraphyCanvas.h"

GraphyCanvas::GraphyCanvas(QWidget *parent) : QWidget(parent) {
    QPalette newPalette = palette();
    newPalette.setColor(QPalette::Window, Qt::white);
    setPalette(newPalette);
}

void GraphyCanvas::refresh() {
    vertexModel->select();
    edgeModel->select();

    for (auto child : children()) child->deleteLater();

    int edgesCount = edgeModel->rowCount();

    for (int e = 0; e < edgesCount; ++e) {
        //paints edge objects
    }

    int verticesCount = vertexModel->rowCount();

    for (int v = 0; v < verticesCount; ++v) {
        //paints vertex objects
    }
}

void GraphyCanvas::setVertices(QSqlTableModel *vertexTableModel) {
    GraphyCanvas::vertexModel = vertexTableModel;
    connect(vertexModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}

void GraphyCanvas::setEdges(QSqlRelationalTableModel *edgeTableModel) {
    GraphyCanvas::edgeModel = edgeTableModel;
    connect(edgeModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}

void GraphyCanvas::setGraphID(const QString &newGraphID) { GraphyCanvas::graphID = newGraphID; }


main.cpp

#include "setup/GraphySetup.h"

#include <QApplication>
#include <database/DBManager.h>

int main(int argc, char *argv[]) {
    QApplication application(argc, argv);

    DBManager::initialize();

    GraphyEditor editor;
    editor.setGraphID("1");
    editor.showMaximized();

    return QApplication::exec();
}

DBManager is a helper class that's responsible for initializing and accessing the database

4

There are 4 best solutions below

1
On BEST ANSWER

In GraphyEditor.cpp, function GraphyEditor::setGraphID, this line

edgeModel->setFilter("graphID = " + newGraphID);

should be

edgeModel->setFilter("edge.graphID = " + newGraphID);

The underlying query is a join, where the field name graphID belongs to more than one table, so the table name has to be specified along with the field name.

0
On

Maybe modify selectStatement:

QString QSqlRelationalTableModel::selectStatement() const
......
            //!!! my
            fList.append(QLatin1String(", "));
            fList.append(relTableAlias);
            fList.append(QLatin1String("."));
            fList.append(relation.indexColumn());
            fList.append(QLatin1String(" as "));
            fList.append(relation.tableName());
            fList.append(QLatin1String("_"));
            fList.append(relation.indexColumn());
1
On

Your problem is probably hidden in some other place in your project that you don't provide in your question, but not in the code using QSqlRelationalTableModel.

There is only a minimal problem in your (commented) code: the two replaced columns would be having the same name: "name", but you can rename both columns while defining the QSqlRelation.

Here is a m.r.e. to illustrate how to deal with your two tables and a QSqlRelationalTableModel, just in case someone else comes to stackoverflow asking a similar question.

SQLite database dump:

PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE `vertex` (
        `ID`    INTEGER,
        `name`  TEXT,
        PRIMARY KEY(`ID`)
);
INSERT INTO vertex VALUES(1,'one');
INSERT INTO vertex VALUES(2,'two');
INSERT INTO vertex VALUES(3,'three');
INSERT INTO vertex VALUES(4,'four');
INSERT INTO vertex VALUES(5,'five');
INSERT INTO vertex VALUES(6,'six');
INSERT INTO vertex VALUES(7,'seven');
INSERT INTO vertex VALUES(8,'eight');
INSERT INTO vertex VALUES(9,'nine');
CREATE TABLE IF NOT EXISTS "edge" (
        "ID"    INTEGER,
        "sourceID"      INTEGER,
        "targetID"      INTEGER,
        FOREIGN KEY("targetID") REFERENCES "vertex"("ID"),
        PRIMARY KEY("ID"),
        FOREIGN KEY("sourceID") REFERENCES "vertex"("ID")
);
INSERT INTO edge VALUES(1,1,4);
INSERT INTO edge VALUES(2,2,5);
INSERT INTO edge VALUES(3,3,6);
INSERT INTO edge VALUES(4,4,7);
INSERT INTO edge VALUES(5,5,8);
INSERT INTO edge VALUES(6,6,9);
COMMIT;

test.pro

QT = core sql
CONFIG += c++11 console
SOURCES += main.cpp

main.cpp

#include <QCoreApplication>
#include <QTextStream>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlRelationalTableModel>
#include <QSqlRecord>
#include <QSqlField>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTextStream cout(stdout, QIODevice::WriteOnly);
    QTextStream cerr(stderr, QIODevice::WriteOnly);
    auto db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("test.db");
    if (!db.open()) {
        cerr << db.lastError().text() << endl;
        return 1;
    }

    QSqlRelationalTableModel model;
    model.setTable("edge");
    model.setRelation(1, QSqlRelation("vertex", "ID", "name as sourceName"));
    model.setRelation(2, QSqlRelation("vertex", "ID", "name as targetName"));
    model.select();
    auto rec = model.record();
    // headers output
    cout << qSetFieldWidth(15);
    for(int i=0; i<rec.count(); ++i) {
         cout << rec.field(i).name();
    }
    cout << endl;
    // rows output
    for(int i=0; i<model.rowCount(); ++i) {
        rec = model.record(i);
        cout << rec.value("ID").toInt() << rec.value("sourceName").toString() << rec.value("targetName").toString() << endl;
    }
}

And this is the output of the program:

         ID     sourceName     targetName              
          1            one           four              
          2            two           five              
          3          three            six              
          4           four          seven              
          5           five          eight              
          6            six           nine         
0
On

Maybe you can look our open Qt project: [github][1]

This project contains a wrapper above QSqlTableModel + QTableView and realize PblTableDlg class with basic table functionaliy. We use a new variant of QSqlRelationalTableModel = PblSqkRelationalTableModel.

There is PblTableView (inherited by QTableView) and PblTableDlg that contains a db table view with all controls. [1]:https://github.com/PavelDorofeev/Fork-Sql-Qt-4.8.1--SQLite-3--relations--calc-fields