Copying values from Google sheet, add time stamp, prevent duplicates - google-apps-script

I'm using Kimono to scrape a site that lists active development permits. For a one off data scrape it's fine, the problem is that there is no way of sorting new data. Every time Kimono scrapes it updates the entire array.
This is what the sheet currently looks like
https://docs.google.com/spreadsheets/d/1BH8ESAHQJrog6x8nRBOpgBN-nTN1_aDY7wr8W_YYet0/edit#gid=1865015934
The first sheet is automatically populated and overwritten by Kimono. It seems like the most logical way of making this work would be to copy the values to another sheet, adding a time stamp when this happens and then preventing duplicate values from being posted.
Following this thread is was able to muster this code
I've got the copying part down with the following:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange('Building Permits!A1:D');
source.copyTo(ss.getRange('Sheet2!A1'), {contentsOnly: true});
source.clear();
}
What I am trying to figure out is how to prevent duplicates based on the URL value.
I know that it is right in front of me, but I'm drawing a blank on how to get this to work.

This Google documentation article on removing duplicates is very well written, so I won't duplicate it: https://developers.google.com/apps-script/articles/removing_duplicates
It has exactly what you need. Read the later part of the article where it talks about how to check duplicates not for the entire row, but specific columns in that row. Once you understand that, your problem is straightforward to solve. Just use 2 arrays, to hold the contents of the rows from the 2 sheets as in the example they've given. compare the first column value of the current row. if it matches, don't copy the row over.
note: this works only when you copy row-by-row into the target sheet, not the entire range as your'e doing right now. But that's unavoidable.

Related

Using setformulas to copy both values and formulas down a column

