I want to implement Groups in Google Sheets via API(appscript) as the direct method doesn't works dynamically. I have a column named levels(0-8) and then two more columns(other info). I want to write a script to make the groups. It will check the first column which has levels and if the next row has level more than the current i level, it will make a group of those rows until a row comes which has the same level or less than the i level. For example, levels are: 1,2,3,4,1,0,3,4. In this it will start from 1 and make the group of 2,3,4 as they are greater than 1. Skip 1,0 as they are equal or less than that and then make a group of 3,4. It will then run for 2 and do the same, make a group for 3,4 and skipping 1,0 and then make a group for 3,4.
Here is the link: https://docs.google.com/spreadsheets/d/1Ejbkl2imgEFi2mVwQ81xF5OkC97IXc4UcQIC3dxwPh4/edit?usp=sharing
Here's the code:
function myFunction() {
const rootSheet = SpreadsheetApp.getActive().getActiveSheet();
var r = rootSheet.getLastRow();
for (var i = 3; i <= r; i++) {
var t = 0;
do {
rootSheet.getRange(i,6).shiftRowGroupDepth(1);
t = t + 1;
} while (SpreadsheetApp.getActiveSheet().getRange(i,1).getValue() == t)
}
}
Here's how manually I have achieved grouping as per the pictures: https://drive.google.com/file/d/1JthF2ZJXgj5--0IOnW1LCM5Pneo9XUxJ/view?usp=sharing https://drive.google.com/file/d/1JthF2ZJXgj5--0IOnW1LCM5Pneo9XUxJ/view?usp=sharing
Modification points:
In your script, it seems that the values from the column "A" in "Sheet1" are not used for the group depth.
In this case, I would like to propose the following flow.
Retrieve values from the column "A".
Set the group depth using the retrieved values.
Modified script:
function myFunction() {
const sheetName = "Sheet1";
// 1. Retrieve values from the column "A".
const rootSheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
const levels = rootSheet.getRange("A2:A" + rootSheet.getLastRow()).getValues();
// 2. Set the group depth using the retrieved values.
levels.forEach(([a], i) => rootSheet.getRange(i + 2, 1).shiftRowGroupDepth(a));
}
Result:
When above script is used for your shared Spreadsheet, the following result is obtained.
Reference:
shiftRowGroupDepth(delta)
Related
I need some help with the Google Sheets Apps Script to do the following:
select column T
apply filter and select only values "In review" in that column
change all values "In review" to "Done"
select and copy rows with "Done" value from columns A-L and N-R (i.e. skip column M).
I have tried recording a macro (with relative and absolute values) but it only works for the specific rows that were selected for the macro. It does not apply to the entire sheet.
function myFunction() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
const lastRow = spreadSheet.getLastRow();
var arrayDoneRowNumber = []
for (let i = lastRow; i > 1 ; i--) {
var value = spreadSheet.getRange('T'+i).getValue();
if (value == 'InReview') {
spreadSheet.getRange("T"+i).setValue("Done");
arrayDoneRowNumber.push(i)
} else if (value == 'Done') {
arrayDoneRowNumber.push(i)
}
}
console.log(arrayDoneRowNumber)
}
This code snippet is responsible for the 1st three tasks you have mentioned. I will update the code when you have responded to my question for the 4th task.
Note :- Anyhow the array arrayDoneRowNumber has the row numbers of all the rows which have Done in column T. You can simply process further using this information of row numbers itself.
Seeking a script that will copy data from source sheet range B4:C and paste to a separate target workbook columns H4:I only rows with content in one or both cells (ie. discarding row 7 of the top table below), sorted by source column C date values latest to earliest, as demonstrated in the tables below:
Data source is constantly being added to and rearranged, so this is a growing range; not a static range dimension as is defined in my below script
Below is best I can make it work, though it doesn't omit empty rows, sort by date, and only evaluates a static source range. And it takes kind of a long time to process (~2 minutes, but it's not that big of a deal), so I'm hoping a proper script as suggested by the Community might improve that aspect as well:
function Import() {
var cc = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.openById('sheetID').getSheetByName('tabname');
var data = ss.getRange("B4:C8723").getValues();
cc.getRange("H4:I").clearContent();
cc.getRange("H4:I8723").setValues(data)
}
The desired script is, in effect, the following formula:
=query(importrange(sheet,tab!range),"select B,C where B is not null order by C desc")
Hope the request is clear; thanks for the help.
I believe your goal is as follows.
You want to convert the formula of =query(importrange(sheet,tab!range),"select B,C where B is not null order by C desc") to Google Apps Script.
In this case, how about the following modification?
From:
cc.getRange("H4:I").clearContent();
cc.getRange("H4:I8723").setValues(data)
To:
var values = data.filter(([b]) => b != "").sort(([,c1], [,c2]) => c1 > c2 ? -1 : 1);
cc.getRange("H4:I").clearContent();
cc.getRange("H4:I" + (values.length + 3)).setValues(values);
In this modification, the filtered values are sorted and put them to the destination sheet.
References:
filter()
sort()
function Import() {
var dss = SpreadsheetApp.getActive();
const dsh = dss.getActiveSheet();
var sss = SpreadsheetApp.openById('sheetID');
const ssh = sss.getSheetByName('tabname');
var vs = ssh.getRange(4,2,ssh.getLastRow() -3,2).getValues().filter(r => r[0] && r[1]);
dsh.getRange(4,8,dsh.getLastRow() - 3, 2).clearContent();
dsh.getRange(4,8,vs.length,vs[0].length).setValues(vs)
}
I'm trying to find a way to find the last row with data in Column D. I also want the search to start at Row 4.
I'm really struggling and would appericate any help please.
You can refer to this code:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var rowOffset = 3;
var count = sheet.getRange('D4:D').getDisplayValues().flat().filter(String).length;
var lastRow = count+rowOffset;
Logger.log(lastRow);
What it does?
Select Range D4:D and get its value, since you want to get the last row with data in column D starting at row 4.
Use array.flat() to change 2-d array into 1-d array.
Use array.filter() to remove empty values. Then get it's array length
To get the last row index, Use the cell count that has data which we obtained in step 3 and add it to 3 (start offset since we start our search at row 4)
Note:
This solution will only work assuming you don't have empty rows in between.
Output:
Execution log
2:01:53 AM Notice Execution started
2:01:54 AM Info 13.0
2:01:55 AM Notice Execution completed
There are many ways to find the last value in a column, but here is one. See if this helps!
function myFunction() {
const spreadsheet = SpreadsheetApp.openByUrl('Insert Sheet URL Here');
const sheet = spreadsheet.getSheetByName('Insert Sheet Name Here - e.g. Sheet1');
const lastSheetRow = sheet.getLastRow();
let lastColumnRow = 'This column is empty';
let reversedColumnValues = sheet.getRange(1, 4, lastSheetRow).getValues().reverse();
for (index in reversedColumnValues) {
if (reversedColumnValues[index][0] != '') {
lastColumnRow = lastSheetRow - index;
break;
}
}
Logger.log(lastColumnRow);
}
This Apps Script Video may help you out too: https://www.youtube.com/watch?v=1Po1QElOFPk
I'm very new to Google Scripts so any assistance is greatly appreciated.
I'm stuck on how to apply my formula which uses both JOIN and FILTER to an entire column in Google Sheets.
My formula is: =JOIN(", ",FILTER(N:N,B:B=R2))
I need this formula to be added to each cell in Column S (except for the header cell) but with 'R2' changing per row, so in row 3 it's 'R3', row 4 it's 'R4' etc.
This formula works in Google sheets itself but as I have sheet that is auto replaced by a new updated version daily I need to set a google script to run at certain time which I can set up via triggers to add this formula to my designated column.
I've tried a few scripts I've found online but none have been successful.
If you want to solve this using only formulas:
Since your formula is always in the format:
=JOIN(", ",FILTER(N:N,B:B=R<ROW NUMBER>))
and you want to apply it to a very large number of rows, you can use INDIRECT and ROW to achieve a dynamic formula. This answer has a good example on how to use this.
Using formulas you don't risk running into time limits with Apps Script
In practical terms, if you have your data on column A, you can write =ARRAYFORMULA(CONCAT("R",ROW(A2:A))) to get something like this:
Your final formula should look like this:
=JOIN(", ",FILTER($N:$N,B:B=INDIRECT(CONCAT("R",ROW($R2)))))
Also, you can drag it down to other cells like any other formula!
Set the formulas through Apps Script:
You can use setFormulas(formulas) to set a group of formulas to all the cells in a range. formulas, in this case, refers to a 2-dimensional array, the outer array representing the different rows, and each inner array representing the different columns in each specific row. You should build this 2D array with the different formulas, while taking into account that the row index from R should be different for each single formula.
You could do something like this:
function settingFormulas() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var firstRow = 2;
var column = 19; // Column S index
var range = sheet.getRange(firstRow, column, sheet.getLastRow() - firstRow + 1);
var formulas = range.getValues().map((row, index) => {
let rowIndex = index + firstRow;
return ["=JOIN(\", \",FILTER(N:N,B:B=R" + rowIndex + "))"];
});
range.setFormulas(formulas);
}
In this function, the optional index parameter from the method map is used to keep track of the row index, and adding it to the formula.
In this function, the sheet name is used to identify which sheet the function has to set the formulas to (in this case, the name's Sheet1). Here I'm assuming that once the sheet is replaced by a newer one, the sheet name remains the same.
Execute this daily:
Once you have this function, you just have to install the time-driven trigger to execute this function daily, either manually, following these steps, or programmatically, by running this function once:
function creatingTrigger() {
ScriptApp.newTrigger("settingFormulas")
.timeBased()
.everyDays(1)
.create();
}
Reference:
setFormulas(formulas)
getRange(row, column, numRows)
Installable Triggers
Instead of the workaround hacks I implemented a simple joinMatching(matches, values, texts, [sep]) function in Google Apps Script.
In your case it would be just =joinMatching(R1:R, B1:B, N1:N, ", ").
Source:
// Google Apps Script to join texts in a range where values in second range equal to the provided match value
// Solves the need for `arrayformula(join(',', filter()))`, which does not work in Google Sheets
// Instead you can pass a range of match values and get a range of joined texts back
const identity = data => data
const onRange = (data, fn, args, combine = identity) =>
Array.isArray(data)
? combine(data.map(value => onRange(value, fn, args)))
: fn(data, ...(args || []))
const _joinMatching = (match, values, texts, sep = '\n') => {
const columns = texts[0]?.length
if (!columns) return ''
const row = i => Math.floor(i / columns)
const col = i => i % columns
const value = i => values[row(i)][col(i)]
return (
// JSON.stringify(match) +
texts
.flat()
// .map((t, i) => `[${row(i)}:${col(i)}] ${t} (${JSON.stringify(value(i))})`)
.filter((_, i) => value(i) === match)
.join(sep)
)
}
const joinMatching = (matches, values, texts, sep) =>
onRange(matches, _joinMatching, [values, texts, sep])
I created an array containing multiple rows and columns.
In column B, I have the type of vegetables, and in column c, the varieties.
In the array, there is an automatic sorting which is carried out in column B then column c.
Is it possible to merge automatically the cells of column B containing the same type?
I just know how to merge a range with :
var range1 = sheet.getRange("b4:b9");
range1.merge();
A test array here
Cordially.
How about this sample script? I think that there are several answers for this situation, so please think of this as one of them. The flow of this script is as follows.
Flow :
Retrieve values of column B.
Retrieve the number of duplication values.
Merge cells.
Sample script :
function myFunction() {
var start = 4; // Start row number for values.
var c = {};
var k = "";
var offset = 0;
var ss = SpreadsheetApp.getActiveSheet();
// Retrieve values of column B.
var data = ss.getRange(start, 2, ss.getLastRow(), 1).getValues().filter(String);
// Retrieve the number of duplication values.
data.forEach(function(e){c[e[0]] = c[e[0]] ? c[e[0]] + 1 : 1;});
// Merge cells.
data.forEach(function(e){
if (k != e[0]) {
ss.getRange(start + offset, 2, c[e[0]], 1).merge();
offset += c[e[0]];
}
k = e[0];
});
}
Result :
Note :
This sample script supposes that the values of column B is sorted.
From your shared spreadsheet, it supposes that there are the values for merging cells at column B.
In your shared spreadsheet, the order of sorted values is Composées, Cucurbitacées, Légumineuses, Liliacées, Solanacées. On the other hand, the order of "Wish" is Composées, Cucurbitacées, Légumineuses, Solanacées, Liliacées.
I couldn't understand the difference logic between Liliacées, Solanacées and Solanacées, Liliacées.
In this sample script, it uses the order of sorted values.
Reference :
merge()
If I misunderstand your question, I'm sorry.
Edit 1 :
For your next question, I think that the following flow can achieve what you want. But I think that there may be other solution.
Add values by an user.
Break the merged cells using breakApart().
For example, it merges cells of "A1:A3" which have the values of "sample", "sample", "sample". When this merged cell is broken using breakApart(), each value of "A1:A3" retrieved by getValues() becomes [["sample"],[""],[""]].
Fill the empty cells created by breakApart().
Sort the cells.
Run the sample script on my answer.
Reference :
breakApart()
Edit 2 :
Usage of breakApart():
If you want to break "B1:B", please use as follows.
var ss = SpreadsheetApp.getActiveSheet();
ss.getRange("B1:B").breakApart()