diff options
Diffstat (limited to 'kspread/kspread_functions_database.cpp')
-rw-r--r-- | kspread/kspread_functions_database.cpp | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/kspread/kspread_functions_database.cpp b/kspread/kspread_functions_database.cpp new file mode 100644 index 00000000..a294914d --- /dev/null +++ b/kspread/kspread_functions_database.cpp @@ -0,0 +1,537 @@ +/* This file is part of the KDE project + Copyright (C) 1998-2002 The KSpread Team + www.koffice.org/kspread + Copyright (C) 2005 Tomas Mecir <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +// built-in database functions + +#include "functions.h" +#include "valuecalc.h" +#include "valueconverter.h" + +using namespace KSpread; + +// prototypes +Value func_daverage (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dcount (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dcounta (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dget (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dmax (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dmin (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dproduct (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dstdev (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dstdevp (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dsum (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dvar (valVector args, ValueCalc *calc, FuncExtra *); +Value func_dvarp (valVector args, ValueCalc *calc, FuncExtra *); +Value func_getpivotdata (valVector args, ValueCalc *calc, FuncExtra *); + +// registers all database functions +void RegisterDatabaseFunctions() +{ + FunctionRepository* repo = FunctionRepository::self(); + Function *f; + + f = new Function ("DAVERAGE", func_daverage); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DCOUNT", func_dcount); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DCOUNTA", func_dcounta); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DGET", func_dget); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DMAX", func_dmax); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DMIN", func_dmin); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DPRODUCT", func_dproduct); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DSTDEV", func_dstdev); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DSTDEVP", func_dstdevp); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DSUM", func_dsum); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DVAR", func_dvar); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("DVARP", func_dvarp); + f->setParamCount (3); + f->setAcceptArray (); + repo->add (f); + f = new Function ("GETPIVOTDATA", func_getpivotdata); // partially Excel-compatible + f->setParamCount (2); + f->setAcceptArray (); + repo->add (f); +} + +int getFieldIndex (ValueCalc *calc, Value fieldName, + Value database) +{ + if (fieldName.isNumber()) + return fieldName.asInteger() - 1; + if (!fieldName.isString ()) + return -1; + + TQString fn = fieldName.asString(); + int cols = database.columns (); + for (int i = 0; i < cols; ++i) + if (fn.lower() == + calc->conv()->asString (database.element (i, 0)).asString().lower()) + return i; + return -1; +} + +// *********************************************************** +// *** DBConditions class - maintains an array of conditions *** +// *********************************************************** + +class DBConditions { + public: + DBConditions (ValueCalc *vc, Value database, Value conds); + ~DBConditions (); + /** Does a specified row of the database match the given criteria? + The row with column names is ignored - hence 0 specifies first data row. */ + bool matches (unsigned row); + private: + void parse (Value conds); + ValueCalc *calc; + Condition **cond; + int rows, cols; + Value db; +}; + +DBConditions::DBConditions (ValueCalc *vc, Value database, + Value conds) : calc(vc), cond(0), rows(0), cols(0), db(database) +{ + parse (conds); +} + +DBConditions::~DBConditions () { + int count = rows*cols; + for (int r = 0; r < count; ++r) + delete cond[r]; + delete[] cond; +} + +void DBConditions::parse (Value conds) +{ + // initialize the array + rows = conds.rows() - 1; + cols = db.columns(); + int count = rows*cols; + cond = new Condition* [count]; + for (int r = 0; r < count; ++r) + cond[r] = 0; + + // perform the parsing itself + int cc = conds.columns (); + for (int c = 0; c < cc; ++c) + { + // first row contains column names + int col = getFieldIndex (calc, conds.element (c, 0), db); + if (col < 0) continue; // failed - ignore the column + + // fill in the conditions for a given column name + for (int r = 0; r < rows; ++r) { + Value cnd = conds.element (c, r+1); + if (cnd.isEmpty()) continue; + int idx = r * cols + col; + if (cond[idx]) delete cond[idx]; + cond[idx] = new Condition; + calc->getCond (*cond[idx], cnd); + } + } +} + +bool DBConditions::matches (unsigned row) +{ + if (row >= db.rows() - 1) + return false; // out of range + + // we have a match, if at least one row of criteria matches + for (int r = 0; r < rows; ++r) { + // within a row, all criteria must match + bool match = true; + for (int c = 0; c < cols; ++c) { + int idx = r * cols + c; + if (!cond[idx]) continue; + if (!calc->matches (*cond[idx], db.element (c, row + 1))) { + match = false; // didn't match + break; + } + } + if (match) // all conditions in this row matched + return true; + } + + // no row matched + return false; +} + + +// ******************************************* +// *** Function implementations start here *** +// ******************************************* + +// Function: DSUM +Value func_dsum (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value res( Value::Float ); + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) + res = calc->add (res, val); + } + + return res; +} + +// Function: DAVERAGE +Value func_daverage (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value res; + int count = 0; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + res = calc->add (res, val); + count++; + } + } + if (count) res = calc->div (res, count); + return res; +} + +// Function: DCOUNT +Value func_dcount (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + int count = 0; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if ((!val.isEmpty()) && (!val.isBoolean()) && (!val.isString())) + count++; + } + + return Value (count); +} + +// Function: DCOUNTA +Value func_dcounta (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + int count = 0; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty()) + count++; + } + + return Value (count); +} + +// Function: DGET +Value func_dget (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + bool match = false; + Value result = Value::errorVALUE(); + int rows = database.rows() - 1; // first row contains column names + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + if (match) { + // error on multiple matches + result = Value::errorVALUE(); + break; + } + result = database.element (fieldIndex, r + 1); + match = true; + } + + return result; +} + +// Function: DMAX +Value func_dmax (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value res; + bool got = false; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + if (!got) { + res = val; + got = true; + } + else + if (calc->greater (val, res)) + res = val; + } + } + + return res; +} + +// Function: DMIN +Value func_dmin (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value res; + bool got = false; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + if (!got) { + res = val; + got = true; + } + else + if (calc->lower (val, res)) + res = val; + } + } + + return res; +} + +// Function: DPRODUCT +Value func_dproduct (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value res = 1.0; + bool got = false; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + got = true; + res = calc->mul (res, val); + } + } + if (got) + return res; + return Value::errorVALUE (); +} + +// Function: DSTDEV +Value func_dstdev (valVector args, ValueCalc *calc, FuncExtra *) +{ + // sqrt (dvar) + return calc->sqrt (func_dvar (args, calc, 0)); +} + +// Function: DSTDEVP +Value func_dstdevp (valVector args, ValueCalc *calc, FuncExtra *) +{ + // sqrt (dvarp) + return calc->sqrt (func_dvarp (args, calc, 0)); +} + +// Function: DVAR +Value func_dvar (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value avg; + int count = 0; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + avg = calc->add (avg, val); + count++; + } + } + if (count < 2) return Value::errorDIV0(); + avg = calc->div (avg, count); + + Value res; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) + res = calc->add (res, calc->sqr (calc->sub (val, avg))); + } + + // res / (count-1) + return calc->div (res, count - 1); +} + +// Function: DVARP +Value func_dvarp (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + Value conditions = args[2]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + + DBConditions conds (calc, database, conditions); + + int rows = database.rows() - 1; // first row contains column names + Value avg; + int count = 0; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) { + avg = calc->add (avg, val); + count++; + } + } + if (count == 0) return Value::errorDIV0(); + avg = calc->div (avg, count); + + Value res; + for (int r = 0; r < rows; ++r) + if (conds.matches (r)) { + Value val = database.element (fieldIndex, r + 1); + // include this value in the result + if (!val.isEmpty ()) + res = calc->add (res, calc->sqr (calc->sub (val, avg))); + } + + // res / count + return calc->div (res, count); +} + +// Function: GETPIVOTDATA +// FIXME implement more things with this, see Excel ! +Value func_getpivotdata (valVector args, ValueCalc *calc, FuncExtra *) +{ + Value database = args[0]; + int fieldIndex = getFieldIndex (calc, args[1], database); + if (fieldIndex < 0) + return Value::errorVALUE(); + // the row at the bottom + int row = database.rows() - 1; + + return database.element (fieldIndex, row); +} |