Pull data from one Google spreadsheet to another using Google script - javascript

I have 2 spreadsheet in my Drive. I want to pull data from a cell in 1 spreadsheet and copy it in another.
The spreadsheet "TestUsage" will sometimes have data in column A, but none is column B.
I would like so that when I open the spreadsheet, it would populate that empty cell in sheet "TestUsage" from sheet "TestParts".
Here is my code:
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 4);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 3);
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
Logger.log(rangeTestUsage[i][1]);
Logger.log(rangeTestParts[j][1]);
rangeTestUsage[i][1] = rangeTestParts[j][1];
break;
}
}
}
}
}
The problem with this is this doesn't do anything:
rangeTestUsage[i][1] = rangeTestParts[j][1];
I know there must be a method that can get data from one range to another.
Please let me know if I am totally incorrect or I am on the right path.

the statement
"this doesn't do anything:"
rangeTestUsage[i][1] = rangeTestParts[j][2];
is not really true... and not really false neither..., actually it does assign the value to rangeTestUsagei but you dont see it because it is not reflected in the spreadsheet.
Both values are taken from the Sheet using getValues so at that time they are both array elements.
What is missing is just writing back the array to the sheet using the symetrical statement setValues()
Give it a try and don't hesitate to come back if something goes wrong.
EDIT :
I didn't notice at first that you were using getSheetValues instead of getValues (simply because I never use this one)... the only difference is that getValues is a method of the range class while yours belongs to the sheet class; the syntax is similar in a way, just use
Sheet.getRange(row,col,width,height).getValues()
it takes one word more but has the advantage to have a direct equivalent to set values
Sheet.getRange(row,col,width,height).setValues()

Serge insas has a good explanation of why your code doesn't work and hints at the solution below.
I recommend you use an array to store the updated values of column B that you want then set the entire column at the end.
Modifying your code...
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 2);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 2);
var colB = [];
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
var matched = false;
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
//Logger.log(rangeTestUsage[i][1]);
//Logger.log(rangeTestParts[j][1]);
colB.push([rangeTestParts[j][1]]); // push the value we want into colB array
matched = true;
break;
}
}
if(!matched) // this is in case you don't have a match
colB.push([""]); // incase we don't have a matching part
} else {
colB.push([rangeTestUsage[i][0]]); // we already have a value we want so just add that to colB array
}
}
sTestUsage.getRange(2,2,lastRowTestUsage).setValues(colB); // update entire column b with values in colB array
}

Related

How do i shorten time this GAS code(search correct column and data paste)

