Custom function output in rows instead of columns - google-apps-script

The following script returns the values from the for loop in rows. Is there anyway to return the results into the adjacent columns instead?
function readMessage(msgId){
var url = "https://api.pipedrive.com/v1/mailbox/mailThreads/";
var token = "/mailMessages?api_token=token";
Logger.log("readMessage called with msgId: " + msgId);
if (msgId){ // make sure there is a msgId in the cell
var rows = []
var response = UrlFetchApp.fetch(url+msgId+token);
var dataAll = JSON.parse(response.getContentText());
var dataSet = dataAll;
var rows = [], data;
for (var i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
rows.push([data.body_url]);
}
Logger.log( JSON.stringify(rows,null,2) );
return rows;
}
Current output is-
A B C D
1 =readMessage(msgId)
2 "blah blah"
3 "blah blah"
What I want is-
A B C D
1 =readMessage(msgId) "blah blah" "blah blah"
2
3

Google Apps Script data is always in the form of a 2D array. It's helpful to have visualization.
A B C
1 [[A1, B1, C1],
2 [A2, B2, C2]]
in order to return a row of data the function should return
[[A,B,C]] // 3 columns of data in one row
in order to return a column of data you can use either the full 2D array or a single array
[[1],[2],[3]]
OR
[1,2,3]

The issue is the formatting of the data you are returning. Try this rows.push(data.body_url); instead:
for (var i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
rows.push(data.body_url); //Brackets [] have been removed to insert data as a row.
}
If you return data like this: [[1],[3],[6]] the result will be a column of values:
1
3
6
If you data has this format: [2,4,8] you get a row of values:
2 | 4 | 6
Source: Custom Function

Related

Get row values by column name match

I want to get the value of all the cells of rows. But not from all the rows, only rows having specific name in a specific column.
I attached a screenshot. I want to get all row values which "userID" is "user3". It means row 4 row 6 data.
I used following code.
function getByName(colName, row) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var col = data[0].indexOf(colName);
if (col != -1) {
return data[row-1][col];
}
}
Above I getting only specific cell value in the user ID column. Not all data in the ROW.
function getAllrowData() {
var user3 = getByName("userID",2);
var values = user3.getDisplayValues()
Logger.log(values)
return values
}
getByname giving only cell value.
I want following result so I can display data in html, belongs to only "user 3" in the image. Help me to correct my code.
C 25 30 0 16 user3
E 28 36 6 19 user3
Get Data by Row Number and Column Name:
function getByName(user, colName = 'userID') {
const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getDataRange().getValues();
const hA = data.shift();
const idx = hA.reduce((a, h, i) => (a[h] = i, a), {});
let o = data.map(r => {
if (r[idx[colName]] == user) {
return r;
}
}).filter(r => r );
if (o && o.length > 0) {
return o;
}
}
Remove [col] from return data[row-1][col];
The above because sheet.getDataRange().getValues(); return an Array of Arrays of values. Using two indexes returns a cell value, using only the first index will return an Array having all the row values.

How to Find Empty Rows Using Map() in Google Apps Script

