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

980 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
p-a-o-l-o 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.

1
Former contributor 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
P.Dorofeev 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());
0
P.Dorofeev 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