PROBLEM: In the below spreadsheet, cells C12:C17 (green) contain text imported from another spreadsheet (The "PARENT").
https://docs.google.com/spreadsheets/d/1brm0dHkXG1vxn2NQ7wGvEoayCdGYKsY4yA-MX4Jtt1w/edit#gid=396314711
Some cells have text. Some are blank. Some contain simple math (i.e 1+1), and others relational math (i.e. A1+B1). The DATA SET in the PARENT sheet are different than the CHILD sheet. I will eventually have to create a lot of CHILD sheets, each with their own unique DATA SET. The formulas on the PARENT sheet will change from time to time, so the solution isn't to just make a copy of the PARENT spreadsheet and turn it into a CHILD sheet.
I need to be able to create the formulas in the PARENT sheet, but when imported into the CHILD sheet, they need to be calculated using the data set on the CHILD sheet.
I'm trying to use a script that will take what is in the C12:C17 range, and make it an active formula in the corresponding D12:D17 cells. If the C column cell is a value, it should just put that value in there instead of making it into a formula.
I've made about a dozen attempts at the setFormulas script. All failed. At this point, I would say I'm "spit-balling", "flailing", and possibly "spiraling". The BEST I've been able to do is to get ONE cell to update (but only if a formula, not text).
I'm looking for a script that will take whatever is in cells C12:C17, and execute/evaluate them into cells D12:D17. If there are text/values in the C cell, then it should put that text/value in the corresponding D cell. If there is a formula in the C cell, it should make it execute in the corresponding D cell. There is no pattern on whether a cell will be a number, text, or formula.
I appreciate any help you can give.
FYI: The formulas/text in the C column were made by importing from the PARENT sheet. That range of cells in the PARENT sheet was made by the following formula:
=IFERROR(FORMULATEXT('P CUSTOMERS'!B12),'P CUSTOMERS'!B12)
Essentially, "If it's a formula, convert it into text. If it's not a formula , just put what is in the cell in the first place ."
I've been working on this for an embarrassing amount of time... (Not hours, not days, not weeks, but MONTHS!).
EDIT / UPDATE:
OK, Marti's scripts worked great in the example file. Moved it to another file, same conditions, and worked again.
THEN, I moved it to another file... Only difference I can tell is that it is MUCH larger.
EDIT SUMMARY:
Ran the script under these conditions.
Made table with formulas. Used FORMULATEXT on that table to convert into text. Imported that range to another sheet. Used an HLOOKUP formula to select which column I wanted to look at formulas for.
In the sample sheet (linked here: https://docs.google.com/spreadsheets/d/1cPSJMXNiKDnHCiUCGJ0iSjaoIiRLQldr1T5rClUUEm0/copy), it works.
But when I run the exact same series of events in another sheet, it fails to do anything. Process is the same, only the range is different. No other scripts on this sheet.
I trimmed down the second sheet so I can share (script still doesn't work).
https://docs.google.com/spreadsheets/d/1RRMy4RtF9CVSXw18bWg79Dh4nwHg8IN3wwhLb3QynvA/edit#gid=236899042
(Note: I can't force a copy, as this sheet requires authorizations from another file)
Here is a video better explaining the issue:
https://drive.google.com/file/d/1pjz_LilRReQlNt7p_4NhU3prtbcAhLah/view?usp=sharing
So, I'm trying to understand why it works in one, but not the other... And just as important, what can I do to make it work in 2nd sheet, which is actual goal.
You can simply use getValues together with setValues. setValues actually interprets values starting with = as formulas, and getValues actually doesn't add the ' before them. So you can simply chain them:
function computeValues() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const s = ss.getSheets()[0]
const src = s.getRange('C12:C')
const target = src.offset(0, 1)
target.setValues(src.getValues())
}
References
Range.getValues() (Apps Script reference)
Range.setValues(values) (Apps Script reference)
Range.offset(rowOffset, columnOffset) (Apps Script reference)
Martí nailed it...
function computeValues() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const s = ss.getSheets()[0]
const src = s.getRange('C12:C')
const target = src.offset(0, 1)
target.setValues(src.getValues())
}
I tested it, as written, by copy/paste. Worked 1st time.
I added in more formulas below the original range, just to see what the limits were (in cell C20), and re-ran it. Still worked.
I would have kept going in my setFormula direction for another few months. Not sure how I got it stuck in my head that was the only solution. This was the final major barrier in my project, and while I still have another few years of manual data entry and formula creation to do, THIS will be the key that makes it all work. THANK YOU!!!

getMergedRanges() array output is out of order

I have a column of merged cells and am trying to modify a specific merged cell. For some reason, the array of ranges that I get back when calling the getMergedRanges() method is out of order. Here's my code:
var column = spreadsheet.getRange('H8:H151');
var mergedCells = column.getMergedRanges();
mergedCells[0].setValue('This is a string');
Instead of setting the value of the first merged cell in the column, it would modify the 13th merged cell... Is this a bug with App Script or did I do something wrong?
Click here to see a similar example
I want to edit merged cell #2 but when I modify the output by accessing index 1, it would modify some other cell. The result is always different each time.
After looking at your sheet I understood what your problem was. This seems to only happen when the number of merged cells is high.
This does indeed look like a bug on Google's side. So I have reported a bug on your behalf. You can click on the +1 to give it more attention to be solved quickly.

insertRowBefore the last Row returning error

I have a script that (gross, I know) writes to a spreadsheet. It dynamically adds and deletes rows based on input data from another sheet. I like doing this because the user has to copy and paste the resulting data, formatted into a table, into an email after (Let's skip creating an email directly from this script for now, that's part two).
I figured the easiest way to let the script know where to paste new data is to always insert a row before the very last row of the spreadsheet and paste said data.
I have been using getMaxRows(), getLastRow(), and insertRowBefore(), but all combos of these functions returns an "those rows are out of bounds" error!
I really don't understand what's going on. I use logs and see the variables getLastRow() and getMaxRows both get updated after I've done some copying of a template table to the sheet which then "inserts" rows. For some reason, even after re-calling the getMaxRows() and getLastRow() functions, they don't recognize the new rows. The script can still manipulate these new rows.
This leads me to believe getMaxRows() and getLastRow() can only get info about the number of rows in the sheet only when the script first runs, or only when the sheet is first activated? Has anyone faced this issue before? Does anyone have details about how those two functions work? The GAS 'sheet' class page didn't help.
spread = SpreadsheetApp.getActiveSpreadsheet();
emailsSheet = spread.getSheetByName("emails");
emailsMaxRows = emailsSheet.getMaxRows(); //# of rows regardless of content.
emailsSheet.deleteRows(2,emailsMaxRows-1); //I want to start from scratch each time I run the script, having just 1 row from which I append 'template' boxes where I can put data for the user to copy and paste into emails.
emailsMaxRows = emailsSheet.getMaxRows(); //now that I've deleted, Logger.log() shows that getMaxRows() correctly updates with just 1 row.
emailsSheet.insertRowsBefore(emailsMaxRows, 2);//insert two more, so there are 3 rows in the sheet.
emailsMaxRows = emailsSheet.getMaxRows(); //logger correctly shows 3
templateEmail = spread.getRangeByName("templateEmail"); //email template, A1:E5
templateEmail.copyTo(emailsSheet.getRange(emailsMaxRows,1)); //paste a table of 5 rows to row 3, so there are 7 total.
emailsMaxRows = emailsSheet.getMaxRows(); //should be 7, no?
//NO. Logger shows MaxRow is still 3, how come????? I can visibly see in the sheet there are 7 rows, with formatting and text. I simply don't understand how getMaxRows() doesn't update to return 7.
I need either getMaxRows() or getLastRow() to correctly update each time I delete or insert a row, why can't they do that for me?
SpreadsheetApp.flush() fixed the problem.

Hard link cells in script editor for Google Spreadsheet

When in Google Spreadsheet, you have formula's referencing cells to make for example a simple sum, Spreadsheets will automatically update the references, should the cells be moved. For example if the cells where cut and pasted to a different location, or rows/columns where added somewhere.
I've got this Spreadsheet where I refresh some data in using REST API's in the Spreadsheet script editor. This one runs every 5 minutes or so. However if the cells in which the data needs to be inserted, move around, my script breaks. So I have a list of constants with all kinds of cell names, for example:
/* --- EXCHANGE RATES --- */
var CELL_BTC_EUR = "B3";
var CELL_ETH_EUR = "B4";
var CELL_BNK_EUR = "B5";
But I need to update these every time I move something to optimize the spreadsheet. Is there a way to hard link it to a specific cell so that they automatically update similar to in cell formula's? Maybe not with these constant but with lines like:
sheet.getRange(CELL_BTC_EUR).setValue(btceur.last);
Potential solution
I make a seperate sheet with all my "dynamic" data which is refreshed via Spreadsheet script. In my main sheet, I can reference those cells. Thus if in my mainsheet things start moving, the reference to my other sheet stay the same. I can then hide/protect the Script Sheet.
I'm still gonna make a small table in my mainsheet with my currency exchange rates, just to get an overview of everything. But my references would not break anymore everytime I insert a new coin or something.
(better solutions are welcome. :) )