I just renew the example and also add the How I want the code to work
I'm quite new with map() function. I want to change the for and if condition to map() because it takes to long for processing a lot of data. Or you guys have any idea to make my code more faster and efficient to work, it can be really helpful.
How I want my code's work:
1. Find the row that have empty value on Column 3 from Table of Data
2. Concate or merge the value of Column 1 and Column 2 from Table of Data
3. Find the same value with the merged value in Table of Source_data
4. If the merged value is same with the value of Column 1 on Table of Source_data, then Get the data of column 2, Column 3, and Column 4
5. Write the data from Table of Source_data (Column 2, Column 3, Column 4) on the Column 3, Column 4, and Column 5 of Table of Data (Result like The Expected Output)
Thank you!
Table of Data:
Column 1
Column 2
Column 3
Column 4
Column 5
lose
data
data1
data2
data3
Second
row
Second
row
Second
row
data4
data5
data6
Table of Source_Data:
Column 1
Column 2
Column 3
Column 4
losedata
data1
data2
data3
Secondrow
data4
data5
data6
Table of Data: (Expected Output)
Column 1
Column 2
Column 3
Column 4
Column 5
lose
data
data1
data2
data3
Second
row
data4
data5
data6
Second
row
data4
data5
data6
Second
row
data4
data5
data6
function main3() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var data = sheet.getDataRange().getValues();
var source_file = SpreadsheetApp.openById("").getSheetByName("");
var source_data = source_file.getRange(2, 1, source_file.getLastRow() - 1, source_file.getLastColumn()).getValues();
var headerName1 = "Column 1";
var headerName2 = "Column 2";
var header = data.shift();
var header_index1 = header.indexOf(headerName1);
var header_index2 = header.indexOf(headerName2);
// To find empty row with specific number of column
for (var i = 1; i < data.length; i++) {
if (data[i][2] == "") {
// merge 2 column
// column 1: lose and column 2: data
// var concat will generate the merge of those two column -> losedata
var concat = data.map((row, i) => ([data[i][header_index1] + data[i][header_index2]]));
// find the same value with the value of merged columns
// this will find the same data on source_data (Source Spreadsheet) like "losedata"
var matching = concat.map(row => {
var row_match = source_data.find(r => r[0] == row[0])
return row_match ? [row_match[3], row_match[4], row_match[5]] : [null, null, null]
});
// write the value to the table of Data
sheet.getRange(2, 3, matching.length, matching[0].length).setValues(matching);
}
}
}
As far as I can tell the problem is in setValues() inside the loop. It doesn't matter if the loop is for() or map(). If you want to speed up the script you have to reduce calls to the server. I believe in this case you can process all data on client side as a 2d array and set it on the sheet all at once with just one setValues():
function myFunction() {
// get the destination sheet and data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var [header, ...data] = sheet.getDataRange().getValues();
// get the source data
var source_file = SpreadsheetApp.openById('').getSheetByName('');
var [_, ...src_data] = source_file.getDataRange().getValues();
// make source object from the source data {col1:[col2, col3, col4], ...}
var obj = {};
src_data.forEach(x => obj[x[0]] = x.slice(1));
// loop through all the data and add cells from the object
// whenever the object has the key (key is col1 + col2 of current row)
for (let row in data) {
var key = data[row][0] + data[row][1];
if (key in obj) data[row] = [data[row][0], data[row][1], ...obj[key]];
}
// make the table from the updated data and set the table on the sheet
var table = [header, ...data];
sheet.getDataRange().setValues(table);
}
Data (original):
Source:
Data (results):
Update
It turned out the actual data has another columns.
Data (original):
Source:
Data (results):
The updated code to copy the blue columns from source to the data sheet (based on 'keys' in yellow columns) is here:
function myFunction() {
// get the destination sheet and data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var [header, ...data] = sheet.getDataRange().getValues();
// get the source data
var source_file = ss.getSheetByName('Source');
// var source_file = SpreadsheetApp.openById('').getSheetByName('');
var [_, ...src_data] = source_file.getDataRange().getValues();
// make source object from the source data {col1:[col2, col3, col4], ...}
var obj = {};
src_data.forEach(x => obj[x[0]] = x.slice(3));
var headerName1 = "Column 1";
var headerName2 = "Column 5";
var header_index1 = header.indexOf(headerName1);
var header_index2 = header.indexOf(headerName2);
// loop through all the data and add cells from the object
// whenever the object has the key (key is col1 + col2 of current row)
for (let row in data) {
var key = data[row][header_index1] + data[row][header_index2];
if (key in obj) data[row] = [...data[row].slice(0,5), ...obj[key]];
}
// make the table from the updated data and set the table on the sheet
var table = [header, ...data];
sheet.getDataRange().setValues(table);
}
A better way to look for empty cells is using the Range.getNextDataCell(). This will look for the first empty cell. Assuming there are no empty cells in the column it will find the last empty cell. This is the same as Ctrl+[arrow key]. You can also use it to find empty columns by using Direction.NEXT or PREVIOUS.
Actually it's not the empty cell it returns but the boundary cell containing data of the direction you are looking. So be sure and add 1 or subtract 1 if looking UP.
function getEmptyCell() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet3");
let column = 1;
// In these 2 examples cell A6 is empty then A16. Column B has 20 values, more than A
// This first example finds the first empty cell starting from the 1st row down
let row = sheet.getRange(1,column).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
console.log(row);
// In this example the first empty cell starting from the last row up.
row = sheet.getRange(sheet.getLastRow()+1,column).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
console.log(row);
}
catch(err) {
Logger.log("Error in getEmptyCell: "+err)
}
}
6:51:01 AM Notice Execution started
6:51:03 AM Info 5
6:51:03 AM Info 15
6:51:03 AM Notice Execution completed

