Trying to Set Value in For Loop - apps script - google-apps-script

I am trying to set the value of a cell in a column when two other columns at an index match values. How can I set the value using an index? (<Edited)
for (let i = 0; i < assetId.length; i++) {
for (let p = 0; p < oldId.length; p++) {
if (assetId[i] !="" && oldId[p] !="") {
if (assetId[i] == oldId[p]) {
Logger.log('Old Match: ' + assetId[i])
//if match modify 4th column at row [i] to 'null'
d.getRange(i,3).setValue('null')
}
}
}
}
Based on if assetId[i] == oldId[p], I am trying to change column F of row [i] to 'null'
Edit (examples requested)
Column J is oldId and K is newId
EXPECTED OUTPUT: F4 should be null
Full code:
function replaceIds() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const r = ss.getSheetByName("Form Responses 1")
const d = ss.getSheetByName("Devices")
const oldId = r.getRange("J2:J").getValues().flat()
const newId = r.getRange("K2:K").getValues().flat()
const studentName = r.getRange("C2:C").getValues().flat()
const assetId = d.getRange("G3:G").getValues().flat()
const annotatedUser = d.getRange("E3:E").getValues().flat()
for (let i = 0; i < assetId.length; i++) {
for (let p = 0; p < oldId.length; p++) {
if (assetId[i] !="" && oldId[p] !="") {
if (assetId[i] == oldId[p]) {
Logger.log('Old Match: ' + assetId[i])
//if match modify 4th column at row [i] to 'null'
d.getRange(i,3).setValue('null')
}
}
}
//new asset ID loop
for (let r = 0; r < newId.length; r++) {
//Logger.log(oldId[p])
if (assetId[i] !="") {
if (newId[r] !="") {
//Logger.log('## not null ##')
if (assetId[i] == newId[r]) {
Logger.log('New Match: ' + assetId[i])
}
}
}
}
}
}

Issue:
Issue is that, using a nested for loop is not a good idea as you can't properly follow where the proper index is, and it will also needlessly reiterate on items that were already visited.
Solution:
Looping only on the assetId should suffice, then using indexOf as it will help you identify if a certain element (current assetId) belongs in an array (list of oldIds).
If assetId is found, indexOf will return a non-negative number (which is what index the element is found in the array).
Exclude empty assetIds due to how you get your data
Then you can remove the column of that same row, but since index starts at 0 and your data starts at 3rd row, we need to offset the getRange row so it would match the cell we want to delete properly.
Modifying your current solution, this is what the solution says above, and should work.
Script:
function replaceIds() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const r = ss.getSheetByName("Form Responses 1")
const d = ss.getSheetByName("Devices")
const oldId = r.getRange("J2:J").getValues().flat()
const newId = r.getRange("K2:K").getValues().flat()
const studentName = r.getRange("C2:C").getValues().flat()
const assetId = d.getRange("G3:G").getValues().flat()
const annotatedUser = d.getRange("E3:E").getValues().flat()
// loop your assetId
assetId.forEach(function(cell, index){
// if assetId is listed under oldId, remove annotated location of that row
// also, skip any rows where assetIds are blank
if(oldId.indexOf(cell) > -1 && cell != "")
// offset here is 3 since assetId starts at G3 and index starts at 0
// 3 - 0 = 3, which is the offset, and 6 is column F
d.getRange(index + 3, 6).setValue('');
});
}
Output:

This function will change the value in column1 if the value of col2 at that index is in column 10 on any line. you can change the indices as you desire.
function findDataBasedOnMatch() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const sr = 2;//data start row
const vs = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn()).getValues();
const col10 =vs.map(r => r[9]);//you pick the indices
vs.forEach((r,i) => {
if(~col10.indexOf(r[1])) {//you pick the indices
sh.getRange(i + sr, 1).setValue('');
}
});
}

Related

Restarting a COUNTIF function depending on multiple criteria

