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:
- Load the Excel template you can examine here
- Load a data set (for the example we use static data from javascript array).
-
Update dataset into named columns in the
Workbook
. All named columns are prefixed withgdn
. There is a named column for each column in data source (date, code, name, units, price, disccount) -
Select all
Workbook
columns stating with table namesales
and perform a copy of target row for each row in data source. This will copy formula changing cell references.
<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:
<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>