Workaround for not losing cell's reference because of user doing sorting, while script checks cells value

I have been struggling with the following situation for almost the whole last week and it would be awesome if someone could give me some hint.
The situation:
1. Script finds a particular value in, lets say, 'Sheet1', and gets the row number of the cell containing this value.
2. Since script has found this value, it executes a bunch of actions like creating new spreadsheet and copying numerous 'Sheet1' from dozen other spreadsheets to this newly created spreadsheet, and comparing/ analyzing data.
The problem.
- While script is doing a bunch of other actions, user is able to rearrange cells by, for example, sorting A to Z, which changes the address of previously found cell.
Here is the code that I used in order to verify this:
function WhatHappensIfUserSorts () {
var ss = SpreadsheetApp.getActive();
var sh = ss.getActiveSheet();
var rng = sh.getRange("B17"); //Lets say that script finds this cell according to some rules
Utilities.sleep(10000);
rng.setValue("Test Value");
}
Question:
Might there be any workaround for this?
My current ideas.
1. I was thinking about hiding the filter row in the beginning of the script, but this doesn't help a lot, because users can insert new row in the which will change the addresses of the rows below.
The background.
I am trying to create two way synchronization, meaning, each project member has his/ her own spreadsheet with 'Project X', 'Project Y' etc. sheets and no matter who updates their project sheets, all other users that work on the same project get these updates in their project sheets. These updates that have to be tracked are not just the cell values, these are cell notes as well. And this is the reason why script has to do the bunch of other actions, since CopyTo method does not work between spreadsheets.
During my research I found sheetSpider project, but it seems somewhat different and too complicated from what I need.
A simple suggestion would be to give each row a unique identifier so that you could use it to evaluate the target range again before you write back to the sheet.
get target row's unique ID --> do work --> locate target rows ID and use to determine write range --> write back to sheet.
Alternatively, during the operation you could delete the target row and then use appendRow() to drop the updated version back in.
A third and final suggestion might be to temporarily suspend the permissions for the sheet. See: https://developers.google.com/apps-script/reference/spreadsheet/page-protection#setProtected