Google Sheets (app-script): Copying data to another tab without duplicate based on criteria [duplicate]

I have this form of a spreadsheet:
A B C D
abc abc abc 1
def ghi jkl 1
mno pqr stu 3
vwx yza bcd 4
mno pqr stu 5
mno pqr stu 5
vwx yza bcd 5
mno pqr stu 1
Where the first 3 columns are just data of type string. The column D has integers which has numbers repeating. My question is how to output a fifth column like so:
A B C D E
abc abc abc 1 1
def ghi jkl 1 3
mno pqr stu 3 4
vwx yza bcd 4 5
mno pqr stu 5
mno pqr stu 5
vwx yza bcd 5
mno pqr stu 1
It only outputs the unique numbers from column D.
I imagined running an if/else statement in a for or while loop that checks each cell in "D" and stores any value not previously "seen" in an array. Then outputting the array in column E.
I was wondering if there is a more efficient way to do this. Also the above is just a small example. Most likely the data range is in the 400 range. (Row wise. Columns are only 4 or 5 including the new output column.)
Thanks in advance.
P.S.
I searched for this here but I'm only getting questions that relate to deleting duplicate rows. If there is a question that asks this already, please link me to it.
You can do that inside google-spreadsheets with the UNIQUE function.
Here is the doc to all available functions.
(You find UNIQUE in the Filter category)
Most likely you want to insert into cell E1:
=UNIQUE(D1:D)
This will populate column E with the unique values from all of column D while preserving the order. Furthermore this will dynamically update and reflect all changes made to column D.
To do that from within google-apps-script:
SpreadsheetApp.getActiveSheet()
.getRange("E1").setFormula("=UNIQUE(D1:D)");
here is a way to do that... probably not the only one but probably not bad...
I added a few logs to see intermediate results in the logger.
function keepUnique(){
var col = 3 ; // choose the column you want to use as data source (0 indexed, it works at array level)
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
Logger.log(data);
var newdata = new Array();
for(nn in data){
var duplicate = false;
for(j in newdata){
if(data[nn][col] == newdata[j][0]){
duplicate = true;
}
}
if(!duplicate){
newdata.push([data[nn][col]]);
}
}
Logger.log(newdata);
newdata.sort(function(x,y){
var xp = Number(x[0]);// ensure you get numbers
var yp = Number(y[0]);
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on numeric ascending
});
Logger.log(newdata);
sh.getRange(1,5,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted in column of your choice (here column 5, indexed from 1, we are on a sheet))
}
EDIT :
Following Theodros answer, the spreadsheet formula is indeed an elegant solution, I never think about it but I should !!! ;-)
=SORT(UNIQUE(D1:D))
gives exactly the same result...
Currently, in V8 engine, the easiest way to do this is to use Set:
/**
* #returns {Object[]} Gets unique values in a 2D array
* #param {Object[][]} array2d
* #private
*/
const getUnique_ = array2d => [...new Set(array2d.flat())];
/**
* Gets Values from a column, makes it unique and sets the modified values
* to the next column
* #param {string} sheetName
* #param {number} column Number of the column to uniquify
* #param {number} headers Number of headers
* #returns void
*/
const uniquifyAColumn = (sheetName = 'Sheet1', column = 3, headers = 1) => {
const sh = SpreadsheetApp.getActive().getSheetByName(sheetName),
rg = sh.getRange(1 + headers, column, sh.getLastRow() - headers, 1),
values = rg.getValues(),
uniqueValues = getUnique_(values).map(e => [e]);
rg.offset(0, 1, uniqueValues.length).setValues(uniqueValues);
};
Here's a script for getting a unique list of nonempty values in a column given the Sheet it's on and the header location of the column using an array to store the values and indexOf to find duplicates. You can then write the array wherever you'd like.
headerLocation is a Location object:
var Location = function(sheet, row, col) {
this.sheet = sheet;
this.row = row;
this.col = col;
this.addRow = function() { this.row = this.row + 1; }
this.addCol = function() { this.col = this.col + 1; }
this.getValue = function() { return sheet.getRange(this.row, this.col).getValue(); }
this.toString = function() { return "(" + this.row + "," + this.col + ")"; }
}
This is the function to read the column and return unique values:
/**
* Get unique values in column, assuming data starts after header
* #param {Sheet} sheet - Sheet with column to search
* #param {object} headerLocation - row and column numbers of the column header cell
* #returns {array} list of unique values in column
*/
function getUniqueColumnValues(sheet, headerLocation) {
let startRow = headerLocation.row + 1;
let lastRow = sheet.getLastRow();
let values = [];
for (i = startRow ; i <= lastRow ; i++) {
let value = sheet.getRange(i, headerLocation.col).getValue();
if ((value != "") && (values.indexOf(value) == -1)) {
values.push(value);
}
}
return values;
}
Or, using a Location to find the values:
function getUniqueColumnValues(sheet, headerLocation) {
let values = [];
let searchLocation = new Location(sheet, headerLocation.row + 1, headerLocation.col);
let lastRow = sheet.getLastRow();
while (searchLocation.row <= lastRow) {
let value = searchLocation.getValue();
if (values.indexOf(value) == -1) {
values.push(value);
}
searchLocation.addRow();
}
return values;
}

