Using Molnify's Database

Introduction

There are many reasons to save user data in a Molnify application. Dynamic product lists, usage logging, issue statuses, and sales data, are just a few example cases where saving to the database is key.

By connecting your application with the database, you can store, modify and retrieve data without updating your application.

When you upload an Excel file or link a Google spreadsheet to Molnify, you provide Molnify with instructions for how to create your application. But Molnify does not update the instruction file once it is in use, it is only used by you when you wish to upload a new version of the application. For instance, two users can use the same application simultaneously, without the risk of their inputs "clashing" and overriding each other. Because the data in the instruction file cannot change over the course of an application's lifespan, we call that data "static", i.e. non-changing.

On the other hand, if a user activates an action to store data in the application's database, then that data will be available even after the user has closed the application (and, depending on your instruction file, other users of your application may be able to access the data) Since the database data may be modified without updating the app, we call the data in the database "dynamic", i.e. changing.

Before you start

Before you start using Molnify's Database functionalities in your application, you must ensure that you have a static (non-changing) application ID. You have this if your application has the ID metadata set up. If you do not have the ID set up, you will lose access to all your table data whenever you update your application.

Once you have the ID metadata included in your application, then you are ready to unleash the power of Molnify's database!

The database, Molnify autofills, and other instructions that interact with the database use MySQL.

The Data Manager

The Data Manager is used to configure your database connection, and you can access it from My Applications, or from your application's sidebar menu. The data manager is accessible only to the application's uploader and its superusers.

Link to the Data manager
You can access the Data manager in the sidebar of your application, or from the page "My Apps"

The Data Manager consists of two tools: the table management interface and the database query tester.

Table management

The table management interface lets you create and edit tables for your application. You can also use it to edit to the data stored in your tables.

Creating a table

To create a table, click on the "Create New Table" button. A new table will be automatically created and assigned to your application. The new table will be given a name of the form data_<your-application-id>_ followed by a number, and this name cannot be changed.

The list of tables for an application
The list of tables for an application

The actions that add or modify data to your tables require the columns _molnify_user_ip, _molnify_user_email, _molnify_application_id and _molnify_timestamp to exist in the table. So, they are included automatically whenever you create a new table.

Click on the name of a table which you have created to bring up its details in the right portion of the window.

Table data

Once you have opened up a table in the detailed view, you can inspect its data in a searchable view. Note that if you just created your database table, then it will not contain any data initially.

Data for an application
The view where you can insert, edit and remove data for an application

In addition to reading the data, you can also insert new rows or delete selected rows. You can select a row or several by clicking on the checkbox in the rightmost column on its row. Click on an entry in order to edit it - any changes will be saved to the database as soon as you deselect the entry.

Unlike Excel, there is no undo-functionality in the database. That means that any changes to your tables' structure or data cannot be reverted.

Table statistics

The shown statistics are, unlike the other views on this page, not editable.

Database statistics for an application
Database statistics for an application

Each table has a counter associated with it, the Auto Increment. If your table has a column which is the primary key with AUTO_INCREMENT enabled, then the value of this counter is the next new value for that column.

Table columns

You can use this table for adding, editing and removing columns in the same way as you use the data table above it.

Columns for a table
The view where you can insert, edit and remove the columns of a database table

A table's columns defines the structure of its data. For example, if there is a column named variable, then each row of data may have a value for variable. Column names are ideally descriptive, and we recommend that you avoid names that do not describe their function. For example, we consider the name max_acceleration to be easier to work with than the abbreviated ma.

If a table's columns define its data, then a column's data type defines the data in the column. The data type is a label that instructs the database on how it shall interpret the 1s and 0s that constitute the data, how much memory it should reserve for the data, and more.

Data type Use case
INT UNSIGNED Non-negative whole numbers
INT Whole numbers that may be negative, recordId
BOOL Values that are either TRUE/FALSE. The database converts BOOL columns to TINYINT, where 0 is FALSE and all other values are TRUE
FLOAT Decimal numbers
TIMESTAMP Date and time of an audit event, or other logging
VARCHAR Text strings, up to 255 characters
TEXT Text strings longer than a VARCHAR
Common data types and their uses

