I'm trying to alter some code from a tutorial on how to remove duplicate rows from my sheet. The code works great for straight values, but some of my rows have cells with hyperlink formulas that I don't want to lose.
I think I need to make the spreadsheet data as an array, rather than just using getValues. It's been a long time since I've had to make an array, so I'm struggling with the best way to do this. I'll need for loops to populate the array and if statements to either get values or formulas depending on the row.
Here's the remove duplicates code that removes rows if the cell in column A and B match another row:
/**
* Removes duplicate rows from the current sheet.
*/
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var newData = [];
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if(row[0] == newData[j][0] && row[1] == newData[j][1]){
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
sheet.clearContents();
sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}
So instead of just using one line to set the data variable, I need to build the array with values and formulas where appropriate. Otherwise, I lose my hyperlinks. How do I do this?
Edit: This question was solved by using an answer to another question. The questions' contexts are not the same, and the other question did not show up in any of my research, so I believe my question is not a duplicate.
Thanks to the comment by TheMaster, I was able to solve this. Here's the original "Remove Duplicates" tutorial, and here's the question TheMaster directed me to to find the solution.
My final code:
/**
* Removes duplicate rows from the current sheet.
*/
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var newData = [];
var data = range.getValues();
data = range.getFormulas().map(function(e, i) {//i=index of row(e)
return e.map(function(f, j) {//j = index of column(f)
return f === "" ? data[i][j] : f;
});
});
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if(row[0] == newData[j][0] && row[1] == newData[j][1]){
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
sheet.clearContents();
sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}
Related
The code below is from here and was originally written by user #Cooper. All it does is that it copies the values of the columns specified in the array colA = [1,3,2,4] from SHEET 1 and add them to SHEET 2. I want to re-write the 2 last lines of the code, so that the values coming from SHEET 1 are added to the last available rows in SHEET 2. By "2 last lines", I mean these last lines of the code:
drng = des.getRange(1,1,drngA.length,drngA[0].length);//destination array controls size of destination range.drng.setValues(drngA);
Any idea how to get this done?
Thank you so much in advance for your help!
function Copy_data()
{
var src = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET 1");
var des = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET 2");
var srng = src.getDataRange();
var srngA = srng.getValues();
var drngA = [];
var colA = [1,3,2,4];//indirect data column selection
for (var i = 0; i < srngA.length;i++ )
{
var k = 0;
drngA[i]=[];
for(var j=0;j<srngA[i].length;j++)
{
drngA[i][k++] = srngA[i][colA[j]-1];
}
drng = des.getRange(1,1,drngA.length,drngA[0].length);//destination array controls size of destination range.
drng.setValues(drngA);
}
}
You can get the last row of the destination Sheet and after, add it to your getRange() function. The method proposed by #soMario will not work because it makes requests inside the loop while you update the Sheet, and this causes it to get different values every time you call des.getLastRow().
The simplest solution is to take the getLastRow() request outside of the loop and then include it in getRange. Note that one is added to it so that it does not overwrite the last row with the setValue() request.
Code.gs
function Copy_data() {
...
var lastRow = des.getLastRow() + 1
for (var i = 0; i < srngA.length;i++ ){
...
drng = des.getRange(lastRow,1,drngA.length,drngA[0].length)
...
}
Documentation
getRange(row, column, numRows, numColumns)
getLastRow()
I would like to add several columns to an existing source of data displayed in a sheet. But as a data source, the number of rows can increase. I don't want to update the sheet once it is set up, it should be automatic. In those new columns, I would like formulas.
Please find below the result expected.
in blue, columns from data source
in red, my new columns with a formula
How can I do ?
Thank for you help 🙏
use in E1:
={"new col 1"; ARRAYFORMULA(IF(C2:C="";;IF((ISNUMBER(C2:C))*(C2:C>=2); 1; 0)))}
use in F1:
={"new col 2"; ARRAYFORMULA(IF(D2:D="";;IF((ISNUMBER(D2:D))*(D2:D>=2); 10; 0)))}
I tried with a custom function and to personalize it with another project, it doesn't work...
In this simple case, it works. If not empty, it returns G cells in uppercase :
={"Feature"; ARRAYFORMULA(SI(G2:G="";;IF((NOT(ISEMPTY(G2:G))); UPPERCASE(G2:G); "-")))}
In other case with a custom function, not. It is named "getFeature"
={"Feature"; ARRAYFORMULA(SI(G2:G="";;IF((NOT(ISEMPTY(G2:G))); getFeature(G2:G); "-")))}
When I use it in a classic way, my function works : =getFeature(G7)
Here my custom function :
function getFeature(searchString) {
if (searchString === "") {
return ""
}
var sFeature = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("raw feature")
var rFeatureCol = sFeature.getRange(2, 1, sFeature.getLastRow(), 1)
array = searchString.split(";")
for (let i = 0; i < array.length; i++) {
try {
var textFinder = rFeatureCol.createTextFinder(array[i])
var search_row = textFinder.findNext().getValue()
return array[i]
}
catch {
// erreur détectée
}
}
return ""
}
I have a UFC database and I'm looking for rematches. Therefore, I need to find the duplicate combinations of names; the combinations that appear more than once. However, since the winner of the first fight could lose the rematch, I need to find duplicates regardless of the order in which they appear.
This is how my database is structured:
database example.
Fighter 1 is the winner and fighter 2 is the loser.
Here is a link to the database (got it from kaggle): https://docs.google.com/spreadsheets/d/19ISNhYFdGzgLZz1x4h2v_Q5Pq0cofw2rkUtwk3xPXGQ/edit?usp=sharing
Feel free to play around with it.
Any ideas on how to solve this?
Here is an example of the result I'd ideally want:
image example
Just a simple highlight over the duplicates.
In order to give you more precise help, it would be helpful if you could give an example of the result you want. As has been said, if there are many duplicates and you mark each group with a different colour, it can be visually confusing.
Anyway, I have written a couple of functions with Apps Script and Spreadsheet Service that you may find useful for your project.
Code 1
With these functions, you will be able to get all fights where two fighters that you previously define have participated:
function main() {
var name1 = 'Conor McGregor'
var name2 = 'Nate Diaz'
var result = findDuplicates(name1, name2)
console.log(result)
}
function findDuplicates(name1, name2) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Ark1')
var lastRow = sheet.getLastRow()
var lastCol = sheet.getLastColumn()
var range = sheet.getRange(1, 1, lastRow, lastCol).getValues()
var result = []
for (var i = 0; i < range.length; i++) {
if (range[i].includes(name1) && range[i].includes(name2)) {
result.push(range[i])
}
}
return result
}
Code 2
With this function, you can add in a third column, the same number for each pair of fighters. This way, it is very easy to change the "add a number" to "paint the cells in a colour".
function findPairs() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Ark2')
var lastRow = sheet.getLastRow()
var lastCol = sheet.getLastColumn()
var range = sheet.getRange(1, 1, lastRow, lastCol).getValues()
var indexToSkip = []
var k = 0
for (var i = 0; i < range.length; i++) {
if (!indexToSkip.includes(i)) {
var match = range[i]
indexToSkip.push(i)
sheet.getRange(i + 1, 3).setValue(k)
for (var j = i + 1; j < range.length; j++) {
if (range[j].includes(match[0]) && range[j].includes(match[1])) {
indexToSkip.push(j)
sheet.getRange(j + 1, 3).setValue(k)
}
}
k = k+1
}
}
}
References:
Apps Script
Spreadsheet Service
I have working code, that delete row from sheet if column corresponds to one of the conditions. It based on arrays and because of it works much more faster than standard google sheet deleteRow function. I call it like this:
deleteRowsBV('SIZ',4,'0','')
where
deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch)
What I want is a call function with more or less and equal signs, like this:
deleteRowsBV('SIZ',4,<='0',=='')
But in case of my main function, it doesn't work, when I specify a variable instead of a sign and a value.
Here is main function:
function deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(listName);
var DataLengBefore = sheet.getLastRow();
var DataColLeng = sheet.getLastColumn();
var data = sheet.getRange(1,1,DataLengBefore,DataColLeng).getValues();
for (var i = data.length-1; i >= 0; i--) {
if (data[i][ColNum] <= FirstSearch||data[i][ColNum] == SecondSearch) {
data.splice(i, 1);
}
}
sheet.getRange(10, 1, DataLengBefore,DataColLeng).clearContent();
sheet.getRange(10, 1,data.length,5).setValues(data);
}
Based from this related post, spreadsheet rows and columns are numbered starting at 1, for all methods in the SpreadsheetApp, while javascript arrays start numbering from 0. You need to adjust between those numeric bases when working with both. When deleting rows, the size of the spreadsheet dataRange will get smaller; you did take that into account, but because you're looping up to the maximum size, the code is complicated by this requirement. You can simplify things by looping down from the maximum.
You may refer with this thread. However, this only looks at the value from a single cell edit now and not the values in the whole sheet.
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//{"source":{},"range":{"rowStart":1,"rowEnd":1,"columnEnd":1,"columnStart":1},"value":"1","user":{"email":"","nickname":""},"authMode":{}}
try {
var ss = e.source; // Just pull the spreadsheet object from the one already being passed to onEdit
var s = ss.getActiveSheet();
// Conditions are by sheet and a single cell in a certain column
if (s.getName() == 'Sheet1' && // change to your own
e.range.columnStart == 3 && e.range.columnEnd == 3 && // only look at edits happening in col C which is 3
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if ( !e.value || e.value == 0) { // Delete if value is zero or empty
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}
I have a general spreadsheet in which Column B has a number 1-9. I want to copy each row item to the corresponding sheet I have created, sheet 1-9. However, I set my loop to begin at row 5 and continue the length of all rows in the data range but the loop does not continue past row 5 even though more rows have a "1" in column B. Can anyone help to see why my loop is not continuing? Thanks!
function GoodFunction() {
var spreadsheet = SpreadsheetApp.getActive()
var R0sheet = spreadsheet.getSheetByName('S14Allocation')
var R1sheet = spreadsheet.getSheetByName('R1-Allocations')
var ss = spreadsheet;
var s = R0sheet
var targetSheet = R1sheet
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
var r = s.getDataRange();
var numColumns = s.getLastColumn();
var numRows=s.getLastRow();
for (var i = 5; i <= numRows.length ; i++); {
if (s.getDataRange().getCell(i,2).getValue() === 1) {
s.getRange(i,1,1,numColumns).copyTo(target);
}
}
}
numRows is already a number (from s.getLastRow();) so use i < numRows as the condition in your for loop.
As Serge said in his comment though, the code is very confusing. There's no need for that many variables, and there are more efficient ways of dealing with multiple cells at once.
https://developers.google.com/apps-script/best_practices#batchOperations