Concatenating columns from many sheets in a single sheet - google-apps-script

I would like to retrieve the data from many sheets into one single sheet.
I have 13 columns titles in my sheets, so I only take the data from the second row of all sheets.
For example, I have 3 sheets whose name are "FR", "UK", "DE", and "Master".
My columns are Country, Name, Month Usage, Model, Machine.
from Row 2, I have plenty of data for "FR","UK","DE".
"Master" has only the columns names.
What I want to merge all the data in a sheet called "Master".
So I took a code from Youtube "Combine one sheet into one" and the guy who has done the video made the code to retrieve data for 3 columns.
It actually does retrieve data from my 3 columns.
function combineData() {
var masterSheet = "Master";
var ss =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(masterSheet);
var lc = ss.getLastColumn();
var lr = ss.getLastRow();
// ss.getRange(2,1,lr-1,lc).clearContent();
var labels = ss.getRange(1, 1, 1, lc).getValues()[0];
labels.forEach(function(label, i) {
var colValues = getCombinedColumnValues(label, masterSheet);
ss.getRange(2, i + 1, colValues.length, 1).setValues(colValues);
})
function getCombinedColumnValues(label, masterSheetName) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var colValues = [];
for ([i, sheet] in sheets) {
var sheetName = sheet.getSheetName();
if (sheetName !== masterSheetName && sheetName !== "UID") {
var tempValues = getColumnValues(label, sheetName);
colValues = colValues.concat(tempValues);
}
}
return colValues;
}
function getColumnValues(label, sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var colIndex = getColumnIndex(label, sheetName);
var numRows = ss.getLastRow() - 1;
var colValues = ss.getRange(2, colIndex, numRows, 1).getValues(); // for name, index =2 but replacing by colIndex says "startong column too small)
return colValues;
}
function getColumnIndex(label, sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var lc = ss.getLastColumn();
var lookupRangeValues = ss.getRange(1, 1, 1, lc).getValues()[0];
var index = lookupRangeValues.indexOf(label) + 1;
return index;
}
};
I thought the code was dynamic to the number of columns present in my Master sheet, but it isn't. My error while compiling is "The starting column of the range is too small"
Does anyone has an idea to fix the bug?
Thanks.

Get Data from all Sheets
function getDataFromAllSheets() {
var excl=['Master'];//Sheets to exclude
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Master');//data destination
sh.getRange(2,1,sh.getLastRow(),sh.getLastColumn()).clearContent();//clears old data but leaves headers
var shts=ss.getSheets();
for(var i=0;i<shts.length;i++) {
if(excl.indexOf(shts[i].getName())>-1) {//does not collected data from excluded sheets
var vA=shts[i].getDataRange().getValues();//get sht[i] data
for(var j=1;j<vA.length;j++) {//skips first line
sh.appendRow(vA[j]);//appends all rows after first line
}
}
}
}

Related

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

I have a range issue with a script