The actions that insert data into the database require the columns _molnify_user_ip (VARCHAR), _molnify_user_email (VARCHAR), _molnify_application_id (VARCHAR) and _molnify_timestamp (TIMESTAMP). They are included in newly created tables by default.

You can set the default value of a column as well. Then, any row of data that is inserted into the table and does not have a value specified for that column will be assigned the default value. For example, you can use a BOOL column is_deleted with a default value of 0 to initialise data as "active", and then use an add row to database action to set is_deleted to 1 when the user wishes to remove an entry. This is a technique for soft-removing data, as no application can actually remove data from the database.

Table indexes

An index is an instruction for the database to prepare the stored data for lookup, and how. For example, if your application stores a list of cars, then an index on the column "brand" may make the query SELECT model FROM `data_<your-application-id>_0` WHERE brand = "VOLVO"; perform better, at the expense of very slightly slowing down insertion of data. For most tables, the performance benefit is too small, but it can be a critical optimization for tables with thousands of rows of data.

Indexes for a table
The view where you can insert, edit and remove indexes on a database table

While it is technically not an index, you can define a "primary key" for your database table by creating an index with the name PRIMARY. A primary key is a constraint on the data, and all rows of the table must have a unique value for the primary key column(s). We recommend that you define a primary key for your table, as it has the following benefits:

  • A primary key ensures that your data is sound, and internally consistent. For example, it can prevent you from accidentally having two price definitions for the same product ID
  • A primary key allows you to update rows of data with the add row to database action

Query tester

Sometimes data does not load into your application as you expect, or you need to experiment with different queries to figure out how to retrieve the data your application needs. You can use the query tester to try out and validate your queries before including them in your Molnify application.

Query tester for an application
The query tester, after having tested a query

Note that the query tester is used for testing queries, and not a mix of Excel functions and queries. For example, the following query would result in an error, as it is an Excel formula that attempts to read a value from a spreadsheet cell: ="SELECT value, recordId FROM `data_<your-application-id>_0` WHERE _molnify_user_email = '"&B2&"';". You will need to replace any cell references in your query with data before using it in the tester.

Error Explanation
General troubleshooting tip for complex queries Simplify your query, and verify that each individual component of the query works. Then, slowly assemble it back together, and see when it breaks. That is where your issue most likely is.
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ..." The database does not know how to interpret the query. For example, a table name which includes dashes (-) must be enclosed in backticks (`), or there might be a comma (,) after the last item in the SELECT. But, the database does not give you more detail than on which line it failed. So you can split your query into multiple lines, to narrow down on the issue.
Unknown column 'column' in 'field list' You are attempting to use a column that does not exist. Check that you have spelled the column name correctly, and that the column exists in the table.
User 'you@email.com' and application 'application-id' are not allowed to execute query: For security reasons, you are only allowed to execute READ-ONLY queries, that only affect the columns in the tables which your application has access to. All of those tables are listed in the Table manager. Contact us if you are not permitted to execute a query which you believe you should be allowed to execute.
Common error messages

Autofill

Molnify's Autofill functionality is a powerful tool for using data from the database in your application. It allows you to specify a range of cells, and a query to retrieve data from the database. Molnify will then populate the cells with the data from the query, allowing you to use the data in Excel formulas and in outputs.

Name the range that makes out the table contents (i.e., B2:C3 in this case). Use this reference in your autofill to have the table contents replaced by the data returned from the database table.

This metadata would run the SQL query (="SELECT * from data_myTable") and populate the named range (data) with the results.

Query cookbook

Here follows some example queries to get you started. More complex queries than these are supported, as well.

