Combine two Google spreadsheet together and keep it sync - google-sheets

I have two sheet from others and wish to create another one which combine both sheet together.
e.g.
SheetA:
Name value
A 10
B 20
SheetB:
Name value
C 30
D 40
Then I want to create one SheetC:
Name value
A 10
B 20
C 30
D 40
And if I change SheetC, I wish data synced to SheetA or SheetB automatically.

I can implement the doc combine with below script:
function runme() {
var docA = "<Doc Id A>";
var docB = "<Doc Id B>";
var sheetName = "Sheet1";
appendSheet(docA,sheetName);
appendSheet(docB,sheetName);
}
function appendSheet(docId,sheetName) {
var sourceSpread = SpreadsheetApp.openById(docId);
var sourceSheet = sourceSpread.getSheetByName(sheetName)
var activeSpread = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = activeSpread.getActiveSheet();
var activeSheetName = activeSheet.getSheetName();
sourceRng = sourceSheet.getDataRange();
sourceRows = sourceRng.getValues(),
activeSheet.appendRow(sourceRows[0]);
for (i = 1; i < sourceRows.length; i += 1) {
activeSheet.appendRow(sourceRows[i]);
}
Logger.log("total: " + i);
}
But it seems it's difficult to sync information from docC to docA and docB.

Related

Jump to a specific cell based on Active Cell

