Package Exports
- @demmings/gssql
- @demmings/gssql/testGsSql.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@demmings/gssql) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
About
gsSQL is a high-quality custom function for Google Sheets that aims to provide standard SQL SELECT syntax to quickly filter and summarize data, using any sheet or range as an SQL table. Easy to learn and understand: the SQL query consists mainly of English statements, making it easy to write - rather than the cryptic syntax of the Google Sheet QUERY function.
|
gsSQL Demo of SELECT statement using JOIN and calculated fields.
Why use gsSQL
- It's easier and less work than using QUERY.
- It is less cryptic when attempting simple things like JOIN and SELECT IN.
Example Data
- See table data that was used below.
- Each table is in its own sheet, where the sheet name is the table name.
Inner Join
gsSQL | QUERY |
---|---|
=gsSQL("SELECT books.id, books.title, authors.first_name, authors.last_name FROM books INNER JOIN authors ON books.author_id = authors.id ORDER BY books.id") |
No easy solution just using QUERY. Not sure how to make it work 100%. Book '9' should not be included. =ArrayFormula({Books!A1:B10,vlookup(Books!D1:D10, {Authors!A1:A6, Authors!B1:C6}, {2,3}, false)}) |
gsSQL Results
books.id | books.title | authors.first_name | authors.last_name |
---|---|---|---|
1 | Time to Grow Up! | Ellen | Writer |
2 | Your Trip | Yao | Dou |
3 | Lovely Love | Donald | Brain |
4 | Dream Your Life | Ellen | Writer |
5 | Oranges | Olga | Savelieva |
6 | Your Happy Life | Yao | Dou |
7 | Applied AI | Jack | Smart |
8 | My Last Book | Ellen | Writer |
QUERY Results
id | title | #N/A | #N/A |
---|---|---|---|
1 | Time to Grow Up! | Ellen | Writer |
2 | Your Trip | Yao | Dou |
3 | Lovely Love | Donald | Brain |
4 | Dream Your Life | Ellen | Writer |
5 | Oranges | Olga | Savelieva |
6 | Your Happy Life | Yao | Dou |
7 | Applied AI | Jack | Smart |
9 | Book with Mysterious Author | #N/A | #N/A |
8 | My Last Book | Ellen | Writer |
Multiple Inner Joins
gsSQL | QUERY |
---|---|
=gsSQL("SELECT books.id, books.title, books.type, authors.last_name, translators.last_name FROM books INNER JOIN authors ON books.author_id = authors.id INNER JOIN translators ON books.translator_id = translators.id ORDER BY books.id") |
No easy solution just using QUERY |
Results
books.id | books.title | books.type | authors.last_name | translators.last_name |
---|---|---|---|---|
2 | Your Trip | translated | Dou | Weng |
5 | Oranges | translated | Savelieva | Davies |
6 | Your Happy Life | translated | Dou | Green |
7 | Applied AI | translated | Smart | Edwards |
Multiple Right Joins
gsSQL | QUERY |
---|---|
=gsSQL("SELECT books.id, books.title, books.translator_id, editors.last_name, editors.id, translators.last_name FROM books RIGHT JOIN editors ON books.editor_id = editors.id RIGHT JOIN translators ON books.translator_id = translators.id ORDER BY books.id") |
={query(Books!A1:F10,"Select A,B,F where E matches '"&TEXTJOIN("|",true,Editors!A1:A11)&"' AND F matches '"&TEXTJOIN("|",true,Translators!A1:A5)&"' order by A", 1), {"Editors.Last_Name"; ARRAYFORMULA( VLOOKUP(query(Books!A2:F10,"Select E where E matches '"&TEXTJOIN("|",true,Editors!A2:A11)&"' AND F matches '"&TEXTJOIN("|",true,Translators!A2:A5)&"' order by A", -1), Editors!A2:C11, 3))}, {"Editors.ID"; ARRAYFORMULA( VLOOKUP(query(Books!A2:F10,"Select E where E matches '"&TEXTJOIN("|",true,Editors!A2:A11)&"' AND F matches '"&TEXTJOIN("|",true,Translators!A2:A5)&"' order by A", -1), Editors!A2:C11, 1))}, {"Translators.Last_Name"; ARRAYFORMULA( VLOOKUP(query(Books!A2:F10,"Select F where E matches '"&TEXTJOIN("|",true,Editors!A2:A11)&"' AND F matches '"&TEXTJOIN("|",true,Translators!A2:A5)&"' order by A", -1), Translators!A2:C11, 3))} } |
gsSQL Results
books.id | books.title | books.translator_id | editors.last_name | editors.id | translators.last_name |
---|---|---|---|---|---|
2 | Your Trip | 32 | Johnson | 22 | Weng |
5 | Oranges | 31 | Wright | 25 | Davies |
6 | Your Happy Life | 33 | Johnson | 22 | Green |
7 | Applied AI | 34 | Evans | 23 | Edwards |
9 | Book with Mysterious Author | 34 | Evans | 23 | Edwards |
QUERY Results
id | title | translator id | Editors.Last_Name | Editors.ID | Translators.Last_Name |
---|---|---|---|---|---|
2 | Your Trip | 32 | Johnson | 22 | Weng |
5 | Oranges | 31 | Wright | 25 | Davies |
6 | Your Happy Life | 33 | Johnson | 22 | Green |
7 | Applied AI | 34 | Evans | 23 | Edwards |
9 | Book with Mysterious Author | 34 | Evans | 23 | Edwards |
Full Join
gsSQL | QUERY |
---|---|
=gsSQL("SELECT authors.id, authors.last_name, editors.id, editors.last_name FROM authors FULL JOIN editors ON authors.id = editors.id") |
Life is too short finding a replacement QUERY. |
Results
authors.id | authors.last_name | editors.id | editors.last_name |
---|---|---|---|
11 | Writer | ||
12 | Savelieva | ||
13 | Smart | 13 | Smart |
14 | Brain | ||
15 | Dou | ||
21 | Brown | ||
22 | Johnson | ||
23 | Evans | ||
24 | Roberts | ||
25 | Wright | ||
26 | Jones | ||
27 | Smith | ||
50 | Dumb | ||
51 | Smart |
Select Where IN Select
gsSQL | QUERY |
---|---|
=gsSQL("SELECT books.id, books.title, books.author_id FROM books WHERE books.author_id IN (SELECT id from authors)ORDER BY books.title") |
=QUERY(Books!A1:F10,"Select A,B,D where D matches '"&TEXTJOIN("|",true,Authors!A1:A6)&"' order by B", 1) |
books.id | books.title | books.author_id |
---|---|---|
7 | Applied AI | 13 |
4 | Dream Your Life | 11 |
3 | Lovely Love | 14 |
8 | My Last Book | 11 |
5 | Oranges | 12 |
1 | Time to Grow Up! | 11 |
6 | Your Happy Life | 15 |
2 | Your Trip | 15 |
Select Like
gsSQL | QUERY |
---|---|
=gsSQL("SELECT id, title, author_id FROM books WHERE author_id IN (select id from authors where first_name like '%ald') ORDER BY title") |
=QUERY(Books!A1:F10,"Select A,B,D where D matches '"&TEXTJOIN("|",true, query(Authors!A1:C5,"Select A where B contains 'ald'"))&"' order by B", 1)) |
id | title | author_id |
---|---|---|
3 | Lovely Love | 14 |
Example Summary
- We could go on with more examples, but in general, using gsSQL is easier.
- You can see that most standard SELECT syntax is supported.
Example Source Table Data
Authors
id | first_name | last_name |
---|---|---|
11 | Ellen | Writer |
12 | Olga | Savelieva |
13 | Jack | Smart |
14 | Donald | Brain |
15 | Yao | Dou |
BookReturns
RMA | Book Id | Customer ID | Quantity | Price | Date |
---|---|---|---|---|---|
Rma001 | 9 | c1 | 10 | 34.95 | 05/01/2022 |
rma020 | 8 | c2 | 3 | 29.95 | 05/01/2022 |
rmA030 | 7 | c2 | 5 | 18.99 | 05/01/2022 |
RMA040 | 9 | c3 | 1 | 59.99 | 05/02/2022 |
rma005 | 1 | c1 | 1 | 90 | 05/02/2022 |
RMA600 | 2 | c4 | 100 | 65.49 | 05/03/2022 |
Rma701 | 3 | c4 | 150 | 24.95 | 05/03/2022 |
RmA800 | 4 | c4 | 50 | 19.99 | 05/03/2022 |
RMA900 | 7 | c1 | 1 | 33.97 | 05/04/2022 |
rma1010 | 7 | c2 | 100 | 17.99 | 05/04/2022 |
BookSales
Invoice | Book Id | Customer ID | Quantity | Price | Date |
---|---|---|---|---|---|
I7200 | 9 | C1 | 10 | 34.95 | 05/01/2022 |
I7201 | 8 | C2 | 3 | 29.95 | 05/01/2022 |
I7201 | 7 | C2 | 5 | 18.99 | 05/01/2022 |
I7202 | 9 | C3 | 1 | 59.99 | 05/02/2022 |
I7203 | 1 | 1 | 90 | 05/02/2022 | |
I7204 | 2 | C4 | 100 | 65.49 | 05/03/2022 |
I7204 | 3 | C4 | 150 | 24.95 | 05/03/2022 |
I7204 | 4 | C4 | 50 | 19.99 | 05/03/2022 |
I7205 | 7 | C1 | 1 | 33.97 | 05/04/2022 |
I7206 | 7 | C2 | 100 | 17.99 | 05/04/2022 |
Books
id | title | type | author id | editor id | translator id |
---|---|---|---|---|---|
1 | Time to Grow Up! | original | 11 | 21 | |
2 | Your Trip | translated | 15 | 22 | 32 |
3 | Lovely Love | original | 14 | 24 | |
4 | Dream Your Life | original | 11 | 24 | |
5 | Oranges | translated | 12 | 25 | 31 |
6 | Your Happy Life | translated | 15 | 22 | 33 |
7 | Applied AI | translated | 13 | 23 | 34 |
9 | Book with Mysterious Author | translated | 1 | 23 | 34 |
8 | My Last Book | original | 11 | 28 |
Customer
ID | Name | Address | City | Phone | |
---|---|---|---|---|---|
C1 | Numereo Uno | 101 One Way | One Point City | 9051112111 | bigOne@gmail.com |
C2 | Dewy Tuesdays | 202 Second St. | Second City | 4162022222 | twoguys@gmail.com |
C3 | Tres Buon Goods | 3 Way St | Tres City | 5193133303 | thrice@hotmail.com |
C4 | ForMe Resellers | 40 Four St | FourtNight City | 2894441234 | fourtimes@hotmail.com |
C5 | Fe Fi Fo Giant Tiger | 5 ohFive St. | FifthDom | 4165551234 | fiver@gmail.com |
C6 | Sx in Cars | 6 Seventh St | Sx City | 6661116666 | gotyourSix@hotmail.com |
C7 | 7th Heaven | 7 Eight Crt. | Lucky City | 5551117777 | timesAcharm@gmail.com |
Editors
id | first name | last name |
---|---|---|
13 | Jack | Smart |
21 | Daniel | Brown |
22 | Mark | Johnson |
23 | Maria | Evans |
24 | Cathrine | Roberts |
25 | Sebastian | Wright |
26 | Barbara | Jones |
27 | Matthew | Smith |
50 | Jack | Dumb |
51 | Daniel | Smart |
Translators
id | first_name | last_name |
---|---|---|
31 | Ira | Davies |
32 | Ling | Weng |
33 | Kristian | Green |
34 | Roman | Edwards |
Usage
=gsSQL( SelectSqlStatement, [TableDefinitions], [ColumnOutputFlag], [BindVariableData])
SelectSqlStatement. (Required)
- Only the SELECT statement is supported.
- Most all common usage is supported (see below).
- The first row of the table MUST contain unique column titles (for field names).
- To reference a field where the title contains spaces, just use the underscore in place of the space.
- e.g. Title = "Transaction Date", SELECT=
"SELECT transaction_date from master_transactions"
- e.g. Title = "Transaction Date", SELECT=
- To reference a field where the title contains spaces, just use the underscore in place of the space.
- If parameter 2 is to be omitted, the table must be a sheet name. If the sheet name contains spaces, you must use single quotes around the table name within the select.
- e.g.
select * from 'master transactions' where account = 'bank'
- e.g.
- Bind variables use the question mark as a placeholder. There must be matching question marks to bind variable data - which is specified starting in parameter 4.
- e.g.
select * from transactions where transaction_date >= ? and transaction_date <= ?
- e.g.
- The PIVOT command is also supported. The 'PIVOT field' if used is the last part of the statement. It must be used in conjunction with 'group by'.
- e.g.
select transaction_date, sum(gross), sum(amount) from mastertransactions where transaction_date >= '01/01/2022' and transaction_date <= '05/19/2022' and expense_category in (select income from budgetCategories where income <> '') group by transaction_date pivot account
- e.g.
TableDefinitions (Optional)
- Defines each table referenced in SELECT statement.
- If a table does not encompass an entire sheet or you need to specify a range for the data, a table definition is required.
- The table definition is an Array of arrays. Each inner array defines ONE table.
- a) Table name - this is the table name referenced in the select. This is a logical table name which will associated with the data range. It does not have to be the sheet name (string).
- b) Range of data - the google range that contains the data with the first row containing titles (used as field names). This is any valid Google Sheet range name (i.e. Sheet Name, A1 notation or named range), but it must be passed in as a STRING (string)
- c) Cache seconds - (integer) number of seconds that data loaded from range is held in cache memory before another select of the same range would load again. (default=60)
- d) Has Column Title - (boolean) set to false if no column title in first row of data. Columns are then referenced as column letter. The first column of DATA is column A. (default=true)
- Use the CURLY bracket notations to create the double array of table definitions. If two separate tables are used within your SELECT, the table specifications would be entered as follows.
- {{a, b, c}; {a, b, c}}
- e.g.
gsSQL("select transaction_date, sum(gross), sum(amount) from mastertransactions where transaction_date >= '01/01/2022' and transaction_date <= '05/19/2022' and expense_category in (select income from budgetCategories where income <> '') group by transaction_date pivot account", {{"mastertransactions", "Master Transactions!$A$1:$I", 60};{"budgetCategories","budgetIncomeCategories", 3600}})
ColumnOutputFlag (Optional)
- Include column title in output or not. (true adds column titles, false omits the title row).
- This example will include the title row on output.
- Include column title in output or not. (true adds column titles, false omits the title row).
- BindVariableData. (Optional)
- There should be one data item listed PER question mark in the SELECT statement. Data for the variables can be literal data, cell references (A1 notation), and named fields.
- Using the data from the GIF above, here is an example a date input and appropriate data selected.
- The dates are stored in named ranges startDate and endDate.
Usage Bonus (for all you GAS lovers)
The Google QUERY is only available as a sheet function and it is not available for use for your javascript functions.
The Sql.gs (Sql.js) contains the Sql() class. This is what the gsSQL() custom function uses to implement the data selects.
Commands can be chained.
Sql() Methods
- addTableData(table, data, cacheSeconds, hasColumnTitle)
- table name referenced in SQL statement.
- data either a double array with column title in first row OR a string indicating a sheet range (named range or A1 notation).
- cacheSeconds number of seconds that loaded table data will be available from the cache after the initial loading. default=0.
- hasColumnTitle first row of data is a column title (true). If not available (false), columns are referenced by letter, where the first column is A
- enableColumnTitle(true)
- true or false. Output a column title (default is none or false)
- addBindParameter(value)
- For every question mark (no quotes) in your SELECT statement, there needs to be a matching bind variable data. Call this method as for as many question marks in the select are used - in the order that they are found.
- Do not use for named range data, in that case use the method addBindNamedRangeParameter
- addBindNamedRangeParameter(nameRange)
- For a bind variable that references a SINGLE cell named range. Input is a STRING.
- execute(stmt)
- stmt: SQL SELECT statement to run.
Returns a double array of data (first row is column title - if enabled).
- stmt: SQL SELECT statement to run.
- addTableData(table, data, cacheSeconds, hasColumnTitle)
Example usage:
let stmt = "select date, sum(quantity) from bookReturns where date >= ? and date <= ? group by date pivot customer_id";
let data = new Sql()
.addTableData("bookReturns", this.bookReturnsTable())
.enableColumnTitle(true)
.addBindParameter("05/01/2022")
.addBindParameter("05/04/2022")
.execute(stmt);
Installing
Copy files manually.
- In the ./dist folder there is ONE required file:
- gssql.js
- If you never plan to run the test suite, just use this ONE file in your app script.
- None of the files in ./src are required if you use gssql.js
- OR in the ./src folder there are SIX required files:
- SimpleParser.js
- Sql.js
- Table.js
- TableData.js
- Views.js
- ScriptSettings.js
- And the optional file
- SqlTest.js
- The simple approach is to copy and paste each file.
- From your sheets Select Extensions and then Apps Script
- Ensure that Editor is selected. It is the < >
- Click the PLUS sign beside File and then select Script
- Find each file in turn in the src OR dist folder in the Github repository.
- Click on a file, and then click on Copy Raw Contents which puts the file into your copy buffer.
- Back in your Google Project, rename Untitled to the file name you just selected in Github. It is not necessary to enter the .gs extension.
- Remove the default contents of the file myFunction() and paste in the new content you have copied from Github (Ctrl-v).
- Click the little diskette icon to save.
- Continue with all five files until done.
- Change to your spreadsheet screen and try typing in any cell
=gsSQL()
. The new function with online help should be available.
- In the ./dist folder there is ONE required file:
clasp push
- Install the gsSQL source files locally.
- Use
npm install @demmings/gssql
to install to node_modules folder.- I have included a sanity check after you have installed to your node_modules folder. Look for "@demmings/gssql" folder and run
npm test
- To find where your node_modules folder is just type
npm root
- Please note that gsSQL is not really a node package since Google Sheets does not recognize this. The use of npm to install is just a simple way to get the javascript to your local machine.
- I have included a sanity check after you have installed to your node_modules folder. Look for "@demmings/gssql" folder and run
- Clone the project from github repository to a local local.
- In your existing local Google Sheet project, create a folder called SQL below your existing javascript source folder.
- clasp push all your source files to Google.
- Use
- Install the gsSQL source files locally.
Supported SELECT syntax.
- All supported major keywords.
- 'SELECT',
- 'FROM',
- 'JOIN',
- 'LEFT JOIN',
- 'RIGHT JOIN',
- 'INNER JOIN',
- 'FULL JOIN',
- 'ORDER BY',
- 'GROUP BY',
- 'HAVING',
- 'WHERE',
- 'LIMIT',
- 'UNION ALL',
- 'UNION',
- 'INTERSECT',
- 'EXCEPT',
- 'PIVOT'
- Supported JOINS
- 'FULL JOIN'
- 'RIGHT JOIN'
- 'INNER JOIN'
- 'LEFT JOIN'
- Supported SET commands.
- 'UNION',
- 'UNION ALL',
- 'INTERSECT',
- 'EXCEPT'
- Aggregate Functions (group by)
- "SUM",
- "MIN",
- "MAX",
- "COUNT",
- "AVG",
- "DISTINCT"
- SQL Server Functions
- "ABS",
- "CASE",
- "CEILING",
- "CHARINDEX",
- "COALESCE",
- "CONCAT_WS",
- "DAY",
- "FLOOR",
- "IF",
- "LEFT",
- "LEN",
- "LENGTH",
- "LOG",
- "LOG10",
- "LOWER",
- "LTRIM",
- "MONTH",
- "NOW",
- "POWER",
- "RAND",
- "REPLICATE",
- "REVERSE",
- "RIGHT",
- "ROUND",
- "RTRIM",
- "SPACE",
- "STUFF",
- "SUBSTRING",
- "SQRT",
- "TRIM",
- "UPPER",
- "YEAR"
- Logical Operators.
- '='
- '>'
- '<'
- '>='
- '<>'
- '!='
- 'EXISTS'
- 'NOT EXISTS'
- 'LIKE'
- 'NOT LIKE'
- 'IN'
- 'NOT IN'
- 'IS NOT'
- 'IS'
NOTES
- First ROW of data MUST be the column name.
- If the column includes spaces, the SELECT statement must replace the spaces with an underscore. e.g.: "First Name" is the column and the select would be "select first_name from myTable"
- Column names do not support the period ".", so you must remove periods before trying the select.
- Column names must be unique (obviously).
- When specifying the input table definitions, you should only specify tables referenced in the SELECT as all data from every table is loaded into memory for processing.
- When gsSQL is used within your sheet multiple times and the same tables are also referenced multiple times, it makes sense to specify a cache seconds value. For tables that change often and up to date info is required, keep the cache either very low or zero. However, for tables that rarely change, it makes sense to cache for a longer period.
- The Google cache does have size and duration limits. If the table is huge, it is probably best to set the cache size to zero. Also note that the cache has a duration limit of 21600 seconds. Beyond that number of saeconds, the script properties are used to store the data - which may not be as quick as the cache and the long term cache has VERY limited capacity.
- Use BIND variables to simplify the SELECT statement. In the following statement, you must supply 3 bind variables e.g.
- BIND variables simplify the use of date comparisons. The QUERY statement requires that you format the date in your SELECT. Any DATE BIND variables are converted automatically. Just specify the named range or A1 range in your gsSQL statement (without quotes) for each parameter and in your SELECT, just substitute with a question mark.
Known Issues:
Most all SELECT functionality is implemented, however if you want to do anything extremely fancy, it may not to work. Check out the SqlTest.js to get an idea of the kind of commands that will work.
Field alias syntax is not fully supported. It is currently only used for column titles that can be returned with the select data.
Moderate amount of error checking. When developing your SQL SELECT statements and something is not correct or not supported, the application may just fail without giving any real indication of the problem. This needs improvements (although it is much improved since the first version).
Not really an issue, but the use of bind variables does not mean that the SELECT is compiled and reused. It is only to make your SELECT easier to read.
If no column titles are available and the column is referenced by letter, the first column in the data is column A. So if the table is defined by a range, and the range does not start in column A, gsSQL() still references the first column as A. This is different from QUERY(), as the absolute column letter is always used. Also QUERY() requires references to the column to be upper case, this is not required by gsSQL(). For example:
=gsSQL("select a, B, C, D, E, F from invoice where c = 'C4'", {{"invoice", "BookSales", 0, false}}, true )