Query Description
SELECT * FROM `data_<your-application-id>_0` Select all data in all rows from the table
SELECT `colour`, `value` FROM `data_<your-application-id>_0` Select only specific columns from the table
SELECT * FROM `data_<your-application-id>_0` WHERE `colour` = 'red' Select only rows from a table where a column matches a condition
SELECT * FROM `data_<your-application-id>_0` WHERE `colour` = 'red' AND `value` > 5 Select only rows from a table based on multiple conditions
SELECT * FROM `data_<your-application-id>_0` WHERE `colour` = 'red' ORDER BY `_molnify_timestamp` DESC Order the result based on the values in a column
SELECT * FROM `data_<your-application-id>_0` WHERE `colour` = 'red' ORDER BY `_molnify_timestamp` DESC LIMIT 10 Fetch the 10 most recent rows of data that match the condition
SELECT COUNT(*) FROM `data_<your-application-id>_0` WHERE `value` < 5 Count the number of rows that match the condition
SELECT SUM(`value`) FROM `data_<your-application-id>_0` WHERE YEAR(`_molnify_timestamp`) = 2024 Count the number of rows that were created during a particular year
SELECT DISTINCT(`customer`) FROM `data_<your-application-id>_0` List all different customers that have data in the table
Common operations when fetching data

Actions

You can use the following actions in order for your application to add new data or update the data in your database table.

Add record to database

A record is a snapshot of your application's variables. Unlike a scenario, which captures all inputs, a record captures all variables which have corresponding column names in the application's records table, and stores them in a new row.

You must use the RecordTableName metadata to specify the name of the table that will contain the records for your application.

The database table for storing records must also be set up as follows:

  1. The columns _molnify_user_ip, _molnify_user_email, _molnify_application_id and _molnify_timestamp must exist in the table.
  2. There must also be a column named recordId, of a data type that can store whole numbers, such as INT. This name is, like all column names in the database, case-sensitive.
  3. The table must have a primary key (see indexes), and the primary key must consist of only the recordId column.
  4. The recordId column must also be set to auto increment.

Example:

Setting up a records table
An example of a database table configured to be used as a records table for an application

Once you have your database table set up, and metadata naming the table as your records table, you are ready to include the add record to database action in your application.

When a user activates this action, the current values of its variables are stored into a new row of your application's records table. But, if your application has a variable named recordId, and it has a value that is not 0 or empty when the user activates the action, then the existing record corresponding to that ID will instead be updated. After a record has been created, the recordId variable will be updated to the recordId of the newly created row. This way, any subsequent saved records by the same user will update the previously saved record - until the webpage is refreshed or the application is reset.

If your application also has a variable named recordId, and you load the application with it set using a URL parameter, then the data from the corresponding record will be loaded into the application.

The add record to database action does not offer many customization options, but it also does not require extensive configuration.

Option Value Description
type addRecord This must be set to instruct Molnify to create an add record to database action. This option is required
title Any string This sets the text displayed on the generated action button
name Any string This sets an internal name for the action, and is used to integrate the action with e.g. the multiple action and JavaScript functions
hidden TRUE or FALSE Set this to TRUE if you want to hide the generated action button, and instead activate the action with JavaScript, using e.g. custom buttons

Example:

This example creates an add record action, since type is set to addRecord. The action button's title will be Add Record to database.

Add row to database

You can use the add row to database action for a configurable method of storing and updating database data. The action allows you to define which values should be stored in which columns and allows for a primary key consisting of several columns when updating data.

The columns _molnify_user_ip, _molnify_user_email, _molnify_application_id and _molnify_timestamp are required to exist in the table. Values for these columns are created automatically by the action.

Option Value Description
type insertRow This must be set to instruct Molnify to create an add row to database action. This option is required
tableName data_<your-application-id> This must be set to instruct Molnify which table to insert the data into. This option is required
.col Any value Replace "col" in the option name with the name of the database column to insert data into. A dot (.) must prefix the column name. The action requires at least one ".col" option
k.keycol Any value If a database column is part of the primary key, replace "keycol" in the option name with the name of the column to insert data into. A "k" and a dot (k.) must prefix the column name.
title Any string This sets the text displayed on the generated action button
name Any string This sets an internal name for the action, and is used to integrate the action with e.g. the multiple action and JavaScript functions
hidden TRUE or FALSE Set this to TRUE if you want to hide the generated action button, and instead activate the action with JavaScript, using e.g. custom buttons

