Hard-Soft News

Новости железа и софта

Qt и NULL во внешних ключах

Время от времени мы сталкиваемся с ситуацией, когда поле таблицы, являющееся внешним ключом, содержит значения NULL. С точки зрения проектирования баз данных это ошибка, которую следует избегать. Но, предположим, избежать ее не удалось (базу данных проектировал другой человек, было это давно, внести изменения нельзя и т.д.). Проблема в том, что при работе с объектами QSqlRelationalTableModel строка, которая содержит NULL во внешнем ключе, вообще не будет отображаться. Это связано с тем, как QSqlRelationalTableModel генерирует запросы SQL.

Рассмотрим пример. Путь у нас есть две таблицы

Поле Items.Color — это внешний ключ, который ссылается на Colors.id. Предположим, что содержимое таблицы Items выглядит так:

То есть, сдержит NULL в поле внешнего ключа. При работе с QSqlRelationalTableModel строка 3 не будет отображаться. Было предложено несколько решений проблемы, разной степени неуклюжести. Я предлагаю решение, которое кажется мне наиболее гибким и универсальным. Суть его в том, чтобы возложить обработку отношений между таблицами на класс-делегат. Специальный класс WeakRelationalDelegate позволяет устанавливать отношения между таблицами, даже если они содержат значения NULL в полях внешних ключей. Объявление класса выгладит следующим образом:

class WeakRelationalDelegate : public QStyledItemDelegate
{
public:
    WeakRelationalDelegate(QObject * parent = 0);
    void addRelation(const QString &fkColumn, const QString &extTableName, const QString &extIDColumn, const QString &extDisplayColumn);
    void refreshRelations();
protected:
    QWidget * createEditor ( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData ( QWidget * editor, const QModelIndex & index ) const;
    void updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
    void setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const;
private:
    TableMap tables;
};

Вот как выглядит применение класса WeakRelationalDelegate в нашем примере:

    QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
    db.setDatabaseName(QString("Driver={SQL SERVER};Server=LOCAL;Database=test;UID=sa;PWD=Password123"));
    if (! db.open()) {
        qDebug() << "not open";
        return;
    }
    QSqlTableModel * m = new QSqlTableModel(this);
    m->setTable("Items");
    WeakRelationalDelegate  * wd = new WeakRelationalDelegate(this);
    wd->addRelation("Color","Colors", "Id", "Name");
    ui->tableView->setItemDelegate(wd);
    m->select();
    ui->tableView->setModel(m);
    ui->tableView->show();

Метод addRelation() создает отношение между таблицей Items, представленной моделью m, и таблицей Colors. Первый аргумент метода — имя поля таблицы Items, являющегося внешним ключом (между прочим, это поле не обязательно должно быть объявлено как внешний ключ в самой БД). Второй аргумент — имя таблицы, на поле которой ссылается внешний ключ. Третий аргумент — имя поля, на которое ссылается внешний ключ. Четвертый аргумент — имя поля таблицы Colors, значениями которого должно быть заменено поле Color в таблице Items. Обратите внимание, что в качестве модели таблицы Items мы можем использовать как объект  QSqlRelationalTableModel, так и объект QSqlTableModel. В результате получим вот что:

Для редактирования поля внешнего ключа используется раскрывающийся список, как и в случае использования QSqlRelationalDelegate. Исходные тексты класса, приложения примера и базы данных вы найдете на этой странице.


© 2011 Андрей Боровский

Комментировать