Gathering all the unique values from one column and outputting them in another column..?

I have this form of a spreadsheet:
A B C D
abc abc abc 1
def ghi jkl 1
mno pqr stu 3
vwx yza bcd 4
mno pqr stu 5
mno pqr stu 5
vwx yza bcd 5
mno pqr stu 1
Where the first 3 columns are just data of type string. The column D has integers which has numbers repeating. My question is how to output a fifth column like so:
A B C D E
abc abc abc 1 1
def ghi jkl 1 3
mno pqr stu 3 4
vwx yza bcd 4 5
mno pqr stu 5
mno pqr stu 5
vwx yza bcd 5
mno pqr stu 1
It only outputs the unique numbers from column D.
I imagined running an if/else statement in a for or while loop that checks each cell in "D" and stores any value not previously "seen" in an array. Then outputting the array in column E.
I was wondering if there is a more efficient way to do this. Also the above is just a small example. Most likely the data range is in the 400 range. (Row wise. Columns are only 4 or 5 including the new output column.)
Thanks in advance.
P.S.
I searched for this here but I'm only getting questions that relate to deleting duplicate rows. If there is a question that asks this already, please link me to it.
You can do that inside google-spreadsheets with the UNIQUE function.
Here is the doc to all available functions.
(You find UNIQUE in the Filter category)
Most likely you want to insert into cell E1:
=UNIQUE(D1:D)
This will populate column E with the unique values from all of column D while preserving the order. Furthermore this will dynamically update and reflect all changes made to column D.
To do that from within google-apps-script:
SpreadsheetApp.getActiveSheet()
.getRange("E1").setFormula("=UNIQUE(D1:D)");
here is a way to do that... probably not the only one but probably not bad...
I added a few logs to see intermediate results in the logger.
function keepUnique(){
var col = 3 ; // choose the column you want to use as data source (0 indexed, it works at array level)
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
Logger.log(data);
var newdata = new Array();
for(nn in data){
var duplicate = false;
for(j in newdata){
if(data[nn][col] == newdata[j][0]){
duplicate = true;
}
}
if(!duplicate){
newdata.push([data[nn][col]]);
}
}
Logger.log(newdata);
newdata.sort(function(x,y){
var xp = Number(x[0]);// ensure you get numbers
var yp = Number(y[0]);
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on numeric ascending
});
Logger.log(newdata);
sh.getRange(1,5,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted in column of your choice (here column 5, indexed from 1, we are on a sheet))
}
EDIT :
Following Theodros answer, the spreadsheet formula is indeed an elegant solution, I never think about it but I should !!! ;-)
=SORT(UNIQUE(D1:D))
gives exactly the same result...
Currently, in V8 engine, the easiest way to do this is to use Set:
/**
* #returns {Object[]} Gets unique values in a 2D array
* #param {Object[][]} array2d
* #private
*/
const getUnique_ = array2d => [...new Set(array2d.flat())];
/**
* Gets Values from a column, makes it unique and sets the modified values
* to the next column
* #param {string} sheetName
* #param {number} column Number of the column to uniquify
* #param {number} headers Number of headers
* #returns void
*/
const uniquifyAColumn = (sheetName = 'Sheet1', column = 3, headers = 1) => {
const sh = SpreadsheetApp.getActive().getSheetByName(sheetName),
rg = sh.getRange(1 + headers, column, sh.getLastRow() - headers, 1),
values = rg.getValues(),
uniqueValues = getUnique_(values).map(e => [e]);
rg.offset(0, 1, uniqueValues.length).setValues(uniqueValues);
};
Here's a script for getting a unique list of nonempty values in a column given the Sheet it's on and the header location of the column using an array to store the values and indexOf to find duplicates. You can then write the array wherever you'd like.
headerLocation is a Location object:
var Location = function(sheet, row, col) {
this.sheet = sheet;
this.row = row;
this.col = col;
this.addRow = function() { this.row = this.row + 1; }
this.addCol = function() { this.col = this.col + 1; }
this.getValue = function() { return sheet.getRange(this.row, this.col).getValue(); }
this.toString = function() { return "(" + this.row + "," + this.col + ")"; }
}
This is the function to read the column and return unique values:
/**
* Get unique values in column, assuming data starts after header
* #param {Sheet} sheet - Sheet with column to search
* #param {object} headerLocation - row and column numbers of the column header cell
* #returns {array} list of unique values in column
*/
function getUniqueColumnValues(sheet, headerLocation) {
let startRow = headerLocation.row + 1;
let lastRow = sheet.getLastRow();
let values = [];
for (i = startRow ; i <= lastRow ; i++) {
let value = sheet.getRange(i, headerLocation.col).getValue();
if ((value != "") && (values.indexOf(value) == -1)) {
values.push(value);
}
}
return values;
}
Or, using a Location to find the values:
function getUniqueColumnValues(sheet, headerLocation) {
let values = [];
let searchLocation = new Location(sheet, headerLocation.row + 1, headerLocation.col);
let lastRow = sheet.getLastRow();
while (searchLocation.row <= lastRow) {
let value = searchLocation.getValue();
if (values.indexOf(value) == -1) {
values.push(value);
}
searchLocation.addRow();
}
return values;
}