If you use the add row to database action to insert a row of data into a database table where the table's primary key is exactly one column and the key is auto-incremented, then Molnify will update the value of the recordId variable (if it exists) to the newly generated value.

Example:

This example creates an add row to database action, since type is set to insertRow. The action button's title will be Add Data. The values for the database columns 'key' (also primary key), 'col1', and 'col2' will be inserted into the database table data_my_table.

Download Custom Data

The download custom data action can be used to allow the users of your application to download the result of a query. The downloaded file can be in either .csv or .json format.

Example:

This example creates a download query action, since type is set to downloadQuery. The action button's title will be Download Query.The result of the MySQL query will be downloaded as a csv file, delimited by semicolons ;).

Download Database Table

An entire database table can be downloaded through the download database table action. Unlike the download custom data action, this action does not allow choosing file format, delimiter, or query.

This action is equivalent to the download custom data action with the query SELECT * FROM data_<your-application-id>_0.

Example:

This example creates a download table action, since type is set to downloadTable. The action button's title will be Download Table, and clicking on it will download the table data_my_table.

Table access and application ID

The list of tables that your database may use is determined by its ID. As such, if you need to change your application's ID, you should either delete all your database tables for the application or contact us for help to migrate your data to your new application ID.

Example: a logging tool

This example will walk you through how to build a logging tool in Molnify.

Logs can easily be abstracted to other concepts where your user stores transactions. For example, a log showing a product name, a quantity, and a price is the same as an order.

We pretend that we have a set of locations, each of which should be inspected regularly. An inspection should determine if a location is in "Good" condition, "Poor" condition (but serviceable), or if action is needed.

First things first: we set up our metadata with an ID for the application, a name, a description, a link to this guide, and a template for some quick styling. We place this in the columns A-B in our sheet. In general, we recommend that you structure your workbook so that you can find your metadata easily.

A B
1
2
3
4
5

Copy for Excel Copy for Sheets

Note that you will have to change the ID if you copy this example, as this ID is taken by the example application.

Now that our application has a static ID and some basic metadata, we can add some inputs to it.

First off, we need a list of names of our inspectors, and a list of warehouse locations. We place them in columns K and L, giving us some space to their left for inputs, outputs and actions. For brevity, we choose to have 5 inspectors and 10 locations, but the application could easily be extended to include more.

The states "Good", "Poor" and "Action needed" go into column M.

K L M
1 Names Warehouses States
2 Ivan Metcalf Chepstow Good
3 Renee Ellison Tarrin Poor
4 Aisha Morris Wigston Action needed
5 Barrie Henderson Pitmedden
6 Ewa Mistry Laewaes
7 Kirkwall
8 Woodpine
9 Rochdale
10 Peatsland
11 Auctermunty

Copy for Excel Copy for Sheets

The names and warehouse locations are randomly generated.

Now, we assign these to inputs. We create a named range "Names" for the names, a named range "Warehouses" for the warehouse locations, and a named range "States" for the states. By enabling data validation for the inputs, and typing "dropdown" in their UI cell, we instruct Molnify that the inputs should be rendered as dropdown lists with options defined by the data validation ranges.

D E F
1 Name dropdown
2 Warehouse dropdown
3 Status dropdown

Copy for Excel Copy for Sheets

We use data validation to define the range of values which can be entered into the inputs: limit the accepted values of "Name" to those in "Names", the values of "Warehouse" to those in "Warehouses", and the values of "Status" to those in "States".

We can now upload the app, and we have some working dropdowns. But, there is no way to save a log yet.

The logging app so far