I want to create a button that moves a given number of cells from one sheet into another sheet. The starting range cell is always the same (A9) and there will be always 8 columns in total. The final row will depend on how many rows the user completes. How can I indicate this behaviour in my range code?
I think this needs to be instructed in range field, but couldn't obtain what I need.
function finishReport() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
sheet = ss.getActiveSheet(),
sheetName = sheet.getName(),
data = sheet.getDataRange().getValues();
if (sheetName == "Main") {
var range = sheet.getActiveRange(),
startRow = range.getRowIndex(),
numRows = range.getNumRows(),
numCols = range.getNumColumns()
if (numCols == 8 {
if (data.length > 1) {
var values = range.getValues(),
nextSheet = ss.getSheetByName("History record")
lastRow = nextSheet.getLastRow();
nextSheet.getRange(lastRow+1,1,numRows,8).setValues(values);
sheet.deleteRows(startRow,numRows);
}
}
}
}
I expect that all rows entered by user in tab Main are moved to tab History record. Any thoughts?
Query
You want to copy a range starting at cell A9 and stretching over 8 columns until the last row containing contents
Problem
You are using getActiveRange() which returns you only the selected (=highlighted) range - if no range is highlighted in the sheet, only the active cell will be returned as a range
Solution
Select the range manually by retrieving the last content containing row and column with
mainLastRow = sheet.getLastRow();
mainLastColumn = sheet.getLastColumn();
and defining
var numRows = mainLastRow-startRow+1;
var numCols = mainLastColumn-startColumn+1;
Full Code
function finishReport() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheet = ss.getActiveSheet();
sheetName = sheet.getName();
if (sheetName == "Main") {
var startRow = 9;
var startColumn=1;
var mainLastRow = sheet.getLastRow();
var mainLastColumn = sheet.getLastColumn();
var numRows = mainLastRow-startRow+1;
var numCols = mainLastColumn-startColumn+1;
var range = sheet.getRange(startRow,startColumn,numRows,numCols);
Logger.log(numRows);
Logger.log(numCols);
Logger.log(mainLastRow);
Logger.log(mainLastColumn);
if (numCols == 8) {
if (numRows >= 1) {
var values = range.getValues();
nextSheet = ss.getSheetByName("History record");
lastRow = nextSheet.getLastRow();
nextSheet.getRange(lastRow+1,1,numRows,8).setValues(values);
sheet.deleteRows(startRow,numRows);
}
}
}
}
Annotation
You could also retrieve the range with getDataRange(), but you would have to correct manually for the fact that dataRange starts by definition in cell A1.
I hope this works for you.
function onOpen(e) {
// Add a custom menu to the spreadsheet.
SpreadsheetApp.getUi()
.createMenu('Report')
.addItem('Finish', 'FinishReport')
.addToUi();
}
function FinishReport(){
var sp = SpreadsheetApp.getActiveSpreadsheet();
var sh = sp.getActiveSheet();
var as =sp.getSheetByName('Main').activate();
var MainRange = sp.getRange('A9:H9').activate();
var MainLastRow = sp.getLastRow();
var selection = sp.getRange('A9:H' + MainLastRow).activate();
sp.getCurrentCell().activateAsCurrentCell();
sp.setActiveSheet(sp.getSheetByName('History record'), true);
var lastRow = sp.getLastRow();
var newRow = lastRow+1;
sp.getRange('A'+newRow+':H'+newRow).activate();
sp.getCurrentCell().activateAsCurrentCell();
sp.getRange('Main!' + selection.getA1Notation()).moveTo(sp.getActiveRange());
}
REFERENCE
Spreadsheet Custom Menu
Apps Script, SpreadsheetApp

Move Rows From Multiple Tabs With a Historic Date in Column A to a Single Tab

I have a spreadsheet with multiple tabs all with a date in column A.
I need a script that I can run using a trigger each night to move all rows with a historic date in Column A to a single tab for historic rows.
I have been successful in getting historic rows in my first sheet to move to the historic tab however I cannot seem to make the script work for multiple tabs.
function HistoricDates() {
SHEET_NAME = "Area1" || "Area2" || "Area3"||"Area4";
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Sheet = ss.getSheetByName(SHEET_NAME);
var PastSheet = ss.getSheetByName("Historic Sheet");
var lastColumn = Sheet.getLastColumn();
// Check all values from sheets
for(var i = Sheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = Sheet.getRange(i, 1).getValue(); //Dates in column 1
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = Sheet.getRange(i, 1, 1, Sheet.getLastColumn()).getValues();
PastSheet.getRange(PastSheet.getLastRow() + 1, 1, 1, Sheet.getLastColumn()).setValues(rangeToMove);
Sheet.deleteRow(i);
}
}
}}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}
The expected result would be for all historic rows in Area 2,3 & 4 to move to move to the single historic tab.
My spreadsheet with script is available on the following link:
https://docs.google.com/spreadsheets/d/1WiZWok4onddTErdAxlWmU82KRSGfVJr5wi1p-rlbY5E/edit?usp=sharing
The way you defined SHEET_NAME, it will always be "Area 1". You can test this.
function test() {
SHEET_NAME = "Area1" || "Area2" || "Area3"||"Area4";
Logger.log(SHEET_NAME);
}
Instead, SHEET_NAME should be an array, and then you need to loop through that array. Below I've included a sample for how to define that array and get the sheets.
function loopThroughSpecificSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNames = ["Area1", "Area2", "Area3", "Area4"];
for (var j in sheetNames) {
var sheet = ss.getSheetByName(sheetNames[j]);
// Do other stuff
}
}
Assuming that the rest of your code works correctly (I haven't analyzed it), your modified script would look like this:
function HistoricDates() {
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastSheet = ss.getSheetByName("Historic Sheet");
var sheetNames = ["Area1", "Area2", "Area3", "Area4"];
for (var j in sheetNames) {
var sheet = ss.getSheetByName(sheetNames[j]);
var lastColumn = sheet.getLastColumn();
// Check all values from sheets
for(var i = sheet.getLastRow(); i > 0; i--) {
// Check if the value is a valid date
var dateCell = sheet.getRange(i, 1).getValue(); //Dates in column 1
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today) {
var rangeToMove = sheet.getRange(i, 1, 1, sheet.getLastColumn()).getValues();
pastSheet.getRange(pastSheet.getLastRow() + 1, 1, 1, sheet.getLastColumn()).setValues(rangeToMove);
sheet.deleteRow(i);
}
}
}
}
}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}