Count Reoccuring Values in Column of Google Spreadsheet Using Script

I'd like to count the number of times a value reoccurs in a specific column of my spreadsheet using a script so that it populates a different column (same row) with the count when a form is submitted. These are the values in Column B that I'd like to count:
6 ACM
5 ACM
4 ACM
5 CGC
7 CGC
6 ACM
7 ACM
7 ACM
so that if the calculation were working correctly Column C would be populated with these numbers:
1
1
1
1
1
2
1
2
I know how to read the data and how to write to the spreadsheet, but I don't know how to actually count the values. Here is what I have so far:
function countif() {
var ss = null;
try {
ss = SpreadsheetApp.openById("0AliYViHYAwaNdHQyMXlKT2Q5UElQY184T3BWYTRiM2c");
} catch (ex) {
ss = SpreadsheetApp.getActiveSpreadsheet();
}
var sheet = ss.getSheetByName("Sheet1");
var lastLine = sheet.getLastRow();
var data = sheet.getRange("B2:B").getValues();
var count = 0;
//need help here
sheet.getRange(lastLine,3).setValue(count);
}
Thank you!
Put this piece of code in your //need help section.
for (var i = 0 ; i < lastLine ; i++){
var count = 1;
for ( var j = 0 ; j < i ; i ++) {
if (data[j][0] == data[i][0] ){
count++;
}
}
sheet.getRange('C' + (i+1).toString()).setValue(count);
}
Just as an alternative, you can achieve this with a spreadsheet function, entered in C1:
=ArrayFormula(IF(ROW(B:B)=1;"Count";IF(LEN(B:B);MMULT((ROW(B:B)>=TRANSPOSE(ROW(B:B)))*(B:B=TRANSPOSE(B:B));SIGN(ROW(B:B)));IFERROR(1/0))))