Setting up the database

In order to save the logs, we need to set up a database table. We do so in the Data manager.

Link to the Data manager
You can access the Data manager in the sidebar of your application, or from the page "My Apps"

In the Data manager, we click on the button to create a new table. It will be automatically assigned the name "data_<my-app-id>_0". After the table is created, we click on its name to open the configuration for our new table.

The newly created table
The newly created table will be named data_<your-application-id>_0, and will contain the Molnify default columns with no data

To store our log data, we create the following columns in the "Columns" table: recordId, name, warehouse, status. We cannot set recordId to auto increment unless we define it as the primary key first, so we do that by creating an index with the name PRIMARY. After we add the primary key, we set recordId to be auto-incremented, so new recordIds will be generated for new logs.

We define columns for recordId, name, warehouse and status
Unlike the other three new columns, recordId should be of type INT and not nullable. After we create the primary key, we set it to auto-increment

And that is all the database setup we will need. Now, we will use the add record to database action to add the ability to save logs to the application.

Saving logs

We add our newly created table as the application's records table in the metadata, and we create the action.

A B
1
2
3
4
5
6

Copy for Excel Copy for Sheets

We use an Excel formula to calculate the RecordTableName. This way, if you change the application's ID, the RecordTableName changes as well.

H I
1 type addRecord
2 title Saving log
2 name save-log
3 hidden TRUE
5 successUrl ="https://app.molnify.com/app/"&B1

Copy for Excel Copy for Sheets

We use the successUrl action option to re-load the application after we save a value, in order to clear the input fields. Similarly to when we defined the metadata for RecordTableName, we use an Excel formula to calculate the URL in successUrl.

For Molnify to know which inputs to save in which database columns, we assign variable names to the inputs matching the column names. E.g. the input for name should have the ui-string variable=name;dropdown.

We also hide the default action button for the action. The default action button is visible on all input tabs, and we are limited in how we style it. So, we create a custom action button using an HTML Panel and place it among inputs.

D E F
1 Name variable=name;dropdown
2 Warehouse variable=warehouse;dropdown
3 Status variable=status;dropdown
4 <button class="btn btn-sm btn-success buttonMargin molnifyActionButton" onClick="performActionWithName('save-log');">Save log</button> html;amonginputs;hidecopy;

Copy for Excel Copy for Sheets

The button element uses the onClick attribute to call the hidden action when clicked on. The classes btn btn-sm btn-success buttonMargin molnifyActionButton are for styling.

Now that we're able to save logs, it is time to visualise the results. We want to display two tables. The first table should show the ten most recently made logs. The second table will show an overview of our locations, their last reported state, who made that report, and when the report happened.

Table 1: most recent logs

First, we prepare a named range in the Excel file to read the most recent logs into. We will display the 10 most recent logs, so the range should be 10 rows. For each of the rows, we want to display when the log was made, who made it, for which warehouse, and which state was logged - that is a total of 4 columns. We name the cells O2:R11 "Recent_logs", and colour them grey - the grey has no meaning in Molnify but helps us remember that we have defined a named range there. We also write the column headers "timestamp", "name", warehouse", and "status" above the named range, to help us remember what data will be filled into which column.

Then, we create a table in the cells below the named range, N13:S23. In each cell of the table, we read the value of the corresponding cell in the named range, or leave the cell blank if there is no read value. If we do not include the check for empty values, then there could be rows displaying 0s if the result of the database query does not fill all rows.

