Results 1 to 12 of 12

Thread: Group / Aggregate QAbstractItemModel

  1. #1
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Group / Aggregate QAbstractItemModel

    Hi Guys,

    I've been looking around for a while to see if I can get my QAbstractItemModel to group and/or sum given a defined column in the model. I my case I have a model with three columns from a ledger system - account number, account name, amount. I may have say ten rows in the model, but if those ten rows contains only entries from two accounts I want to show only two rows with the account number, account name and the sum of the amount. Basically exactly like a Group By statement in SQL, but in memory.

    I've had a look to the QSortFilterProxyModel to put on top of the model I already have but I can't get it to group/sum - which means I haven't found an example yet that shows this.

    I will be truely greatfull if someone sould share his/hers experience with grouping rows in QAbstractItemModel.

    Thanks

  2. #2
    Join Date
    Jan 2006
    Location
    travelling
    Posts
    1,116
    Thanks
    8
    Thanked 127 Times in 121 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    I can only see two ways of doing this :

    • using a tree model and grouping items upon insertion into a parent with same name/account and updating the amount of the parent item
    • using QAbstractProxyModel and using an internal intermediate data structure to do the actual group tracking/summing
    Current Qt projects : QCodeEdit, RotiDeCode

  3. #3
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    Hi fullmetalcoder

    Thanks for your reply. Your second way looks appealing can you elaborate in this, please? I guess you'd used the QAbstractItemModel as source for the proxy but how will you get it to group?

  4. #4
    Join Date
    Sep 2009
    Location
    Tashkent, Uzbekistan
    Posts
    107
    Thanks
    1
    Thanked 4 Times in 4 Posts
    Qt products
    Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    Hope this helps
    -- Tanuki

    per cauda vel vaculus cauda

  5. #5
    Join Date
    Jul 2009
    Posts
    139
    Thanks
    13
    Thanked 59 Times in 52 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: Group / Aggregate QAbstractItemModel

    OK, here is my attempt at a proxy model that does aggregation. Some notes:
    - It assumes the data is unsorted. With sorted data a more efficient implementation could be created.
    - It is static. It doesn't update with changes to the source model. This could be fixed by connecting to the appropriate slots.
    - The groupby is done as a string. If only grouping by integers, it could be made more efficient.
    - It only works on flat models.
    - The setup is done in the constructor. It takes a function pointer to an aggregate function (Total and Average are supplied), the groupby column and the aggregate column.
    So here is the code:

    Header:
    Qt Code:
    1. #include <QDebug>
    2. #include <QAbstractProxyModel>
    3. #include <QMap>
    4. #include <QVariantList>
    5. #include <QStringList>
    6.  
    7. class AggregateItem
    8. {
    9. public:
    10. QVariantList items;
    11. QVariant result;
    12. int sourceRow;
    13. };
    14.  
    15. class Aggregator : public QAbstractProxyModel
    16. {
    17. Q_OBJECT
    18.  
    19. public:
    20. Aggregator(QVariant (*aggFunction)(const QVariantList&),
    21. int colGroup,
    22. int colAgg,
    23. int roleGroup = Qt::DisplayRole,
    24. int roleAgg = Qt::DisplayRole,
    25. QObject * parent = 0);
    26.  
    27. virtual int columnCount (const QModelIndex& parent = QModelIndex()) const ;
    28. virtual int rowCount(const QModelIndex& parent) const ;
    29. virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const ;
    30. virtual QModelIndex parent (const QModelIndex& index) const ;
    31. virtual QModelIndex mapFromSource(const QModelIndex&) const ;
    32. virtual QModelIndex mapToSource(const QModelIndex&) const ;
    33. virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const;
    34. virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;
    35. virtual Qt::ItemFlags flags ( const QModelIndex & index ) const;
    36.  
    37. void transformData();
    38.  
    39. private:
    40. QMap< QString, AggregateItem > map;
    41.  
    42. int m_colGroup, m_colAgg, m_roleGroup, m_roleAgg;
    43. QVariant (*m_aggFunction)(const QVariantList&);
    44. };
    To copy to clipboard, switch view to plain text mode 
    Source:
    Qt Code:
    1. #include <QtGui/QApplication>
    2. #include <QStandardItemModel>
    3. #include <QTreeView>
    4. #include <QObject>
    5.  
    6. #include "aggregator.h"
    7.  
    8.  
    9.  
    10. Aggregator::Aggregator(QVariant (*aggFunction)(const QVariantList&),
    11. int colGroup, int colAgg,
    12. int roleGroup, int roleAgg, QObject *parent) :
    13. m_colGroup(colGroup), m_colAgg(colAgg),
    14. m_roleGroup(roleGroup), m_roleAgg(roleAgg),
    15. m_aggFunction(aggFunction)
    16. { }
    17.  
    18. QVariant Total(const QVariantList & vars)
    19. {
    20. int total = 0;
    21.  
    22. foreach(QVariant var, vars)
    23. {
    24. total += var.toInt();
    25. }
    26.  
    27. return QVariant(total);
    28. }
    29.  
    30. QVariant Average(const QVariantList & vars)
    31. {
    32. int total = 0;
    33.  
    34. foreach(QVariant var, vars)
    35. {
    36. total += var.toInt();
    37. }
    38.  
    39. return QVariant(total / vars.count());
    40. }
    41.  
    42. void Aggregator::transformData()
    43. {
    44. QAbstractItemModel * m = sourceModel();
    45. int rowCount = m->rowCount();
    46.  
    47. for (int i = 0; i < rowCount; i++)
    48. {
    49. QString group = m->data(m->index(i, m_colGroup), m_roleGroup).toString();
    50. QVariant agg = m->data(m->index(i, m_colAgg), m_roleAgg);
    51.  
    52. if (!map.contains(group))
    53. {
    54. AggregateItem item;
    55. item.sourceRow = i;
    56. map.insert(group, item);
    57. rows.push_back(group);
    58. }
    59.  
    60. map[group].items.push_back(agg);
    61. }
    62.  
    63. foreach (QString str, map.keys())
    64. {
    65. map[str].result = m_aggFunction(map.value(str).items);
    66. map[str].items.clear(); /* No longer needed. */
    67. }
    68. }
    69.  
    70. QVariant Aggregator::data ( const QModelIndex & index, int role ) const
    71. {
    72. if (index.column() == columnCount() - 1)
    73. {
    74. if ((index.row() < rows.count() && index.row() >= 0) &&
    75. (role == Qt::DisplayRole || role == Qt::EditRole))
    76. return map.value(rows.at(index.row())).result;
    77. else
    78. return QVariant();
    79. }
    80.  
    81. return QAbstractProxyModel::data(index, role);
    82. }
    83.  
    84. Qt::ItemFlags Aggregator::flags ( const QModelIndex & index ) const
    85. {
    86. if (index.column() == columnCount() - 1)
    87. return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    88. else
    89. return QAbstractProxyModel::flags(index);
    90. }
    91.  
    92. int Aggregator::columnCount(const QModelIndex& parent) const
    93. {
    94. return sourceModel()->columnCount(QModelIndex()) + 1;
    95. }
    96.  
    97. int Aggregator::rowCount(const QModelIndex& parent) const
    98. {
    99. return rows.count();
    100. }
    101.  
    102. QModelIndex Aggregator::index(int row, int column, const QModelIndex& parent) const
    103. {
    104. if (row >= rowCount(QModelIndex()) || row < 0)
    105. return QModelIndex();
    106.  
    107. return createIndex(row, column, map[rows.at(row)].sourceRow);
    108. }
    109.  
    110. QModelIndex Aggregator::parent(const QModelIndex& index) const
    111. {
    112. return QModelIndex();
    113. }
    114.  
    115. QModelIndex Aggregator::mapFromSource(const QModelIndex& index) const
    116. {
    117. if (index.isValid())
    118. {
    119. QString groupby = sourceModel()->data(sourceModel()->index(index.row(), m_colGroup)).toString();
    120. for (int i = 0; i < rows.count(); i++)
    121. {
    122. if (rows.at(i) == groupby)
    123. return createIndex(i, index.column(), map[groupby].sourceRow);
    124. }
    125. }
    126. return QModelIndex();
    127. }
    128.  
    129. QModelIndex Aggregator::mapToSource(const QModelIndex& index) const
    130. {
    131. if (index.isValid() && index.column() != columnCount(QModelIndex()) - 1)
    132. return sourceModel()->index(index.internalId(), index.column(), QModelIndex()) ;
    133. else
    134. return QModelIndex();
    135. }
    136.  
    137. bool Aggregator::hasChildren(const QModelIndex &parent) const
    138. {
    139. return !parent.isValid();
    140. }
    To copy to clipboard, switch view to plain text mode 
    and usage:
    Qt Code:
    1. int main(int argc, char *argv[])
    2.  
    3. {
    4. QApplication app(argc, argv);
    5.  
    6. QStandardItemModel model(8, 4);
    7. for (int row = 0; row < 4; ++row) {
    8.  
    9. QStandardItem *item = new QStandardItem(QString("test"));
    10. model.setItem(row, 0, item);
    11.  
    12. item = new QStandardItem(QString("%1").arg(1));
    13. model.setItem(row, 1, item);
    14.  
    15. for (int column = 2; column < 4; ++column) {
    16. QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
    17. model.setItem(row, column, item);
    18. }
    19. }
    20.  
    21.  
    22. for (int row = 4; row < 8; ++row) {
    23.  
    24. QStandardItem *item = new QStandardItem(QString("test2"));
    25. model.setItem(row, 0, item);
    26.  
    27. item = new QStandardItem(QString("%1").arg(3));
    28. model.setItem(row, 1, item);
    29.  
    30. for (int column = 2; column < 4; ++column) {
    31. QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
    32. model.setItem(row, column, item);
    33. }
    34. }
    35.  
    36.  
    37.  
    38. QTreeView * tv = new QTreeView;
    39. Aggregator * ag = new Aggregator(Total, 0, 1);
    40.  
    41. ag->setSourceModel(&model);
    42. ag->transformData();
    43. tv->setModel(ag);
    44. tv->show();
    45.  
    46. return app.exec();
    47. }
    To copy to clipboard, switch view to plain text mode 

  6. #6
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    Hi numbat

    This looks exiting. I'm getting an error though when trying to compile. It's in the declaration of ag (see above line 39 in main.cpp) and the error description is: 'Total' was not declared in this scope. Are you passing the right argument to the constructor?

  7. #7
    Join Date
    Jul 2009
    Posts
    139
    Thanks
    13
    Thanked 59 Times in 52 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: Group / Aggregate QAbstractItemModel

    Sorry, add the following two lines to the end of the header file:
    Qt Code:
    1. QVariant Average(const QVariantList & vars);
    2. QVariant Total(const QVariantList & vars);
    To copy to clipboard, switch view to plain text mode 

  8. #8
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    I'm still getting the same error after pasting the two function prototypes to the class. The error message is the same as before: 'Total' was not declared in this scope. I tried to work around it but I must admit that I don't understand the following argument for the constructor: QVariant (*aggFunction)(const QVariantList&) and you have it later in the class under the private section as QVariant (*m_aggFunction)(const QVariantList&). Does this mean casting QVariantList Items as pointer to m_aggFunction or what? I don't really follow?!

  9. #9
    Join Date
    Jul 2009
    Posts
    139
    Thanks
    13
    Thanked 59 Times in 52 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: Group / Aggregate QAbstractItemModel

    It works for me. I'll attach a zip.
    I tried to work around it but I must admit that I don't understand the following argument for the constructor: QVariant (*aggFunction)(const QVariantList&) and you have it later in the class under the private section as QVariant (*m_aggFunction)(const QVariantList&).
    That is a function pointer. Specifically, it is a pointer to a function that takes a const QVariantList& as argument and returns a QVariant. I use function pointers so that the programmer can provide another function as an argument to the constructor and this function will be used as the aggregate function. This probably signals my C heritage, in C++ we should probably use a class.
    Attached Files Attached Files

  10. The following user says thank you to numbat for this useful post:

    Nightfox (10th January 2010)

  11. #10
    Join Date
    Jul 2009
    Posts
    139
    Thanks
    13
    Thanked 59 Times in 52 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11

    Default Re: Group / Aggregate QAbstractItemModel

    Here is another simpler, although far less efficient way of doing it:
    Qt Code:
    1. #include <QApplication>
    2. #include <QSqlDatabase>
    3. #include <QDebug>
    4. #include <QSqlTableModel>
    5. #include <QAbstractItemModel>
    6. #include <QStringList>
    7. #include <QSqlQuery>
    8. #include <QSqlQueryModel>
    9. #include <QStandardItemModel>
    10. #include <QTreeView>
    11. #include <QHeaderView>
    12.  
    13.  
    14. /* Given a data model, a column to group by, a column to aggregate and an
    15.   aggregate function, this function will return a model with the given
    16.   transformation. Available aggregate function include SUM, AVG, COUNT, MAX
    17.   and MIN.
    18.  */
    19. unsigned int groupByColumn,
    20. unsigned int aggregateColumn,
    21. const QString & aggregateFunction = "SUM")
    22. {
    23. /* Open an in-memory database. */
    24. QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    25. db.setDatabaseName(":memory:");
    26. bool ok = db.open();
    27.  
    28. /* Create a table with the correct number of columns. */
    29. QStringList columns;
    30. for (int i = 0; i < m.columnCount(); i++)
    31. columns.push_back(QString("F%1").arg(i));
    32.  
    33. QString sql = QString("CREATE TABLE T1(%1)").arg(columns.join(","));
    34. QSqlQuery query(db);
    35. ok = query.exec(sql);
    36.  
    37. /* Set up a table model of our newly created table. */
    38. QSqlTableModel tbl(0, db);
    39. tbl.setTable("T1");
    40. tbl.setEditStrategy(QSqlTableModel::OnManualSubmit);
    41. tbl.insertRows(0, m.rowCount());
    42.  
    43. /* Now copy accross our model to the newly created table. */
    44. for (int i = 0; i < m.rowCount(); i++)
    45. for (int j = 0; j < m.columnCount(); j++)
    46. tbl.setData(tbl.index(i, j), m.data(m.index(i, j)));
    47.  
    48. /* Submit changes to database. */
    49. ok = tbl.submitAll();
    50.  
    51. /* Finally, query our database for the grouped by table. */
    52. QString sql2 = QString("SELECT %3(F%2), * FROM T1 GROUP BY F%1").
    53. arg(groupByColumn).
    54. arg(aggregateColumn).
    55. arg(aggregateFunction);
    56.  
    57. out->setQuery(sql2, db);
    58. return out;
    59. }
    60.  
    61.  
    62.  
    63.  
    64. int main(int argc, char * argv[])
    65. {
    66. QApplication a(argc, argv);
    67.  
    68.  
    69. QStandardItemModel model(8, 3);
    70. for (int row = 0; row < 4; ++row)
    71. {
    72. QStandardItem *item = new QStandardItem(QString("test"));
    73. model.setItem(row, 0, item);
    74.  
    75. item = new QStandardItem(QString("%1").arg(1));
    76. model.setItem(row, 1, item);
    77.  
    78. item = new QStandardItem(QString("row %0").arg(row));
    79. model.setItem(row, 2, item);
    80. }
    81.  
    82. for (int row = 4; row < 8; ++row)
    83. {
    84. QStandardItem *item = new QStandardItem(QString("test2"));
    85. model.setItem(row, 0, item);
    86.  
    87. item = new QStandardItem(QString("%1").arg(5));
    88. model.setItem(row, 1, item);
    89.  
    90. item = new QStandardItem(QString("row %0").arg(row));
    91. model.setItem(row, 2, item);
    92. }
    93.  
    94. QAbstractItemModel * tbl = doGroupby(model, 0, 1, "SUM");
    95. tbl->setHeaderData(0, Qt::Horizontal, "Total");
    96. tv.setModel(tbl);
    97. tv.header()->moveSection(0, 3);
    98. tv.show();
    99.  
    100. return a.exec();
    101. }
    To copy to clipboard, switch view to plain text mode 
    Last edited by numbat; 10th January 2010 at 18:51.

  12. #11
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    Hi numbat
    It just compiled the zip and it works like a charm! Last time I added the two lines inside the class scope so that's why I could''t get it to work before. I must say I'm impressed with your post - so little code and yet so powerfull. This is exactly what I wanted to do. Thanks!

  13. #12
    Join Date
    May 2009
    Location
    Copenhagen
    Posts
    50
    Thanks
    6
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Group / Aggregate QAbstractItemModel

    Hi numbat
    It just compiled the zip and it works like a charm! Last time I added the two lines inside the class scope so that's why I could''t get it to work before. I must say I'm impressed with your post - so little code and yet so powerfull. This is exactly what I wanted to do. Thanks!

Similar Threads

  1. How to make QAbstractItemModel 's data checkable
    By nifei in forum Qt Programming
    Replies: 12
    Last Post: 1st April 2013, 19:52
  2. Replies: 2
    Last Post: 22nd September 2008, 09:22

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.