We can easily generate an Excel Workbook from database data using Ax.rs.Writer functions. But what if we need to place data on certaing regions according different tables and columns ? This may require some complex programming.

Reading database data and updating structured (templated) Excel workbooks can be simplified by using SQL style functions for INSERT, DELETE or UPDATE rows in an Excel workbook.

We can use the functions described in next table to select or update data from an Excel workbook in a similar way we do with a database. Todo that we only need to used named cells.

SQL predicates use four functions described below:

Return Function
int update(Map<Object, Object> map, Consumer<SQLOptions> configurator)
int update(ResultSet rs, Consumer<SQLOptions> configurator)
MemoryResultSet select(Consumer<SQLOptions> configurator)
int delete(Consumer<SQLOptions> configurator)

SQL predictes are operations on Workbook using named cells.

1 Named cells

In Excel, you can create names that refer to:

  • Cell(s) on the worksheet
  • Specific value
  • Formula

After you define Excel names, you can use those names in a formula, instead of using a constant value or cell references. Type a name, to quickly go to that named range of cells

1.1 Rules for Creating Names

To make use of the SQL functions we need to name cells according the column names we will retrieve from database prefixed by a table name. For exampple, to refer the column salary from table employee we will name it employee.salary.

And if we want to have a cell in a workbook to be a placeholder for that, it's name will be employee.salary.

2 Form Layout vs Table Layout

You can setup named cells in two configurations:

  • Form, cells placed in any position in any sheet in the workbook.
  • Table, cells are placed all together in a single row (obviously in one sheet). We also call it a rectangular layout.

2.1 How layout is determined ?

All operations require a table name prefix to search named cells. With that name, a predicate (update, select or delete) analizes the cells layout.

  • If cells are all in a single row on same sheet, the predicate is rectangular (Table),
  • If cells are in different rows, the predicate is non rectangular (Form).

2.2 Single row or multiple rows ?

Depending the layout operations can apply to one row or many rows.

  • If layout is rectangular (Table), any predicate will admit one or many rows in any operation.
  • If layout is non rectangular (Form) any predicate will only admit one row per operation.

3 Update predicate

The update predicate can update rows by name in any sheet of the workbook using Form layout (single row) or Table layout (one or many rows).

Return Method Description
int update(Map map, Consumer<SQLOptions> configurator) Updates all columns from Map into workbook named cells. Returns the number of rows updated (0 if no columns matched or 1).
int update(ResultSet rs, Consumer<SQLOptions> configurator) Updates all columns from ResultSet into workbook named cells. Returns the number of rows updated (0 if no columns matched or the number of rows in the ResultSet).

3.1 SQLOptions

SQLOptions is a lambda function used to configure the predicate.

Method Description
setTableName(String name) Defines the table name.
setEvaluate(boolean evaluate) Defines if evaluation will be done before getting data.
setStartRow(int row) The starting row for processing zero based (notice that Excel row 1 is 0).
setAppend() Indicates the predicate will appply start at las row of sheet (append). This is equivalent to setStartRow(-1)

You can use wb.getNamedRow(String tableName) to dinamically determine the starting row for named cells in table layout (rectangular). This makes easy to setup startRow and copy row.

4 Select predicate

The select predicate can read data by name in any sheet of the workbook using Form layout (single row) or Table layout (one or many rows).

Return Method Description
ResultSet select(Consumer<SQLSelectOptions> configurator) Retrieves a ResultSet from all named cells for a given table name in configurator.

SQLSelectOptions is a lambda function used to configure the predicate. It extends SQLOptions and includes the following methods required by select predicte.

Method Description
setCopyRows(int startRow, int count) Defines the number of rows to copy.

4.1 Row generation

The select predicate can generate rows on read. Rows are generated using a template row and a copy range just before read operation begins. This can be used to expand (generate) rows based on application data.

This feature is controlled by the method setCopyRows(int startRow, int count) in configurator.

In the following example, we use an Excel template to compute a sales report based on daily sales. To build the example we do:

  1. Load the Excel template you can examine here
  2. Load a data set (for the example we use static data from javascript array).
  3. Update dataset into named columns in the Workbook. All named columns are prefixed with gdn. There is a named column for each column in data source (date, code, name, units, price, disccount)
  4. Select all Workbook columns stating with table name sales and perform a copy of target row for each row in data source. This will copy formula changing cell references.