N O P Q R S
13 Warehouses <b>Timestamp</b> <b>Inspector</b> <b>Warehouse</b> <b>Status</b> table
14 =IF(O2 <> "", O2, "") =IF(P2 <> "", P2, "") =IF(Q2 <> "", Q2, "") =IF(R2 <> "", R2, "")
15 =IF(O3 <> "", O3, "") =IF(P3 <> "", P3, "") =IF(Q3 <> "", Q3, "") =IF(R3 <> "", R3, "")
16 =IF(O4 <> "", O4, "") =IF(P4 <> "", P4, "") =IF(Q4 <> "", Q4, "") =IF(R4 <> "", R4, "")
17 =IF(O5 <> "", O5, "") =IF(P5 <> "", P5, "") =IF(Q5 <> "", Q5, "") =IF(R5 <> "", R5, "")
18 =IF(O6 <> "", O6, "") =IF(P6 <> "", P6, "") =IF(Q6 <> "", Q6, "") =IF(R6 <> "", R6, "")
19 =IF(O7 <> "", O7, "") =IF(P7 <> "", P7, "") =IF(Q7 <> "", Q7, "") =IF(R7 <> "", R7, "")
20 =IF(O8 <> "", O8, "") =IF(P8 <> "", P8, "") =IF(Q8 <> "", Q8, "") =IF(R8 <> "", R8, "")
21 =IF(O9 <> "", O9, "") =IF(P9 <> "", P9, "") =IF(Q9 <> "", Q9, "") =IF(R9 <> "", R9, "")
22 =IF(O10 <> "", O10, "") =IF(P10 <> "", P10, "") =IF(Q10 <> "", Q10, "") =IF(R10 <> "", R10, "")
23 =IF(O11 <> "", O11, "") =IF(P11 <> "", P11, "") =IF(Q11 <> "", Q11, "") =IF(R11 <> "", R11, "")

Copy for Excel Copy for Sheets

So, we have set up the table to display data, but we need to supply the range Recent_logs with information from the database.

To do so, we must write a query. The syntax for this is the MySQL database query language. In other words, queries are generally structured to begin with a SELECT which specifies which columns to fetch, and a FROM which determines the database table to fetch from. The FROM can be followed by additional clauses, such as WHERE which filters the output, ORDER BY which determines how the output is sorted, and LIMIT N which only returns the first N rows. In this query, we will use the ORDER BY clause. Note that we do not need to use LIMIT N, since the named range is already limited in the number of rows.


SELECT
	`_molnify_timestamp`,
	name,
	warehouse,
	status
FROM
	`data_database-example-logging_0`
ORDER BY `_molnify_timestamp` DESC
                    

Now, the only thing that is left to do is to add the query as an autofill metadata.

A B
1
2
3
4
5
6
7

Copy for Excel Copy for Sheets

Remember that the named range is called Recent_logs, so we set autofill.Recent_logs to our query.

Table 2: warehouse statuses

With a view of the most recent logs in place, it is time to show the results of the inspections.

Similarly to when we created the first table, we start by preparing the Excel file to receive the data for the second table. Since we have 10 warehouses, we create a named range that is 10 rows and 4 columns. We let this named range be called Warehouse_data, and define it to be the cells V2:Y11. We write the column headers "warehouse", "status", "name", "date" in V1:Y1 above the range. We also colour the range grey, and colour them grey, so we remember that the cells will be populated with data by Molnify.

Next, we create the Molnify table in the cells below the named range, U13:Z23. We refer each cell in the table to the corresponding cell in the named range above it.