I'm completely new to Java Scipt although over the years I've messed around with VBA & Macros in Excel. I am now using Google Sheets almost exclusively, hence needing to learn Java.
I'm trying to jump to a specific cell (E5) if the current cell is (C14). This is what I've put together so far using scripts 'borrrowed' from others.
ie On entry of data in Cell C13 and pressing Enter, focus goes to Cell C14. The next data is to go into Cell E5.
function onSelectionChange(e) {
var sheetNames = ["Score Input"]; // Set the sheet name.
var ranges = ["C14"]; // Set the range to run the script.
var range = e.range;
var sheet = range.getSheet();
var check = ranges.some(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow();
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn();
var colEnd = colStart + rng.getNumColumns();
return (range.rowStart >= rowStart && range.rowEnd < rowEnd && range.columnStart >= colStart && range.columnEnd < colEnd);
});
if (check) {
jumpToDetails();
}
};
function jumpToDetails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Score Input");
var goToRange = sheet.getRange('c14').getValue();
//sheet.getRange(goToRange).activate();
SpreadsheetApp.getActive().getRange('E5').activate();
}
It worked, or did until I inserted a row in the sheet, and even though I have changed the associated cell addresses, it now doesn't work?
Two questions. 'Why has it stopped working'? and 'Is there a simpler way to do it'?
I prefer using onEdit(e) on range C13 and jump to E5
For instance here is a script that jump from one cell to the following in addresses'list
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){ // adapt
var addresses = ["C13","E5","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
if you want to be able to add rows and columns, play with named ranges (for instance ranges names 'first', 'second, 'third')
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){
var addresses = ["first","second","third"];
var values = addresses
.map(ad => myGetRangeByName(ad))
.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
function myGetRangeByName(n) {
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getA1Notation();
}
reference
class NamedRange

getRange variable range

I am trying to use a google sheet to rank a list of elements. This list is continually updated, so it can be troublesome to update the list if i already have hundreds of elements ranked and need to rank 10 new ones. Rather than having to re-rank some of the previously ranked elements every time (whether manually or using formulas), i thought it easier to write a macro that would re-rank for me.
1 - element A
2 - element B
3 - element C
new element: element D
For instance if i wanted element D to be ranked 2nd, i would need to change element B to 3 and element C to 4. This is tedious when doing hundreds of elements.
Here is my code so far but I get stuck with the getRange lines. Rankings are in column A.
function RankElements() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveCell();
var v1 = r.getValue();
var v2 = v1 + 1
var v3 = v2 + 1
var lastRow = s.getLastRow();
s.getRange(1,v2).setValue(v2);
s.getRange(1,v3).autoFill(s.getRange(1,v3+":"+1,lastRow), SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
s.getRange(1,v3+":"+1,lastRow).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
s.getFilter().sort(1, true);
};
You can do the following:
Iterate through all values in column A.
For each value, check if (1) ranking is equal or below the new one, and (2) it's not the element that is being added.
If both these conditions are met, add 1 to the current ranking.
It could be something like this:
function RankElements() {
const sheet = SpreadsheetApp.getActiveSheet();
const cell = sheet.getActiveCell();
const row = cell.getRow();
const newRanking = sheet.getActiveCell().getValue();
const firstRow = 2;
const columnA = sheet.getRange(firstRow, 1, sheet.getLastRow() - 1).getValues()
.map(row => row[0]); // Retrieve column A values
for (let i = 0; i < columnA.length; i++) { // Iterate through column A values
if (columnA[i] >= newRanking && (i + firstRow) != row) {
sheet.getRange(firstRow + i, 1).setValue(columnA[i] + 1); // Add 1 to ranking
}
}
sheet.getFilter().sort(1, true);
};

How to find the SearchImpressionShare for a particular keyword?

One could easily find the average position for a keyword using getAveragePositon() method but the same is not available for SearchImpressionShare.
EDIT
I tried to get the SearchImpressionShare by querying the data but that gives me inconsistent data.
function main() {
var keywordId = 297285633818;
var last14dayStatsQuery = "SELECT Id, SearchTopImpressionShare FROM KEYWORDS_PERFORMANCE_REPORT WHERE Id = "+keywordId+" DURING LAST_14_DAYS"
var last14dayReport = AdWordsApp.report(last14dayStatsQuery);
var last14dayRows = last14dayReport.rows();
var last14dayRow = last14dayRows.next();
Logger.log('Keyword: ' + last14dayRow['Id'] + ' SearchTopIS: ' + last14dayRow['SearchTopImpressionShare']);
}
For example, below are the two outputs I received after running the same code twice.
Output 1:
10/16/2019 10:47:29 AM Keyword: 297285633818 SearchTopIS: 0.0
Output 2:
10/16/2019 10:47:45 AM Keyword: 297285633818 SearchTopIS: 0.17
Keywords performance report provides you those data https://developers.google.com/adwords/api/docs/appendix/reports/keywords-performance-report#searchimpressionshare
sample use:
function main () {
var query = "SELECT SearchImpressionShare, Criteria FROM KEYWORDS_PERFORMANCE_REPORT WHERE Clicks > 15 DURING YESTERDAY"
var report = AdWordsApp.report(query)
var rows = report.rows()
while (rows.hasNext()) {
var row = rows.next()
Logger.log('Keyrword %s, Impressions Share %s', row['Criteria'], row['SearchImpressionShare'])
}
}
update:
please note that if you have the same keyword within several ad group you'll get aslo several rows in report, each row for each adgroup. for the whole list of keywords use the following approach:
function main() {
var keywordId = 350608245287;
var last14dayStatsQuery = "SELECT Id, SearchTopImpressionShare FROM KEYWORDS_PERFORMANCE_REPORT WHERE Id = "+keywordId+" DURING LAST_14_DAYS"
var last14dayReport = AdWordsApp.report(last14dayStatsQuery);
var last14dayRows = last14dayReport.rows();
while (last14dayRows.hasNext()) {
var last14dayRow = last14dayRows.next();
Logger.log('Keyword: ' + last14dayRow['Id'] + ' SearchTopIS: ' + last14dayRow['SearchTopImpressionShare']);
}
}
You might find it useful to add ad group parameters to your query such as AdGroupName, AdGroupId.

Conditional formatting script - set iteration

I have a custom formula for conditional formatting rules. I am trying to write a script that checks a number of values (around 50) on a column (column B on 'Mine' sheet) and if a cell is equal to a specific string (M1, M2 or M3) then the specified formula for conditional formatting is applied to the "Calendar view" sheet. The code I currently have is:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Calendar View");
sheet.getRange("C4:NC50").clearFormat();
var range = sheet.getRange("C4:NC4");
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=AND(indirect("Mine!$B5")="M1", C$2>=indirect("Mine!$C5"), C$2<indirect("Mine!$D5"))')
.setBackground("#FF0000")
.setRanges([range])
.build();
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
}
How can I enter an iteration method on the .whenFormulaSatisfied, such as:
.whenFormulaSatisfied('=AND(indirect("Mine!$B6")="M1", C$2>=indirect("Mine!$C6"), C$2<indirect("Mine!$D6"))')
.whenFormulaSatisfied('=AND(indirect("Mine!$B7")="M1", C$2>=indirect("Mine!$C7"), C$2<indirect("Mine!$D7"))')
.whenFormulaSatisfied('=AND(indirect("Mine!$B8")="M1", C$2>=indirect("Mine!$C8"), C$2<indirect("Mine!$D8"))')
............
This is the sheet I'm working on:
https://docs.google.com/spreadsheets/d/1Af84aHaG0VjXmtaWc0-uAdGFrX1LozRNLQLMatSOqgU/edit?usp=sharing
There are some challenges to the questioner's methodology - first, the dynamic identification of the start and end dates for each property, and second, the creation of up to 50 separate Conditional Formatting rules. It's well known that spreadsheet performance is affected by numbers of Conditional Formatting rules.
I'm suggesting a slightly different approach.
1) Take the data on Mine and build the Calendar.
2) Place values in the booked date fields.
3) Apply a single Conditional Formatting rule for Calendar.
The methodology for identifying which dates are booked is to insert a nominal value in the respective cells. Then the rule .whenCellNotEmpty() is applied rather than specifying a specific value. In addition, the code formats both the background as well as the font colour so that any data is hidden.
Also to note: at the beginning of the script, the code removes both the content as well as the formatting.
function so_53185335() {
// build the spreadsheet app and set source and target sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var calSheet = ss.getSheetByName("Calendar View");
var dataSheet = ss.getSheetByName("Mine");
// get the last rows and start rows for both sheets
var lrMine = dataSheet.getLastRow();
var lrCal = calSheet.getLastRow();
var dataRowStart = 5;
var calRowStart = 4;
// clear formats and data from Calendar
calSheet.getRange(calRowStart, 2, lrCal, 366).clear({
contentsOnly: true,
formatOnly: true
});
// get Mine rows with data, define the range and get data
var dataRows = lrMine - dataRowStart + 1;
//Logger.log("Mine: number of data rows "+dataRows);// DEBUG
var dataRange = dataSheet.getRange(dataRowStart, 2, dataRows, 3);
//Logger.log("data range is "+dataRange.getA1Notation());// DEBUG
var dataValues = dataRange.getValues();
//set some variables for use in loop
var i = 0; // counter
var z = 0; // counter
var calstartCol = 3; // equals first day of the year
var calrow = 0; // counter row for Calendar sheet
var calArray = [];
var masterArray = [];
// loop through the rows in Mine
for (i = 0; i < dataRows; i++) {
// test for value
if (dataValues[i][0] === "M1" || dataValues[i][0] === "M2" || dataValues[i][0] === "M3") {
//Logger.log("Match: i="+i+", value = "+dataValues[i][0]);//DEBUG
calArray = [];
masterArray = [];
calrow = calrow + 1;
// calculate the start day (as a day in the year)
var now = new Date(dataValues[i][1]);
var start = new Date(now.getFullYear(), 0, 0);
var diff = (now - start) + ((start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000);
var oneDay = 1000 * 60 * 60 * 24;
var startday = Math.floor(diff / oneDay);
// calculate the end day (as a day in the year)
var fnow = new Date(dataValues[i][2]);
var fstart = new Date(fnow.getFullYear(), 0, 0);
var fdiff = (fnow - fstart) + ((fstart.getTimezoneOffset() - fnow.getTimezoneOffset()) * 60 * 1000);
var foneDay = 1000 * 60 * 60 * 24;
var endday = Math.floor(fdiff / foneDay);
var nod = endday - startday + 1;
// assign the value for the Property
var cell = calSheet.getRange(calstartCol + calrow, 2);
cell.setValue(dataValues[i][0]);
// create an array of values for booked dates; just insert the number "1"
for (z = 1; z < nod + 1; z++) {
calArray.push(1);
}
masterArray.push(calArray);
// Assign the values for booked dates
var cell = calSheet.getRange(calstartCol + calrow, startday + 2, 1, nod);
cell.setValue(masterArray);
}
}
// create and apply a single Conditional forma rule for the data range on Calendar
var range = calSheet.getRange(calRowStart, calstartCol, calstartCol + calrow, 366);
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenCellNotEmpty()
.setFontColor("#FF0000")
.setBackground("#FF0000")
.setRanges([range])
.build();
var rules = calSheet.getConditionalFormatRules();
rules.push(rule);
calSheet.setConditionalFormatRules(rules);
}
The Calendar looks like this.

Zapier: BigCommerce to Google Sheet, New Row for Each Item

I have successfully linked my BigCommerce account to my Google Sheets (Drive) account so every time I receive a new order in my store the order is automatically exported into a Google Sheet. Unfortunately, an entire order is listed on one row with multiple items added into one cell. What I need is to have each product on its own row; for example, if someone orders three different products Zapier would create three new rows. This functionality exists when directly exporting orders from BigCommerce, but the "Zap" does not use the BigCommerce export function when pulling order information from my store to the Google Sheet.
I know this is a shot in the dark, but I am hoping someone might have a solution that I can implement. Thank you for your help!
I have created a script that perhaps could be used or modified, at least until you find if the process can be done within Zapier.
You can try the script in the following ss: https://docs.google.com/spreadsheets/d/1ggNYlLEeN3UYtZC_KlOGwpyII9CzOLKMnIOKIDrPJPM/edit?usp=sharing
The script assumes that orders arrive in the tab named Zapier. As things are set up, you would run the script through the Custom Menu.
If there are 2 orders or more, click the menu for each order.
The complete rows appear in the sheet FullList.
(if you want to play/try again, you will have to manually delete the rows in FullList once they are showing).
function processForNewOrders() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName('Zapier');
var destinationSheet = ss.getSheetByName('FullList');
var sourceValues = sourceSheet.getDataRange().getValues();
var destinationValues = destinationSheet.getDataRange().getValues();
var index = [];
destinationValues.forEach( function (x) {
index.push(x[0]);
})
var newOrders = [];
for (var y = sourceValues.length -1 ; y > 0 ; y --){
if(sourceValues[y][0].toString().indexOf('po_number') != -1 ) continue;
var i = index.indexOf(sourceValues[y][0]);
if(i != -1) break; // This readies only the fresh orders for processing
newOrders.push(sourceValues[y]);
}
Logger.log(newOrders)
for (var j = 0 ; j < newOrders.length ; j++){
var output = [];
var orderLine = newOrders[j];
Logger.log('orderLine = ' + orderLine);
var circuit = 0;
var items = 1
while (circuit < items){
var row = [];
for (var z = 0 ; z < orderLine.length; z++){
var cell = orderLine[z];
// Logger.log(cell);
var lines = cell.toString().split(',');
if(lines.length > 1) items = lines.length;
row.push(lines[circuit] ? lines[circuit] : lines[0]);
// Logger.log('row =' + row);
}
circuit ++;
Logger.log('circuit circuit circuit =' + circuit)
output.push(row);
}
}
Logger.log(output);
if(output != undefined)
destinationSheet.getRange(index.length+1,1,output.length,output[0].length).setValues(output);
}
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('Custom Menu')
.addItem('Process new order', 'processForNewOrders')
.addToUi();
}

Resources