Google Sheet App Script - How to only setValues only unique values - google-apps-script

In this spreadsheet: https://docs.google.com/spreadsheets/d/1givNbMvgzD8lbk6NAcwjkpp4-A_D8MetltHjEpinOAI/edit#gid=0
I'd like to Combine only unique Links and Category into Combined sheet.
Right now, with my script, it can only combine all existing data:
function combine() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var sourceID = '1givNbMvgzD8lbk6NAcwjkpp4-A_D8MetltHjEpinOAI';
var targetID = '1givNbMvgzD8lbk6NAcwjkpp4-A_D8MetltHjEpinOAI';
var sheetExclude = ["Combined"];
var sheetExcludeIndex = new Array(sheetExclude.length);
for (var s in allsheets) {
var sheet = allsheets[s];
for (var e in sheetExclude) {
if (String(sheet.getName() == sheetExclude[e])) {
sheetExcludeIndex[e] = sheet.getIndex;
}
}
}
allsheets.splice(sheetExcludeIndex, sheetExclude.length);
for (var s in allsheets) {
var sheet = allsheets[s];
updateSourceToTarget(sourceID, sheet.getName(), targetID, 'Combined');
}
}
function updateSourceToTarget(sourceID, sourceName, targetID, targetname) {
Logger.log(sourceID + ' ' + sourceName + ' ' +targetname);
var source = SpreadsheetApp.openById(sourceID).getSheetByName(sourceName);
var destination = SpreadsheetApp.openById(targetID).getSheetByName(targetname);
var sourcelastRow = source.getLastRow();
var sourcelastCol = source.getLastColumn();
var destinationlastRow = destination.getLastRow();
var destinationlastCol = destination.getLastColumn();
var sourcedata = source.getRange(2, 9, sourcelastRow, 10).getValues();
destination.getRange(destinationlastRow + 1, 2, sourcelastRow, sourcelastCol).setValues(sourcedata);
}
However, I'd like to only combine unique links from Sheet2 and Sheet3:
In red is unique data
Sheet2:
Sheet3:
How can I efficiently add only unique values to Combined from Sheet2& Sheet3?