U V W X Y Z
13 Warehouses <b>Location</b> <b>Status</b> <b>Inspector</b> <b>Date of report</b> table
14 =IF(V2 <> "", V2, "") =IF(W2 <> "", W2, "") =IF(X2 <> "", X2, "") =IF(Y2 <> "", Y2, "")
15 =IF(V3 <> "", V3, "") =IF(W3 <> "", W3, "") =IF(X3 <> "", X3, "") =IF(Y3 <> "", Y3, "")
16 =IF(V4 <> "", V4, "") =IF(W4 <> "", W4, "") =IF(X4 <> "", X4, "") =IF(Y4 <> "", Y4, "")
17 =IF(V5 <> "", V5, "") =IF(W5 <> "", W5, "") =IF(X5 <> "", X5, "") =IF(Y5 <> "", Y5, "")
18 =IF(V6 <> "", V6, "") =IF(W6 <> "", W6, "") =IF(X6 <> "", X6, "") =IF(Y6 <> "", Y6, "")
19 =IF(V7 <> "", V7, "") =IF(W7 <> "", W7, "") =IF(X7 <> "", X7, "") =IF(Y7 <> "", Y7, "")
20 =IF(V8 <> "", V8, "") =IF(W8 <> "", W8, "") =IF(X8 <> "", X8, "") =IF(Y8 <> "", Y8, "")
21 =IF(V9 <> "", V9, "") =IF(W9 <> "", W9, "") =IF(X9 <> "", X9, "") =IF(Y9 <> "", Y9, "")
22 =IF(V10 <> "", V10, "") =IF(W10 <> "", W10, "") =IF(X10 <> "", X10, "") =IF(Y10 <> "", Y10, "")
23 =IF(V11 <> "", V11, "") =IF(W11 <> "", W11, "") =IF(X11 <> "", X11, "") =IF(Y11 <> "", Y11, "")

Copy for Excel Copy for Sheets

Now that we have set up the table, and range to receive the data, it is time to construct the query which tells the database which data to find.

We will need to select the warehouse, status, name, and date of the single most recent report for each warehouse. We need to break down this query.

To select the most recent report for each warehouse, we use the function MAX(_molnify_timestamp), and GROUP BY warehouse to aggregate by warehouse. This sub-query becomes:


                SELECT
			warehouse,
			MAX(_molnify_timestamp) AS max_timestamp
		FROM
			`data_database-example-logging_0`
		GROUP BY
			warehouse
                    

The GROUP BY instructs the query to not find the MAX of all columns, but instead find the MAX for each warehouse.

It is unlikely that two reports are logged at the exact same second, but we should not ignore the possibility. Since the recordId is the primary key, we are guaranteed that it will be unique, so it is a perfect tie-breaker. So, for each of the warehouses present in the result of the earlier query, we want to determine the biggest recordId. We perform this operation with a JOIN and a GROUP BY.


        SELECT
		MAX(a.recordId) AS recordId,
		a.warehouse
	FROM
		`data_database-example-logging_0` AS a
	JOIN (
		SELECT
			warehouse,
			MAX(_molnify_timestamp) AS max_timestamp
		FROM
			`data_database-example-logging_0`
		GROUP BY
			warehouse
   	) AS b
   	ON
		a.warehouse = b.warehouse
		AND
                a.`_molnify_timestamp` = b.max_timestamp
	GROUP BY
		a.warehouse
                    

The JOIN combines the table with the result of the inner query. It returns only rows where the ON criteria match.

Now that we have the recordId of the most recent log for each warehouse location, we can use a second JOIN to find the status, name, and log date for the rows that match the recordIds. The final query becomes:


SELECT
	t.warehouse,
	t.status,
	t.name,
	DATE(t._molnify_timestamp)
FROM
	`data_database-example-logging_0` AS t
JOIN (
	SELECT
		MAX(a.recordId) AS recordId,
		a.warehouse
	FROM
		`data_database-example-logging_0` AS a
	JOIN (
		SELECT
			warehouse,
			MAX(_molnify_timestamp) AS max_timestamp
		FROM
			`data_database-example-logging_0`
		GROUP BY
			warehouse
   	) AS b
   	ON
		a.warehouse = b.warehouse
		AND
	        a.`_molnify_timestamp` = b.max_timestamp
	GROUP BY
		a.warehouse
) AS s
ON
	t.recordId = s.recordId
                    

Now, the only thing that is left to do is to add the query as an autofill metadata.

A B
1
2
3
4
5
6
7
8

Copy for Excel Copy for Sheets

Remember that the named range is called Warehouse_data, so we set autofill.Warehouse_data to our query.

You can download the final Excel file for the logging tool, but remember that the application will not work unless you change the ID and set up the database tables for the new ID.

The demo app can be tried out below, or at https://app.molnify.com/app/database-example-logging.