Copy
<script>

    // Initialize sales report.
    var rs = new Ax.rs.Reader().memory(options => {
    	options.setColumnNames([ "date", "code", "name", "units", "price", "disccount" ]);
    	options.setColumnTypes([ Ax.sql.Types.DATE, Ax.sql.Types.CHAR, Ax.sql.Types.CHAR, Ax.sql.Types.INTEGER, Ax.sql.Types.DOUBLE, Ax.sql.Types.DOUBLE ]);
    	options.setColumnScales([ 0, 0, 0, 0, 2, 2 ]);
    });
    
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001001", "Apple",     120, 0.87, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001002", "Peach",     100, 0.65, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001003", "Orange",     80, 0.77, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001004", "Pear",       60, 0.35, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001005", "Tangerine", 150, 0.22, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001006", "Banana",    100, 0.64, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001001", "Apple",     150, 0.85, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001002", "Peach",      80, 0.6,  0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001003", "Orange",    200, 0.62, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001004", "Pear",       60, 0.35, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001005", "Tangerine", 180, 0.2,  0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001006", "Banana",    200, 0.7,  0.01 ]);
    
    // Load Excel template.
    var wb = Ax.ms.Excel.load("https://bitbucket.org/deister/axional-docs-resources/raw/master/Excel/predicates/Sales-Report-Template.xls");

    // Update workbook data. GDN sheet is modified 
    // with all data sales and SALES sheet only with
    // the firt row.
    var numlines = wb.update(rs, options => {
        options.setTableName("gdn");
        options.setStartRow(wb.getNamedRow("gdn") + 1);
        options.setEvaluate(true);        
    }); 

    // Select with setCopyRows options to modify all files 
    // from SALES sheet. The first row is copied numlines - 1
    // times.
    var rs = wb.select(options => {
        options.setTableName("sales");
        // Select data starting 1 row below named cells
        options.setStartRow(wb.getNamedRow("sales") + 1);
        // Copy row starting 1 row below named cells as many times as source data (-1 cause template row is already valid)
        options.setCopyRows(wb.getNamedRow("sales") + 1, numlines - 1);
        options.setEvaluate(true);
    }); 

    // Show rows computed by Excel
    console.log(rs); 

    return wb.toBlob();   
</script>

Data generated on Excel for each source row

+----------+------+-----------+--------+------+---------+------+------+--------+
|date      |code  |description|units   |price |disccount|gross |gst   |day     |
+----------+------+-----------+--------+------+---------+------+------+--------+
|14-01-2019|001001|Apple      |120.0000|0.8700|   0.0100|1.0439|0.0021|120.0000|
|14-01-2019|001002|Peach      |100.0000|0.6500|   0.0100|0.6499|0.0013|220.0000|
|14-01-2019|001003|Orange     | 80.0000|0.7700|   0.0100|0.6159|0.0012|300.0000|
|14-01-2019|001004|Pear       | 60.0000|0.3500|   0.0100|0.2100|0.0004|360.0000|
|14-01-2019|001005|Tangerine  |150.0000|0.2200|   0.0100|0.3300|0.0007|510.0000|
|14-01-2019|001006|Banana     |100.0000|0.6400|   0.0100|0.6399|0.0013|610.0000|
|15-01-2019|001001|Apple      |150.0000|0.8500|   0.0100|1.2749|0.0025|150.0000|
|15-01-2019|001002|Peach      | 80.0000|0.6000|   0.0100|0.4800|0.0010|230.0000|
|15-01-2019|001003|Orange     |200.0000|0.6200|   0.0100|1.2399|0.0025|430.0000|
|15-01-2019|001004|Pear       | 60.0000|0.3500|   0.0100|0.2100|0.0004|490.0000|
|15-01-2019|001005|Tangerine  |180.0000|0.2000|   0.0100|0.3600|0.0007|670.0000|
|15-01-2019|001006|Banana     |200.0000|0.7000|   0.0100|1.3999|0.0028|870.0000|
+----------+------+-----------+--------+------+---------+------+------+--------+

TO DO

This section is incomplete and will be concluded as soon as possible.

5 Delete predicate

The delete operation can be used to:

  • Remove all rows on Table layout.
  • Clear all cells on a Form layout.

Asumming table taptfluj is a rectangular matrix in an Excel Worksheet, to delete all rows below named cells you can do:

Copy
<script>
    // Initialize sales report.
    var rs = new Ax.rs.Reader().memory(options => {
        options.setColumnNames([ "date", "code", "name", "units", "price", "disccount" ]);
        options.setColumnTypes([ Ax.sql.Types.DATE, Ax.sql.Types.CHAR, Ax.sql.Types.CHAR, Ax.sql.Types.INTEGER, Ax.sql.Types.DOUBLE, Ax.sql.Types.DOUBLE ]);
        options.setColumnScales([ 0, 0, 0, 0, 2, 2 ]);
    });
    
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001001", "Apple",     120, 0.87, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001002", "Peach",     100, 0.65, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001003", "Orange",     80, 0.77, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001004", "Pear",       60, 0.35, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001005", "Tangerine", 150, 0.22, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,14), "001006", "Banana",    100, 0.64, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001001", "Apple",     150, 0.85, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001002", "Peach",      80, 0.6,  0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001003", "Orange",    200, 0.62, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001004", "Pear",       60, 0.35, 0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001005", "Tangerine", 180, 0.2,  0.01 ]);
    rs.rows().add([new Ax.sql.Date(2019,01,15), "001006", "Banana",    200, 0.7,  0.01 ]);
    
    // Load Excel template.
    var wb = Ax.ms.Excel.load("https://bitbucket.org/deister/axional-docs-resources/raw/master/Excel/predicates/Sales-Report-Template.xls");

    // Update workbook data. GDN sheet is modified 
    // with all data sales and SALES sheet only with
    // the firt row.
    var numlines = wb.update(rs, options => {
        options.setTableName("gdn");
        options.setStartRow(wb.getNamedRow("gdn") + 1);
        options.setEvaluate(true);        
    }); 

    wb.delete(options => {
        options.setTableName("sales");
        options.setStartRow(wb.getNamedRow("sales") + 1);       
    }); 

    return wb.toBlob(); 
</script>