You want to put the values, which removed the duplicated links, to the target sheet (in this case, it's Combined sheet.).
The duplicated links are checked from the target sheet and source sheets. In this case, the target sheet and source sheets are Combined, Sheet2 and Sheet3, respectively.
In your sample Spreadsheet, you want to put the following rows to the target sheet.
https://thehill.com/policy/national-security/department-of-homeland-security/460158-new-hampshire-border-patrol BorderSecurity
https://abcnews.go.com/International/climate-change-frontier-worlds-northernmost-town/story?id=65381362 ClimateChange
You want to achieve this by modifying your Google Apps Script.
If my understanding is correct, how about this modification? Please think of this as just one of several answers.
Modified script:
Please modify your script as follows. In this modification, your function of updateSourceToTarget() is not used.
From:
for (var s in allsheets) {
var sheet = allsheets[s];
updateSourceToTarget(sourceID, sheet.getName(), targetID, 'Combined');
}
To:
// Retrieve values from the target sheet.
var targetSheet = ss.getSheetByName(sheetExclude[0]);
var targetValues = targetSheet.getRange("B2:C" + targetSheet.getLastRow()).getValues();
// Retrieve values from all source sheets. <--- Modified
var sourceValues = allsheets.reduce(function(ar, sheet) {
var v = sheet.getRange(2, 9, sheet.getLastRow() - 1, 10).getValues().filter(function(r) {return r[0] && r[1]});
if (v.length > 0) {
v = v.filter(function(e) {return !ar.some(function(f) {return e[0] === f[0]})});
Array.prototype.push.apply(ar, v);
}
return ar;
}, []);
// Remove the duplication values between the target sheet and all source sheets.
var dstValues = sourceValues.filter(function(e) {return !targetValues.some(function(f) {return e[0] === f[0]})});
// Add the result values to the target sheet.
if (dstValues.length > 0) {
var destination = SpreadsheetApp.openById(targetID).getSheetByName(sheetExclude[0]);
destination.getRange(destination.getLastRow() + 1, 2, dstValues.length, dstValues[0].length).setValues(dstValues);
}
The flow of this modified script is as follows.
Retrieve values from the target sheet.
Retrieve values from all source sheets.
Remove the duplication values between the target sheet and all source sheets.
Add the result values to the target sheet.
Note:
When your shared Spreadsheet is used as the target (Combined) and source sheets (Sheet2 and Sheet3), the following rows are added to the target sheet.
https://thehill.com/policy/national-security/department-of-homeland-security/460158-new-hampshire-border-patrol BorderSecurity
https://abcnews.go.com/International/climate-change-frontier-worlds-northernmost-town/story?id=65381362 ClimateChange
References:
reduce()
filter()
some()
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
In this additional script, a hash table is used for this situation, as mentioned by TheMaster's comment. For example, a sample can be also seen at this thread. In your situation, at first, all values are retrieved from all sheets including Combined sheet, and the hash table is created. By this, the duplicated values are removed. Then, the converted values to an array are put to the Spreadsheet.
Sample script:
Please modify your script as follows.
From:
allsheets.splice(sheetExcludeIndex, sheetExclude.length);
for (var s in allsheets) {
var sheet = allsheets[s];
updateSourceToTarget(sourceID, sheet.getName(), targetID, 'Combined');
}
To:
// allsheets.splice(sheetExcludeIndex, sheetExclude.length); // In this script, this line is not used.
// Retrieve values from the target sheet.
var targetSheet = ss.getSheetByName(sheetExclude[0]);
var targetValues = targetSheet.getRange("B2:C" + targetSheet.getLastRow()).getValues();
// Retrieve values from all source sheets.
// Remove the duplication values between the target sheet and all source sheets.
var sourceValues = allsheets.reduce(function(obj, sheet) {
var v = sheet.getRange(2, 9, sheet.getLastRow() - 1, 10).getValues().filter(function(r) {return r[0] && r[1]});
if (v.length > 0) v.forEach(function(e) {if (!(e[0] in obj)) obj[e[0]] = e[1]});
return obj;
}, {});
var dstValues = Object.keys(sourceValues).map(function(e) {return [e, sourceValues[e]]});
// Add the result values to the target sheet.
if (dstValues.length > 0) {
var destination = SpreadsheetApp.openById(targetID).getSheetByName(sheetExclude[0]);
destination.getRange(2, 2, destination.getLastRow(), 2).clearContent();
destination.getRange(2, 2, dstValues.length, dstValues[0].length).setValues(dstValues);
}
The proposed 2 sample scripts can be used for your situation. So please select one of them.

Related

Using Checkbox to Transpose Paste onto Target Range using Google Sheets

The script inserted copies the values in range Sheet1!A5:F5 then transpose pastes into the first row and second column of Sheet2.
The script is able to copy and transpose paste under the last row of the second column on continuous use until it is reset.
.
Current Result Before Paste - Copies Range Sheet1 A5:F5
Current Result After Paste - Pastes to Range 2nd Column in Sheet2
.
.
.
My aim would be:
for the script to work with the checkbox in cell B2.
to create a target range for the values to be pasted to:
Sheet2! B7:B - hopefully still allowing the transpose paste to function through getLastRow()+1.
Expected Result Before Paste - Checkbox = True, Copying Range Sheet1 A5:F5
Expected Result After Paste - Paste to Range Sheet2 B7:B
Have tried inserting:
if (sheet.getSheetName() != "*DuplioMltSlds" || range.getA1Notation() != "D13" || !range.isChecked()) return;
as well as changing the function to onEdit.
Have also tried various var row = ss.getRange("Sheet2!B7:B") options, however, have not been able find a suitable result.
Sample Sheet
Script source and script:
function copyTransposeAndDelete () {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var source = ss.getRange("Sheet1!A5:F5");
var destSheet = ss.getSheetByName("Sheet2");
var destRange = destSheet.getRange(destSheet.getLastRow()+1, 2, source.getWidth(), source.getHeight());
destRange.setValues(transpose(source.getValues()));
}
function transpose(array) {
return array[0].map(function(row, i) {
return array.map(function(col) {
return col[i];
});
});
}
I believe your goal is as follows.
You want to copy and paste the cells "A5:F5" of "Sheet1" to the cell "B2" of "Sheet2". In this case, you want to copy the values by transposing.
When the values are copied again, you want to paste the transposed values to (the last row + 1) of column "B" of "Sheet2".
You want to run the script when the checkbox of cell "B2" of "Sheet1" is checked.
In this case, how about the following modification?
Modified script:
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639
Object.prototype.get1stNonEmptyRowFromBottom = function (columnNumber, offsetRow = 1) {
const search = this.getRange(offsetRow, columnNumber, this.getMaxRows()).createTextFinder(".").useRegularExpression(true).findPrevious();
return search ? search.getRow() : offsetRow;
};
var { source, range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != "Sheet1" || range.getA1Notation() != "B2" || !range.isChecked()) return;
var srcRange = sheet.getRange("A5:F5");
var dstSheet = source.getSheetByName("Sheet2")
var row = dstSheet.get1stNonEmptyRowFromBottom(2) + 1;
var dstRange = dstSheet.getRange("B" + (row < 7 ? 7 : row));
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, true);
}
In your situation, I thought that copyTo(destination, copyPasteType, transposed) might be suitable. Because, in this method, the values can be copied by transposing.
When the OnEdit trigger is used, you can use the event object. By this, the process cost will become low a little. Ref
Note:
When you use this script, please check the checkbox of "B2" of "Sheet1". By this, the script is run. When you directly run the script with the script editor, an error like TypeError: Cannot destructure property 'source' of 'e' as it is undefined. occurs. Please be careful about this.
References:
copyTo(destination, copyPasteType, transposed)
Event Objects
Added:
If you want to put the values to the column "B" of "Sheet2" even when the column "B" of "Sheet2" has already had the values after row 7, how about the following sample script?
Sample script:
function onEdit(e) {
// Ref: https://stackoverflow.com/a/44563639/7108653
Object.prototype.get1stEmptyRowFromTop = function (columnNumber, offsetRow = 7) {
const range = this.getRange(offsetRow, columnNumber, 2);
const values = range.getDisplayValues();
if (values[0][0] && values[1][0]) {
return range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
} else if (values[0][0] && !values[1][0]) {
return offsetRow + 1;
}
return offsetRow;
};
var { source, range } = e;
var sheet = range.getSheet();
if (sheet.getSheetName() != "Sheet1" || range.getA1Notation() != "B2" || !range.isChecked()) return;
var srcRange = sheet.getRange("A5:F5");
var dstSheet = source.getSheetByName("Sheet2")
var row = dstSheet.get1stEmptyRowFromTop(2);
var dstRange = dstSheet.getRange("B" + row);
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, true);
}
function myfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet1")
const vs = sh.getRange("A4:F4").getValues().flat().map(e => [e]);
const dsh = ss.getSheetByName("Sheet2");
dsh.getRange(7,2,vs.length,vs[0].length).setValues(vs)
}