I have a sheet with 4 columns, as shown below:
1
Date
Item Name
Counter
Flag
3
Date 1
Item A
1
4
Date 1
Item B
1
5
Date 2
Item B
2
6
Date 3
Item A
2
1
7
Date 3
Item B
3
8
Date 4
Item A
1
9
Date 5
Item A
2
Currently, I'm using a countif function [=countif(B$2:B2,B2)] to count the number of times a specific item appears in the spreadsheet. However, I need to find a way to restart the counter if there is a 1 in column D. In this case, this would mean that the formula in row 8 column C would be [=COUNTIF(B$8:B8,B8)] and would continue counting until it finds another row with a 1 in column D (e.g., formula in column C row 9 would be =COUNTIF(B$8:B9,B9). It would also ideally check whether there is a prior row with a 1 in column D, not through the order of the sheet, but by checking that it's date is smaller (and yet the closest date with a 1 in column D).
I've written the following script, which sets the row with a 1 in column D to 0 and sets the countif for the starting rows correctly to [=countif(B$2:B2,B2)], but it sets any row after there is a row with a 1 in column D as the same formula, with the starting range at B$2.
function setCountifFormula() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Test");
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) { //iterate through each row
var colBValue = data[i][1]; //get columnB in i
var colAValue = data[i][0]; // get date in i
var colDValue = data[i][3]; // get flag in i
var closestRow = 1; // empty variable
if( colDValue == "1") { //if columnD = 1
sheet.getRange(i+1,3).setValue(0); // set columnC = 0
} else {
for (var j = 1; j < data.length; j++) { //iterate through other rows
if (data[j][1] === colBValue && data[j][3] === "1") { // if columnB in j = ColumnB in i, and flag in row j = 1
var dateToCompare = data[j][0]; //set datetoCompare = date in row j
closestRow = j;
if (dateToCompare < colAValue) {
var range = "B$" + (closestRow + 1) + ":B" + (i + 1);
var formula = "=COUNTIF(" + range + ",B" + (i + 1) + ")";
sheet.getRange(i + 1, 3).setFormula(formula);
} else {
var range = "B$2:B" + (i+1);
var formula = "=COUNTIF(" + range + ",B" + (i+1) + ")";
sheet.getRange(i+1, 3).setFormula(formula);
}
}
}
if (closestRow === 1) {
var range = "B$2:B" +(i+1);
var formula = "=COUNTIF("+range +",B"+(i+1)+")";
sheet.getRange(i+1,3).setFormula(formula);
}
}
}
}
I can post the spreadsheet if needs be. If there is a different way without using scripts or COUNTIF, it'd be appreciated. Thanks!
I'm much better at scripting than complex formulas so here is an example of how I would do it.
function myCountif() {
try {
let values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
values.shift(); // remove headers
let unique = values.map( row => row[1] );
unique = [...new Set(unique)];
let count = unique.map( row => 0 );
let counts = values.map( row => 0 );
values.forEach( (row,rIndex) => {
let cIndex = unique.findIndex( item => item === row[1] );
count[cIndex] = count[cIndex]+1;
counts[rIndex] = count[cIndex];
if( row[3] === 1 ) count[cIndex] = 0;
}
)
return counts;
}
catch(err) {
console.log(err);
}
}
Reference
Array.shift()
Array.map()
Set Object
Array.forEach()
Array.findIndex()
Arrow function =>

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.

Increment a value in range ( one column ) with if and multicriteria - Google Apps Script

I try to loop on each row and check for U & AA values and set value of the Y value of the same row based on the folowing criteria :
J value is 1
If AA value is : "TEST" then Y value is set ( j + 1 )
If U value is : "33" then Y value is set ( j + 1 ) then J = j + 1
If AA value isn't "TEST" & U value isn't "33" then Y value is set ( j )
Here my code
function RowNumbre() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('Test');
var range = sheet.getRange("AA:AA200").getValues();
for (var i=2; i < range.length; i++) {
var j = 1;
var value = sheet.getRange("Y"+i);
var value2 = sheet.getRange("U"+i).getValue();
var value3 = sheet.getRange("AA"+i).getValue();
if(value2 === "33"){
value.setValue(j+1);
j= j+1;
}
else if(value3 === "TEST"){
value.setValue(j+1);
}
else {
value.setValue(j);
}
}
}
The code don't work at all and it's look like there is a huge lag ( for the moment, a "1" is added to each cell on Y each 2-3 minutes and it wont stop.
Try this:
function RowNumbre() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Test');
const rg1=sh.getRange("Y1:Y200");
var vs1=rg1.getValues();
const rg2=sh.getRange("U1:U200");
var vs2=rg2.getValues();
const rg3=sh.getRange("AA1:AA200");
var vs3=rg3.getValues();
var sv=1;
vs1.forEach(function(v,i){if(vs2[i][0]==33){v[0]=sv++;}else if(vs3[i][0]=="TEST"){v[0]=sv+1;}else{v[0]=sv;}});
rg1.setValues(vs1);
}

How should I properly use the set range and get range functions in Google Apps Script?

