I'm trying to leave a blank column in between data of an appended row, so that I can add a VLOOKUP function, functionally I don't need to do this (I could just add the VLOOKUP to the end of the row), but aesthetically it would be better if I could. Is there a way to just skip a column when appending data into a row?
Explanation:
You can use two separate setValues statements to achieve your goal and paste the data right after the last row with content with getLastRow().
I have fully described each block of lines in the following script so I think it is straightforward to work with it.
I tried to create a generic code where you can select:
the column you want to separate the data: sep_col
the starting column you want to paste the data: start_col
the number of columns you want to have between the two different datasets: space_col
Code snippet:
function myFunction() {
// get spreadsheet details
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1'); // change that to the name of your sheet
// provide some data
const sep_col = 3; // column you want to separate the data
const data = ["d1","d2","d3","d5","d6"];
const data1 = data.slice(0,sep_col);
const data2 = data.slice(sep_col,data.length);
// get current last row of document
const Lrow = sh.getLastRow();
// paste the data to separate ranges
const start_col = 1;
const space_col = 1;
sh.getRange(Lrow+1,start_col, 1, data1.length).setValues([data1]);
sh.getRange(Lrow+1,start_col+data1.length + space_col, 1, data2.length).setValues([data2]);
}
JavaScript References:
Array.prototype.slice()
Sheet used for the code snippet:
Related
I'm looking to copy the cells highlighted in particular color to the different sheet in one column.
Sample doc: https://docs.google.com/spreadsheets/d/1Xa_WKlmHO5mT688zM0O-LXlqnSbITG2YK1uLPq5XzcA/edit#gid=328987545
Can you help me with the script to get desired output?
I believe your goal is as follows.
You want to retrieve the values of the highlighted cells (From your provided Spreadsheet, it's #ffff00), and want to put the values to the column "A" of destination sheet.
You want to achieve this using Google Apps Script.
In this case, how about the following sample script?
Sample script:
function myFunction() {
const srcSheetName = "Sheet1"; // This is from your sample Spreadsheet.
const dstSheetName = "Ouput"; // This is from your sample Spreadsheet.
const checkColor = "#ffff00"; // This is from your sample Spreadsheet.
// Retrieve sheets.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [src, dst] = [srcSheetName, dstSheetName].map(s => ss.getSheetByName(s));
// Retrieve source values and backgrounds.
const srcRange = src.getDataRange();
const backgrounds = srcRange.getBackgrounds();
// Create an array for putting to the destination sheet.
const values = srcRange.getValues().reduce((ar, r, i) => {
r.forEach((c, j) => {
if (backgrounds[i][j] == checkColor) {
ar.push([c]);
}
});
return ar;
}, []);
// Put the array to the column "A" of the destination sheet.
dst.getRange(1, 1, values.length).setValues(values).setBackground(checkColor);
}
When this script is run, the values are retrieved from the highlighted cells of the source sheet. And, the retrieved values are put to the column "A" of the destination sheet.
From your sample Spreadsheet, the destination sheet has the highlighted cells. So, I added setBackground(checkColor). If you don't want to set the color, please remove it.
References:
getBackgrounds()
getValue()
setValues(values)
reduce()
I am trying to write an App Script that takes string data from multiple different spreadsheets (completely separate documents) and puts them all in a new spreadsheet. When I run the logger, it shows me all the data I want. I want each piece of data to show up in Column A, but when I run my script, it only puts 1 data point in the spreadsheet instead of all of them. Can someone give me some guidance? Here is my code:
function pullTogether() {
var files = DriveApp.getFolderById('Folder ID').searchFiles('title != "nothing"');
const rangeName = 'Sheet1!B2:C';
while(files.hasNext()){
var xFile = files.next();
var name = xFile.getId();
const values = Sheets.Spreadsheets.Values.get(name, rangeName).values;
for (const row in values) {
var a1 = (values[row][0]);
Logger.log(a1);
var ss = SpreadsheetApp.openById("ID of new spreadsheet"); //I have the real ID in my code
var cell = ss.getRange("A2");
cell.setValue(a1);
}
}
}
I believe your goal is as follows.
You want to retrieve the values from the column "B" of each Spreadsheet under the specific folder.
You want to put the retrieved values to the column "A" of the destination sheet.
Modification points:
About but when I run my script, it only puts 1 data point in the spreadsheet instead of all of them., when I saw your script, the retrieved value is always put to the cell "A2" of the destination sheet. I think that this might be the reason for your issue.
In your script, I thought that when the following flow is used, the process cost will become low. By this flow, your issue can be also removed.
In your situation, even when Sheets API is not used, the script might work using getValues().
When these points are reflected in your script, it becomes as follows.
Modified script:
Please set the folder ID and the destination Spreadsheet ID.
function pullTogether() {
// Retrieve values from each Spreadsheet.
var values = [];
var files = DriveApp.getFolderById('Folder ID').searchFiles(`title != 'nothing' and mimeType='${MimeType.GOOGLE_SHEETS}'`);
var sheetName = 'Sheet1'
while (files.hasNext()) {
var xFile = files.next();
var sheet = SpreadsheetApp.open(xFile).getSheetByName(sheetName);
if (sheet) {
var v = sheet.getRange("B2:B" + sheet.getLastRow()).getValues();
values = [...values, ...v];
}
}
// Put values to the destination sheet.
var ss = SpreadsheetApp.openById("ID of new spreadsheet"); //I have the real ID in my code
var dstSheet = ss.getSheets()[0];
dstSheet.getRange(2, 1, values.length, values[0].length).setValues(values);
}
Note:
Although I'm not sure about your actual situation, when the above script didn't work by the large data, please modify as follows.
From
dstSheet.getRange(2, 1, values.length, values[0].length).setValues(values);
To
Sheets.Spreadsheets.Values.update({ values }, ss.getId(), `'${dstSheet.getSheetName()}'!A2`, { valueInputOption: "USER_ENTERED" });
References:
getValues()
setValues(values)
My question is similar to Need script to loop through stock ticker list, copy resulting output, and paste output to separate sheet within workbook. However, instead of copying and pasting a single row (for each ticker) below the last non-empty row, I need to copy and paste 100 rows (for each ticker) below the last non-empty row.
I have three sheets named Tickers , Data, and Results within a single workbook.
If done manually, the job would be the following: Enter a stock symbol into cell A2 in the Data sheet. The sheet then retrieves the historical time series data from google finance, runs formulas and returns 100 rows and 7 columns of data (Ticker, Date, Open, High, Low, Close, Volume) into range A5:G254 within the same Data sheet. Copy the 100 rows of returned data and paste the data on the Results sheet below the last non-empty row. Then repeat the process with each ticker.
Request: Create a script to loop through a list of 50 stock symbols from the Ticker sheet (range B2:B51), paste 1 symbol at a time into cell A2 in the Data sheet, wait for google finance API to run, and then copy all the resulting rows generated in range A5:G254, and pasting those results into the Results sheet, below the data generated from previous ticker, until there is a "long format" table of time series data of each of the 50 tickers.
The issue with my current script is that it pastes only the first row of the wanted range A5:G254.
function getNewPrices() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tickerSheet = ss.getSheetByName('Tickers');
const dataSheet = ss.getSheetByName('Data');
const resultsSheet = ss.getSheetByName('Results');
let data;
let myListOfTickers = tickerSheet.getRange('B2:B51').getValues().filter(String).sort();
myListOfTickers.forEach(ticker => {
dataSheet.getRange('A2').setValue(ticker[0]);
data = dataSheet.getRange('A5:G254').getValues()[0];
pasteData(resultsSheet, data);
});
};
function pasteData(resultsSheet,data){
let nextRow = resultsSheet.getLastRow() + 1;
data.forEach((datum,index) => {
resultsSheet.getRange(nextRow,index + 1,1,1).setValue(datum);
});
};
I'm not completely sure but I think this is what you want
function getNewPrices() {
const ss = SpreadsheetApp.getActive();
const tsh = ss.getSheetByName('Tickers');
let tvs = tsh.getRange('B2:B51').getValues().filter(String).sort();
const dsh = ss.getSheetByName('Data');
const rsh = ss.getSheetByName('Results');
dsh.getRange(2,1,tvs.length,tvs[0].length).setValues(tvs);
let dvs = dsh.getRange('A5:G254').getValues();
rsh.getRange(rsh.getLastRow() + 1, 1, dvs.length, dvs[0].length).setValues(dvs);
}
You can goto to Google Apps Script Reference and using the search box find any function that you don't understand. If it's a pure JavaScript function the go here
Problem solved. Here is the final code if anyone wants to use it later on. Thank you for the initial code Cooper.
function getNewPrices() {
const ss = SpreadsheetApp.getActive();
const tsh = ss.getSheetByName('Tickers');
let tvs = tsh.getRange('B2:B51').getValues().filter(String).sort();
const dsh = ss.getSheetByName('Data');
const rsh = ss.getSheetByName('Results');
tvs.forEach(ticker =>{
dsh.getRange('A2').setValue(ticker[0]);
let dvs = dsh.getRange('A5:G254').getValues();
rsh.getRange(rsh.getLastRow() + 1, 1, dvs.length, dvs[0].length).setValues(dvs);
});
}
I do SEO, and therefore I have a lot of keywords flowing around in different spreadsheets. I'd like a way to filter these into seperate sheets based on specific filters, but I can't for the life of me, figure out how to do this in Google Apps Script.
Criteria I set myself for this to work out:
A list of strings and their corresponding volumes are entered in column 1+2.
A list of filter-words are written in column 3.
The script has to create a new sheet for each of the filter words and move the strings + volumes into these different sheets if the string contains a filter word.
Example:
Filter words: Apple, Banana, Pineapple
String: "The Apple Was Big", Volume: "100"
The script would move the string and volume into the sheet called "Apple" on row 1
(Beware, I'm in no means experienced in coding)
I believe you can use the following structure:
for(let i = 0; i <= column3RowAmount; i++){ //Run as long as there are more filter words
create(column3Row[i]); //create a new sheet with the name of the filter word
for(let j = 0; j <= column1RowAmount; j++){ //Run as long as there are more keywords
if(column1Row[j].indexOf(column3Row[i]) >= 0){ //If the Row in column 1 contains the filter word
column1Row[j].moveToSheet(column3Row[i]); // Make sure not to move Column 3, but only 1+2
}
}
}
Example sheet: https://docs.google.com/spreadsheets/d/15YIMyGmmfZdy094gwuJNxFmTd8h7NOLnA8KevZrGtdU/edit?usp=sharing
Explanation:
Your goal is to create a sheet for every filter-word in column C. Then copy the data in columns A, B but only the rows that include the filter-word to the corresponding sheet.
For starters, you need to get the filter-word list. You can get the full range of column C and filter out the empty cells:
const sh_names = sh.getRange('C1:C').getValues().flat().filter(r=>r!='');
Similarly, you need to get the data in columns A and B:
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
The next step is to iterate over sh_names and for every element / filter-word, check if a sheet with that name exists. If it does not exist, then create a sheet with that name, if it exists then skip the creation part:
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
The next step is to filter data on the rows that include the filter-word:
let f_data = data.filter(r=>r[0].includes(s));
Finally, check if the length of the data is bigger than 0, otherwise there is not data to use and set the values of data to the corresponding sheet:
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data)
Solution
function myFunction() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Ark1');
const filter_sh = ss.getSheetByName('Filter');
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
const sh_names = filter_sh.getRange('A1:A'+filter_sh.getLastRow()).getValues().flat();
sh_names.forEach(s=>{
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
let sheet = ss.getSheetByName(s);
let f_data = data.filter(r=>r[0].includes(s));
if(f_data.length>0){
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data);}
});
}
This function will place all of your results into column 4 next to the appropriate word rather than creating a page for each word. So it runs much faster.
function stringswords() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const sr=2;
const rgd=sh.getRange(sr,1,sh.getLastRow()-sr+1,2);
const data=rgd.getDisplayValues();
const rgw=sh.getRange(sr,3,sh.getLastRow()-sr+1,1);
const words=rgw.getDisplayValues().flat();
const wiObj={};
words.forEach(function(w,i){wiObj[w]=i});
const rgr=sh.getRange(sr,4,sh.getLastRow()-sr+1,1);
rgr.clearContent();
var results=rgr.getValues();
words.forEach(function(w,i,A){
data.forEach(function(r,j,D) {
if(data[j][0] && data[j][0].indexOf(w)!=-1) {
results[wiObj[w]][0]+=Utilities.formatString('String:%s Vol:%s\n',data[j][0],data[j][1]);
}
});
});
rgr.setValues(results);
}
Image of Data and output:
I want to compare two sheets (based on header values in row 1) and delete any column with a unique value (without a match). For example, Assuming Sheet1, Row 1 data and Sheet 2, Row 1 are uniform, if a user adds/deletes a column within any sheet, I want to always match the number of columns in both sheets with their values
Screenshots of sheets headings.
IF both sheets looks like this
And a user adds a new Column N
Or delete column N
How can I ensure that both sheet matches by deleting the odd/distinct column in Sheet 1?
I have tried modifying this code below but I can't just get the unique one out. This code only look for headers with a defined value.
function deleteAloneColumns(){
var sheet = SpreadsheetApp.getActiveSheet();
var lastColumnPos = sheet.getLastColumn();
var headers = sheet.getRange( 1 ,1, 1, lastColumnPos ).getValues()[0];
for( var i = lastColumnPos ; i < 1; i--){
if( headers[i] === "alone" ) sheet.deleteColumn(i);
}
SpreadsheetApp.getUi().alert( 'Job done!' );
}
Any help to compare and delete the column with the unique value will be appreciated.
Problem
Balancing sheets based on header row values mismatch.
Solution
If I understood you correctly, you have a source sheet against which validation is run and two primary use cases: user adds a new column named differently than any other column (if you want to check that the column strictly matches the one in sheet1, it is easy to modify) in source sheet or deletes one that should be there.
const balanceSheets = (sourceShName = 'Sheet1',targetShName = 'Sheet2') => {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const s1 = ss.getSheetByName(sourceShName);
const s2 = ss.getSheetByName(targetShName);
const s2lcol = s2.getLastColumn();
//keep all vals from source to reduce I/O
const s1DataVals = s1.getDataRange().getValues();
const s2Vals = s2.getRange(1, 1, 1, s2lcol).getValues();
const h1Vals = s1DataVals[0];
const h2Vals = s2Vals[0];
//assume s1 is source (validation) sheet
//assume s2 is target sheet that a user can edit
//case 1: target has value not present in source -> delete column in target
let colIdx = 0;
h2Vals.forEach(value => {
const isOK = h1Vals.some(val => val===value);
isOK ? colIdx++ : s2.deleteColumn(colIdx+1);
});
//case 2: target does not have values present in source -> append column from source
h1Vals.forEach((value,index) => {
const isOK = h2Vals.some(val => val===value);
!isOK && s2.insertColumnAfter(index);
const valuesToInsert = s1DataVals.map(row => [row[index]]);
const numRowsToInsert = valuesToInsert.length;
s2.getRange(1,index+1, numRowsToInsert,1).setValues(valuesToInsert);
});
};
Showcase
Here is a small demo of how it works as a macros:
Notes
Solving your problem with two forEach is suboptimal, but I kept number of I/O low (it can be lowered further by, for example, moving deleteColum out of the loop while only keeping track of column indices).
The script uses ES6 capabilities provided by V8, so please, be careful (although I would recommend migrating as soon as possible - even if you encounter bugs / inconsistencies , it is worth more than it costs.
UPD made script more flexible by moving sheet names to parameter list.
UPD2 after discussing the issue with deleteColumn() behaviour, the answer is updated to keep column pointer in bounds (for those curious about it - forEach kept incrementing the index, while deleteColumn reduced bounds for any given index).
Reference
insertColumnAfter() method reference