Google Sheets Script to import data based on cell value and not duplicate information

I need to pull/import data from "sheet 1" to "sheet 2" based on column 4 being a specific text string. The script should not pull lines that already exist.
I have no idea if this is possible. I can pull the data but it just recopies everything so I have duplicates.
Any help would be super appreciated.
function onEdit() {
var ss = SpreadsheetApp.openById('1Ognzsi6C0DU_ZyDLuct58f5U16sshhBpBoQ8Snk8bhc');
var sheet = ss.getSheetByName('Sheet 1');
var testrange = sheet.getRange('D:D');
var testvalue = (testrange.getValues());
var sss = SpreadsheetApp.getActive();
var csh = sss.getSheetByName('Sheet 1');
var data = [];
var j =[];
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] == 'Dan') {
data.push.apply(data,sheet.getRange(i+1,1,1,11).getValues());
j.push(i);
}
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
}
Sheet 1
Sheet 2
Solution
You should be able to replace your code with this and it will work. You would put this script in the target sheet (Sheet 2), and replace the ID in the first line of the function with the origin (Sheet 1).
I'll leave it up to you to change to an onEdit or to make it a menu item. Right now it can be run from the script editor. onEdit doesn't make sense to me as an appropriate trigger. Maybe you prefer a Time-Driven Trigger. Though a custom menu would be the best way IMO.
function pullData() {
var sourceSs = SpreadsheetApp.openById('[YOUR_SPREADSHEET_ID]');
var sourceRange = sourceSs.getSheetByName('Sheet1').getDataRange();
var sourceHeight = sourceRange.getHeight();
var sourceWidth = sourceRange.getWidth();
var sourceData = sourceSs.getSheetByName('Sheet1').getRange(2, 1, sourceHeight - 1, sourceWidth).getValues();
var targetSs = SpreadsheetApp.getActive();
var targetRange = targetSs.getSheetByName('Sheet1').getDataRange();
var targetHeight = targetRange.getHeight();
var targetWidth = targetRange.getWidth();
var sourceDataChecker = [];
var targetDataChecker = [];
sourceData.forEach((row) => {
sourceDataChecker.push(row[0] + row[1] + row[2] + row[3]);
})
if (targetHeight != 1) {
var targetData = sourceSs.getSheetByName('Sheet1').getRange(2, 1, targetHeight - 1, targetWidth).getValues();
targetData.forEach((row) => {
targetDataChecker.push(row[0] + row[1] + row[2] + row[3]);
});
};
sourceData.forEach((row, i) => {
if (!(targetDataChecker.includes(sourceDataChecker[i]))) {
targetSs.appendRow(row);
};
});
}
Explanation
This script builds an "index" of each row in both sheets by concatenating all the values in the row. I did this because I noticed that sometimes you have "joe" in two rows, and so, you can't simply use column 4 as your index. You are basically checking for any row that is different from one in the target sheet (Sheet 2).
If the target sheet is blank, then all rows are copied.
References
Append Row to end of sheet
Get Data Range (range of sheet that contains data)
Get Range Height (to deal with headers)
Get Range Width
for Each