https://i.stack.imgur.com/7VAJk.png
i want to copy data from "dB" sheet A5:A29 and paste to correct column.
so i use the script to find the correct column.
there range B2:CX2 have 0(not-correct) or 1(correct) value, so i use 'for' & 'if'
BUT!! It's too delay!!
i use console.time() and i get 25909ms(timecheck2 value) !!!
please help me.....
here is my code
function save(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('dB');
console.time("timecheck1");
//find last row
var copyrangeO = sheet.getRange(5,1,25,1).getValues();
var lastrowO = copyrangeO.filter(String).length;
var copyrange = sheet.getRange(5,1,lastrowO,1);
console.timeEnd("timecheck1");
//my dB data start "B2".
var cv = 1;
//find correct value(1). B2 ~ CX2 (#100)
console.time("timecheck2");
for (var i=2; i<101;i++){
if(sheet.getRange(2,i).getValue()===1){
cv = i;
}
}
console.timeEnd("timecheck2");
//if data isn't correct, cv===1. so error msg print.
console.time("timecheck3");
if(cv ===1){
Browser.msgBox("ERROR")
}else {
//data copy and paste.
var columnToCheck = sheet.getRange(4,cv,1000).getValues();
var lastrow = getLastRowSpecial(columnToCheck);
var pasterange = sheet.getRange(lastrow+4,cv);
copyrange.copyTo(pasterange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
Browser.msgBox(lastrowO + " saved!");
}
console.timeEnd("timecheck3");
}
Issue:
If I understand your situation correctly, you want to find the cell in B2:CX2 in which the value is 1, but the script is taking too much time for this.
The problem here is that you are using getRange and getValue in a loop (sheet.getRange(2,i).getValue()===1). This greatly increases the amount of calls to the Sheets service, which slows down your script, as you can see at Minimize calls to other services.
Solution:
In that case, I'd suggest doing the following:
Get the values from all columns at once using getValues().
Use findIndex to get the column index for which value is 1.
In order to do that, replace this:
var cv = 1;
//find correct value(1). B2 ~ CX2 (#100)
console.time("timecheck2");
for (var i=2; i<101;i++){
if(sheet.getRange(2,i).getValue()===1){
cv = i;
}
}
With this:
var ROW_INDEX = 2;
var FIRST_COLUMN = 2; // Column B
var LAST_COLUMN = 102; // Column CX
var columnValues = sheet.getRange(ROW_INDEX, FIRST_COLUMN, 1, LAST_COLUMN-FIRST_COLUMN+1).getValues()[0];
var cv = columnValues.findIndex(columnValue => columnValue === 1) + FIRST_COLUMN;
Note:
If there's no cell in the range with value 1, findIndex returns -1 which, added to FIRST_COLUMN, results in 1. That's appropriate for your current script, but won't work if the FIRST_COLUMN stops being 2, so be careful with this (either change the condition if(cv ===1){ to something less strict, or don't assign the resulting value to cv if findIndex returns -1).
The function will spend most of its time in the for loop because it repeats the Range.getValue() call many times. You can speed things up quite a bit by getting all values with one Range.getValues() call, like this:
let cv = 1;
console.time("timecheck2");
sheet.getRange('B2:B100').getValues().flat()
.some((value, index) => (cv = 2 + index) && value === 1);
console.timeEnd("timecheck2");
Note that this is not a cleanest way of finding cv, but it should help illustrate why you have a performance issue. You may want to do a complete rewrite of the code, using declarative instead of imperative style.
Try this:
I don't know what you're doing in the save because to did not supply the helper function code.
function save(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('dB');
var vs0 = sh.getRange(5,1,25,1).getValues();
var lr0 = vs0.filter(String).length;
var crg = sh.getRange(5,1,lr0,1);
var cv = 1;
const vs1 = sh.getRange(2,2,1,99).getValues().forEach((c,i) => {
if(c == 1)cv = i + 2
})
if(cv == 1){
Browser.msgBox("ERROR")
}else {
var vs2 = sh.getRange(4,cv,1000).getValues();
var lastrow = getLastRowSpecial(vs2);
var drg = sh.getRange(lastrow+4,cv);
crg.copyTo(drg, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
Browser.msgBox(lr0 + " saved!");
}
}

Google Appscript transpose dynamic data group from one column

I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))

How to create priorities list and change them without leaving any gaps

I'm trying to write a simple script for google spreadsheet.
The code should work as follows: it should be a priority list, which can be simply edited. For example I have a list of 7 tasks and want to change the priority of one task. My code simply looks for values greater or eqauls then the value I put in a cell and increments them by 1. So at the end the last task has priority 8 and I have only 7 tasks, so there is one gap somewhere.
Here is what I've already done:
function onEdit(e) {
var range = e.range;
var numRowsWithData = SpreadsheetApp.getActiveSheet().getDataRange().getNumRows();
var columnOfCellEdited = range.getColumn();
var cellEdited = range.getA1Notation();
if (columnOfCellEdited === 2) {
var valueOfEditedCell = range.getValue();
for (var i = 2; i <=numRowsWithData; i++) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(i, 2);
var currentValue = currentCell.getValue();
if(currentValue >= valueOfEditedCell) {
if(cellEdited == currentCell.getA1Notation()){
continue;
}
currentCell.setValue(currentValue+1);
};
}
};
};
This script works, but I've got some missing numbers, because every time I add 1 to the current value.
How to edit it to have all the numbers in proper order, without any missing ones?
I know it's not very complicated, but I don't have any idea for now:/
Thanks in advance!
Completely rewritten answer, following clarification of the question.
This function reads all of the existing priorities, including the new entry. Any existing value greater than the new one is incremented by 1. An array is used to store the row on which each value occurs. The array is then looped over, skipping any undefined entries (missing numbers), and renumbering those that do exist starting from 1.
This doesn't change the order of the rows. It simply renumbers the second column.
Where possible I've left your original code intact, and re-used your approach in the new bit.
function onEdit(e) {
var range = e.range;
var numRowsWithData = SpreadsheetApp.getActiveSheet().getDataRange().getNumRows();
var columnOfCellEdited = range.getColumn();
var cellEdited = range.getA1Notation();
if (columnOfCellEdited === 2) {
var valueOfEditedCell = range.getValue();
// get snapshot of current priorities
var priorityRows = [];
for (var row = 2; row <=numRowsWithData; row++) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(row, 2);
var currentValue = currentCell.getValue();
if(currentValue >= valueOfEditedCell && cellEdited !== currentCell.getA1Notation()) {
currentValue++;
};
priorityRows[currentValue] = row;
}
// renumber priorities
var newPriority = 1;
for (var i = 0; i < priorityRows.length; i++) {
var row = priorityRows[i];
if (row) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(row, 2);
currentCell.setValue(newPriority++);
}
}
}
}