SORT script works for owner but not editors

I've written script to sort a google sheet by column A, and then take you to the cell next to the one you just edited, and I have it working perfectly for me the owner, but for editors, nothing seems to work starting from the "sort" function.
I assume this means some part of it is an installable trigger? but as far as I can tell I've made it all with simple triggers.
function onEdit(e) {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var cell = sheet.getActiveCell();
var NEXTcell = sheet.getActiveRange().offset(0,1);
var range = e.range;
var columnOfCellEdited = range.getColumn();
var sheet = spreadsheet.getActiveSheet();
// When Column A edited
if (columnOfCellEdited === 1) {// Column 1 is Column A
//Set marker
NEXTcell.setValue('sorting');
// Sort whole sheet excluding first 2 rows by Column A.
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).activate();
spreadsheet.getActiveRange().offset(2, 0, spreadsheet.getActiveRange().getNumRows() - 2).sort({column: 1, ascending: true});
// Find original cell after sorting
var rowoffset = 3;
var rng = sheet.getRange(rowoffset, 2, 600);
var data = rng.getValues();
var search = "sorting";
for (var i=0; i < data.length; i++) {
if (data[i][0] == search) {
data[i][0] = "";
rng.setValues(data);
var foundcell = sheet.getRange((i+rowoffset), 2);
foundcell.activate();
break;
}
}
}
}
So Stackdriver logs for-the-win.
Turns out protections I hadn't considered are the culprit.

Google script: How to move or copy multiple rows to a target sheet

I am trying to get the source rows that match my conditions to move to a target sheet. Then delete the source rows in the source sheet. Please see below code. Any help would be much appreciated.
function moveDeleteRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1 = ss.getSheets()[0];
var values = s1.getDataRange().getValues();
var activeUser = Session.getActiveUser().getEmail();
var userFullName = ContactsApp.getContact(activeUser).getFullName();
Logger.log(userFullName);
var deleted = 0; // Counter
var cell = s1.getActiveCell();
for (var i = 0; i < values.length; i++) {
if (values[i][0] == userFullName && values[i][2] == 'Shipped') {
var rowdata = values[i];
Logger.log(rowdata);
var targetSheet = ss.getSheets()[2];
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s1.getRange(1, 1, 1, s1.getLastColumn()).moveTo(targetRange);
s1.deleteRow(i + 1 - deleted);
deleted++;
}
}
SpreadsheetApp.flush();
};
This is a general code for moving range to range
function moveRange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheets()[0];
var sourceRange = sourceSheet.getDataRange();
var targetSheet = ss.getSheets()[1];
var targetRange = targetSheet.getRange(1, 1, sourceSheet.getLastRow(), sourceSheet.getLastColumn());
sourceRange.moveTo(targetRange) ;
}
https://developers.google.com/apps-script/reference/spreadsheet/range#movetotarget
You just need to select the source and target properly.
Edit: If you're moving specific rows and want to delete the rows as well then it would be better to push the target values into an array (set them all at once later using range.setValues(array[][])) and also the indexes for the rows to be deleted and then delete the rows using sheet.deleteRow(position) using another loop. While using this, you'll have to keep in mind the offset for the next row being deleted because the ones that were deleted will cause the rest to shift up.