How to get filtered values from Filter after using setColumnFilterCriteria?

I m in trouble using some filter in appscript.
I can see that the spreasheet is filtering, but programaticaly i don't see any changes.
Could you help ?
Thanks
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("xxxx");
var values = sheet.getDataRange().getValues();
Logger.log("VALUES "+values.length);
var newCriteria = SpreadsheetApp.newFilterCriteria().whenTextEqualTo('51').build();
var range = sheet.getFilter().setColumnFilterCriteria(22, newCriteria).getRange(); //The 1-indexed position of the column.
values = range.getValues();
Logger.log("VALUES "+values.length);
Results on logging :
19-08-28 19:27:33:272 CEST] VALUES 1379
[19-08-28 19:27:39:748 CEST] VALUES 1379
You want to retrieve values from the filtered sheet in the Spreadsheet.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer?
Issue and workaround:
Unfortunately, the values cannot be retrieved from the filtered sheet using getValues(). This has already been mentioned by TheMaster`s comment. As the workarounds, I would like to propose the following 2 patterns.
Pattern 1:
When the Spreadsheet is published, the filtered sheet can be seen. In this pattern, this is used, and the values are retrieved using the query Language. But don't worry. In this script, the access token is used. So the filtered sheet can be directly retrieved without publishing Spreadsheet. I think that this is the simple way.
Modified script:
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getSheetByName("xxxx"); // Modified
var values = sheet.getDataRange().getValues();
Logger.log("VALUES "+values.length);
var newCriteria = SpreadsheetApp.newFilterCriteria().whenTextEqualTo('51').build();
var range = sheet.getFilter().setColumnFilterCriteria(22, newCriteria).getRange(); //The 1-indexed position of the column.
// values = range.getValues();
// I added below script.
var url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/gviz/tq?tqx=out:csv&gid=" + sheet.getSheetId() + "&access_token=" + ScriptApp.getOAuthToken();
var res = UrlFetchApp.fetch(url);
var values = Utilities.parseCsv(res.getContentText());
Logger.log("VALUES "+values.length);
Pattern 2:
In this pattern, Sheets API is used. Before you use this script, please enable Sheets API at Advanced Google services.
Modified script:
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getSheetByName("xxxx"); // Modified
var values = sheet.getDataRange().getValues();
Logger.log("VALUES "+values.length);
var newCriteria = SpreadsheetApp.newFilterCriteria().whenTextEqualTo('51').build();
var range = sheet.getFilter().setColumnFilterCriteria(22, newCriteria).getRange(); //The 1-indexed position of the column.
// values = range.getValues();
// I added below script.
var res = Sheets.Spreadsheets.get(ss.getId(), {
ranges: ["xxxx"], // <--- Please set the sheet name.
fields: "sheets/data"
});
var values = res.sheets[0].data[0].rowMetadata.reduce(function(ar, e, i) {
if (!e.hiddenByFilter && res.sheets[0].data[0].rowData[i]) {
ar.push(
res.sheets[0].data[0].rowData[i].values.map(function(col) {
return col.userEnteredValue[Object.keys(col.userEnteredValue)[0]];
})
);
}
return ar;
}, []);
Logger.log("VALUES "+values.length);
References:
Query Language
Method: spreadsheets.get
Advanced Google services
If I misunderstood your question and this was not the result you want, I apologize.

setFormulas() overwrites values and setValues() overwrites formulas [duplicate]

I am using the following script to import data from another Google Sheet but it doesn´t bring in the formula I need with it.
I have tried ImportRange but that seems to just sync the two sheets and I need to delete the data in the original sheet (Sheet 1) but that also deletes it in the sheet I bought in to (Master Sheet).
Is there another way to accomplish this?
function Copy() {
var sss = SpreadsheetApp.openById('Sheet 1 ID');
var ss = sss.getSheetByName('results');
var range = ss.getRange('2:688');
var data = range.getValues();
var tss = SpreadsheetApp.openById('Master Sheet ID');
var ts = tss.getSheetByName('results');
ts.getRange(ts.getLastRow() + 1, 1, data.length, data[0].length).setValues(
data
);
var sheet = SpreadsheetApp.openById('Sheet 1 ID').getSheetByName('Roger');
ss.getRange('2:688').clearContent();
}
Issue:
Formulas are not retrieved with getValues()
Possible Solutions:
Use range#getFormulas and range#getValues and mix those two arrays to create a array of formulas and values to setValues() later.
Use Advanced Google Services to access Sheets API to directly get the mix.
Sample Script Snippets:
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;
});
});
or
var req = {
ranges: 'results!2:688',
valueRenderOption: 'FORMULA', //get formulas with values
};
var r = Sheets.Spreadsheets.Values.batchGet('Sheet 1 ID', req);
data = r.valueRanges[0].values;
References:
getFormulas
EnablingAdvancedServices
batchGet

Google Apps Script Find bottom of column

I need some assistance with Google Apps Script. I am working in google sheets and currently have the following script:
function transpose() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('C3:N3').activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet21'), true);
spreadsheet.getRange("'A/P'!C3:N3").copyTo(spreadsheet.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, true);
};
Rather than just paste in A1 of Sheet 21, I would like it to find the bottom of column F. I am also wondering how to copy data from whichever sheet I am in, not just the sheet named A/P.
Thank you in advance!
You want to add the values of C3:N3 in the active sheet to the next row of last row on column F in Sheet21. If my understanding is correct, how about this modification?
Modification points :
In your script, if the active sheet is not Sheet21, the active range becomes "A1", because spreadsheet.getRange('C3:N3').activate() at other sheet is changed by spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet21'), true). By this, the values of "'A/P'!C3:N3" are copied for "A1" of Sheet21.
Flow of modified script:
Retrieve the range of source (Active sheet)
Retrieve the destination (Sheet21) sheet.
Retrieve the destination range.
Copy
Modified script : Pattern 1
If the address of last row of column "F" in "Sheet21" is smaller than that of other columns, please use this.
function transpose() {
var spreadsheet = SpreadsheetApp.getActive();
var srcRange = spreadsheet.getActiveSheet().getRange('C3:N3');
var dstSheet = spreadsheet.getSheetByName('Sheet21');
var range = dstSheet.getRange('F1:F');
var values = range.getValues();
var formulas = range.getFormulas();
var i;
for (i = values.length - 1; i >= 0; i--) {
if (values[i][0] != "" || formulas[i][0] != "") break;
}
var dstRange = dstSheet.getRange("F" + (i + 2));
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_NORMAL, true);
};
Modified script : Pattern 2
If the address of last row of column "F" in "Sheet21" is larger or the same with that of other columns, please use this.
function transpose() {
var spreadsheet = SpreadsheetApp.getActive();
var srcRange = spreadsheet.getActiveSheet().getRange('C3:N3');
var dstSheet = spreadsheet.getSheetByName('Sheet21');
var dstRange = dstSheet.getRange("F" + (dstSheet.getLastRow() + 1));
srcRange.copyTo(dstRange, SpreadsheetApp.CopyPasteType.PASTE_NORMAL, true);
};
If I misunderstand your question, please tell me. I would like to modify it.