1

I am using a Google Script bound to a Google Sheet to programatically generate the following query:

=query('16 Jul - 20 Jul Responses'!A1:I31, "SELECT C WHERE D = 'Available'", 0)

Is there any way in Google Scripts to parse an object representation of that query's results? I'd like to be able to code something like:

var queryString = '=query('16 Jul - 20 Jul Responses'!A1:I31, "SELECT C WHERE D = 'Available'", 0)'; var results = new Query(queryString); for(var i = 0; i < results.length; i++) { var result = results[i]; // do something }

As far as I can tell, the Query object doesn't exist unless you are working with a Google Web App. Is this true? Or is there a way to implement this idea?

TheMaster
  • 37,620
  • 6
  • 43
  • 68
RouteMapper
  • 2,197
  • 1
  • 22
  • 41

1 Answers1

12

=QUERY is a spreadsheet function. If you want that functionality in Apps Script you can leverage Google's Visualization API and Query Language. I wrote a custom module for it in GAS. Here's an extract:

(function(context) {

    const Utils = (context.Utils || (context.Utils = {}));


    /**
     * Queries a spreadsheet using Google Visualization API's Datasoure Url.
     *
     * @param        {String} ssId    Spreadsheet ID.
     * @param        {String} query   Query string.
     * @param {String|Number} sheetId Sheet Id (gid if number, name if string). [OPTIONAL]
     * @param        {String} range   Range                                     [OPTIONAL]
     * @param        {Number} headers Header rows.                              [OPTIONAL]
     */
    Utils.gvizQuery = function(ssId, query, sheetId, range, headers) {
        var response = JSON.parse( UrlFetchApp
                .fetch(
                    Utilities.formatString(
                        "https://docs.google.com/spreadsheets/d/%s/gviz/tq?tq=%s%s%s%s",
                        ssId,
                        encodeURIComponent(query),
                        (typeof sheetId === "number") ? "&gid=" + sheetId :
                        (typeof sheetId === "string") ? "&sheet=" + sheetId :
                        "",
                        (typeof range === "string") ? "&range=" + range :
                        "",
                        "&headers=" + ((typeof headers === "number" && headers > 0) ? headers : "0")
                    ), 
                    {
                        "headers":{
                            "Authorization":"Bearer " + ScriptApp.getOAuthToken()
                        }
                    }
                )
                .getContentText()
                .replace("/*O_o*/\n", "") // remove JSONP wrapper
                .replace(/(google\.visualization\.Query\.setResponse\()|(\);)/gm, "") // remove JSONP wrapper
            ),
            table = response.table,
            rows;

        if (typeof headers === "number") {

            rows = table.rows.map(function(row) {
                return table.cols.reduce(
                    function(acc, col, colIndex) {
                        acc[col.label] = row.c[colIndex] && row.c[colIndex].v;
                        return acc;
                    }, 
                    {}
                );
            });

        } else {

            rows = table.rows.map(function(row) {
                return row.c.reduce(
                    function(acc, col) {
                        acc.push(col && col.v);
                        return acc;
                    },
                    []
                );
            });
        }

        return rows;

    };

    Object.freeze(Utils);

})(this);

Just drop that module into its own file in your GAS editor then you can call it as follows:

// result is an array of objects if header row is specified, otherwise it is an array of arrays
var result = Utils.gvizQuery(
    "<YOUR_SPREADSHEET_ID>", 
    "<YOUR_QUERY_STRING>", 
    <SHEET_ID_IF_NEEDED>, // can be a number (the sheetId), or the name of the sheet; if not needed, but headers are, pass in undefined
    <RANGE>, // specify range, ex: `A2:O`
    <HEADER_ROW_INDEX_IF_NEEDED> // always a number
);
TheAddonDepot
  • 7,422
  • 2
  • 15
  • 24
  • I tried calling your module using the following call: `var result = Utils.gvizQuery( ss.getId(), '=query(\'16 Jul - 20 Jul Responses\'!A1:I31, "SELECT C WHERE D = \'Not Available\'", 0)' );` I got this error: `TypeError: Cannot read property "rows" from undefined. (line 53, file "GViz")` – RouteMapper Jul 13 '18 at 16:31
  • Try this instead: `var result = Utils.gvizQuery(ss.getId(), "SELECT C WHERE D ='Not Available'", "16 Jul - 20 Jul Responses", 0);` – TheAddonDepot Jul 13 '18 at 16:38
  • That gives the same error. The line `table = response.table` is returning `undefined`. Copying and pasting that same query into the Spreadsheet works fine. – RouteMapper Jul 13 '18 at 16:39
  • 1
    Updated the call to reference the sheet name. If that doesn't work go [here](https://plus.google.com/u/0/communities/102471985047225101769) and post a link to a copy of your sheet so I can troubleshoot. – TheAddonDepot Jul 13 '18 at 16:44
  • @RouteMapper Good to hear. – TheAddonDepot Jul 13 '18 at 18:15
  • I think `out:csv`(getting the output as csv instead of json) will be easier to parse and maybe even faster. – TheMaster Jul 10 '19 at 23:01
  • how do I use the headers, do I need quotes in the select? – tofutim Apr 29 '20 at 20:58