I need to update the value of "product quantity" based on the value of "order quantity" but only when "order sku" is equal to "product sku".
function productLoop2() {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var t = 2;
var n = 2;
var s = 0;
for(var t = 2; t < 52; t++) {
var x = activeSheet.getRange(t, 1).getValue();
//x is the ORDER SKU
var r = activeSheet.getRange(t, 2).getValue();
//r is the ORDER QUANTITY
var q = activeSheet.getRange(n, 3).getValue();
//q is the PRODUCT SKU
var u = activeSheet.getRange(n, 4).getValue();
//u is the PRODUCT QUANTITY
if (x != q) {
n++;
} else {
s = u - r;
}
var m = activeSheet.getRange(n,4).setValue(s);
}
}
I need the cell "n,4" (order quantity) to update so the value equals the result of "u"(product quantity) minus "r"(order quantity)
The code "if" fragment should be corrected as below:
if (x != q) {
n++;
} else {
activeSheet.getRange(n,4).setValue(u - r);
}
Update after discussion:
function updateProductQuantities() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var values = activeSheet.getDataRange().getValues();
// Sum quantities by orders (columns A and B)
var sku = {};
for (var i in values) {
if (i == 0) continue; // headers row
if (sku[values[i][0]] == undefined) {
sku[values[i][0]] = values[i][1];
} else {
sku[values[i][0]] += values[i][1];
}
}
// Update product quantities (columns C and D)
for (i in values) {
if (sku[values[i][2]] != undefined) {
values[i][3] -= sku[values[i][2]];
}
}
// return values to the sheet
activeSheet.getDataRange().setValues(values);
}
You should use 2 "for" loops. One is for sum of orders quantities, and the other is for subtraction.
Here is how you can get all the data, modify it and set it in the sheet.
function productLoop2() {
var sheet = SpreadsheetApp.getActive().getActiveSheet();
// Get the active sheet of the active spreadsheet
var orderRange = sheet.getRange('A2:B52');
var productRange = sheet.getRange('C2:D52');
// Get the data ranges (change these references as necessary)
var orderData = orderRange.getValues();
var productData = productRange.getValues();
// Get the values from the ranges. This method returns an array.
for (var row = 0; row < orderData.length; row++) {
// Loops through every row of the order array
// Arrays are zero-based; this means the first element is element 0,
// the second element in element 1 and so on.
// Data is accessed with [row index][column index];
var oSku = orderData[row][0];
var oQty = orderData[row][1];
for (var productIndex = 0; productIndex < productData.length; productIndex++) {
// Loops through every product in the product array
var pSku = productData[productIndex][0];
var pQty = productData[productIndex][1];
if (oSku === pSku) {
productData[productIndex][1] = pQty - oQty;
// Changes the pQty value in the array
break;
// Added upon suggestion from user tehhowch
}
}
}
productRange.setValues(productData);
// Sets all product values in the array to the range from which they were taken
}
References:
Multidimensional arrays
Best practices - batch operations
For loops

Depending on (Id). How to place this database (Ppt) in (MP04 / MP 05)

Description: I have a sheet (Ppto) with a list of IDs (Id), Credits(Cedent) and Debits(Recept). I would like to move these transactions to MP05, If Id are equal. If Ids are not equal move to MP04. Criterion: If Id = use MP05. If Id ≠ use MP04.
I'm a novice in google script, I need some support.
Thanks for your attention
function mp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ppto = ss.getSheetByName('Ppto.');
var Id = ppto.getRange('B5:B12').getValues();
var cedent = ppto.getRange('D5:D12').getValues();
var recept = ppto.getRange('E5:E12').getValues();
for (var i = 0; i < cedent.length; i++) {
for (var j = 0; j < recept.length; j++) {
if (cedent[i] != '' ) {
if (recept[j] != '' ) {
//if (Id === Id) // MP-05
//if (Id != Id) // MP-04
{
ppto.getRange('H5:H12').setValues(cedent);
ppto.getRange('I5:I12').setValues(recept);
Logger.log(cedent[i]);
ppto.getRange('j5:j12').setValues(cedent);
ppto.getRange('k5:k12').setValues(recept);
}
}
}
}
}
}
Strategy:
FIFO: First-In First Out
Loop through all rows using forEach
If credit is present, Loop again through all rows using some to look for receipts
If credit e[2] in first loop equals receipts f[3] in second loop, Check for id [0]
If ID is equal, splice two empty columns at the end, else at the 2nd position to create a uniform 6-column array
Set that array back to the sheet.
Sample Script:
function transactionSegregator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ppto = ss.getSheetByName('Ppto.');
var data = ppto.getRange('B5:E12').getValues();
data.forEach(function(e) {
//e: Each row
if (e[2] && e.length == 4) {
//e[2]:credits; If this row is not spliced
data.some(function(f) {
//f:Each row; Second loop
if (e[2] == f[3]) {
//if credits = debit
if (e[0] == f[0]) {
//if id = id, splice two empty columns after Col4, else after Col2
e.splice(4, 0, '', '');
f.splice(4, 0, '', '');
} else {
e.splice(2, 0, '', '');
f.splice(2, 0, '', '');
}
return true;
}
});
}
});
Logger.log(data);
ppto.getRange(5, 6, data.length, data[0].length).setValues(data);// F5
}
References:
Javascript tutorial
Array#forEach
Array#some
Array#splice