Pop up dialog box is value is duplicate

I am trying to create a google script that will scan through a specific column of one google sheet and check if any new additions are a duplicate. I have come up with the following, but it isn't working.
function hasDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Customer and form Details");
var array = sheet.getRange('B2:B' + lastRow).getValues();
var valuesSoFar = [];
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (value in valuesSoFar) {
SpreadsheetApp.getUi().alert('Hello, world!');
}
else{
valuesSoFar.push(value);
}
}
}
As far as Im aware the above code creates an array called array and populates it with the specific range I am interested in. I then create a new, empty, array called valuesSoFar. The code then loops through the column and sequentially checks if that item has already been seen before, if so it gives you an alert. If not it adds it to the list of new items and keeps going.
It looks a bit like you are using the online documentation code.
But the following assignment is missing:
var lastRow = sheet.getLastRow();
or just replace lastRow with sheet.getLastRow()
Update:
I assume that the following solution should work if you actually have any duplicates in your column B:
function hasDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Customer and form Details");
var array = sheet.getRange('B2:B' + sheet.getLastRow()).getValues();
var valuesSoFar = [];
for (var i = 0; i < array.length; ++i) {
var value = array[i][0];
if (value in valuesSoFar) {
SpreadsheetApp.getUi().alert('Hello, world!');
} else {
valuesSoFar.push(value);
}
}
}
If it isn't working yet, I am sorry.
As long as you can not provide any information on what kind of error you are running into, I will not look into this anymore.

Ignore empty rows from a loop in Google Apps Script

I'm new to Google Apps Script and I'm trying to ignore the empty rows from a for loop, but I'm still getting the empty rows in my log. Here are my codes,
function getNonEmptyRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Bldng4");
var lr = sheet1.getLastRow() - 17;
for (var i = 1; i < lr; i++) {
var singleRow = sheet1.getRange(i, 1, 1, sheet1.getLastColumn()).getValues();
if (singleRow.length > 0) {
Logger.log(singleRow);
}
}
}
How can I get the only non empty rows from the loop? Need this help badly. Thanks.
var range_data =
sheet1.getRange("A2:A") //Column Range
.getValues() //Get array from range values
.filter(array=>array != '') //Filter non-empty values
I was looking for a solution to a similar problem and here is what I did:
first, I found this tutorial on how to create google apps script to eliminate duplicate rows.
next, i modified it like this to eliminate empty rows:
function removeEmptyRows() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("settings");
var data = sheet.getRange("A2:D").getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var empty = false;
for(i in data){
if(row.toString() == ",,,"){
empty = true;
}
}
if(!empty){
newData.push(row);
}
sheet.getRange(2, 6, newData.length, newData[0].length).setValues(newData);
};
As you can see, it takes A2:D range, removes empty rows and then pastes filtered data without empty rows into range F2:I.
You can try to use this script, but you may need to adjust it to "width" of your array. To do so change the number of commas in the following string:
if(row.toString() == ",,,"){
edit:
I modified script a bit to automatically adjust to width of your array:
function removeEmptyRows() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("settings");
var range = sheet.getRange("A2:C");
var data = range.getValues();
var dataWidth = range.getWidth();
var newData = new Array();
if (dataWidth<=1) {
var stringToCompare = '';
}else{
var stringToCompare = ',';
for (var i=0;i<dataWidth-2;i++) stringToCompare+=","
};
for(i in data){
var row = data[i];
var empty = false;
for(i in data){
if(row.toString() == stringToCompare){
empty = true;
}
}
if(!empty){
newData.push(row);
}
}
sheet.getRange(2, 6, newData.length, newData[0].length).setValues(newData);
};
The getValues() method you are using always return a 2 dimensions array, whatever the contents of the cells might be. There are several ways to get the cells content, one of them is to stringify the range content (convert matrix to single string) , remove the commas (and eventually "invisible" spaces) and check the length of the resulting string.
replacement code could go like this :
var singleRow = sheet1.getRange(i, 1, 1, sheet1.getLastColumn()).getValues().toString().replace(',','').replace(' ','');
That said, this code is very inefficient because it uses SpreadSheetApp service in each loop iteration which is particularly slow.
You'll find better approches in the documentation about best practices.

Categories