Can I pick a cell from every Google Sheet in a Folder? - google-sheets

I want to be able to pick say C3 from a list of Google spreadsheets in a folder.
I have a bunch of structurally identical sheets, but I'd like to be able to provide a sum of the values in C3 across say a hundred sheets in a directory.
Ultimately, would be great to highlight the largest or smallest value of C3 in a directory.
This could be useful in many places where you want to be able to aggregate, aggregate data.

SUGGESTION
If you have hundreds of Google spreadsheet files in a Google Drive folder, I agree with #player0 that it is best to use a script. With the Apps Script, you can:
Automate the process in iterating through Spreadsheet files in your Drive folder.
Filter only the Google Spreadsheet type (e.g you have a bunch of
different file types inside).
Get the range data & process them the way you want.
See this sample below that was derived from existing resources:
Script:
function readSheetsInAFolder() {
//FOLDER_ID is your drive folder ID
var query = '"FOLDER_ID" in parents and trashed = false and ' +
'mimeType = "application/vnd.google-apps.spreadsheet"';
var range = "C3"; //The range to look for on every Spreadsheet files in the Drive folder
var files, pageToken;
var finalRes = [];
do {
files = Drive.Files.list({
q: query,
maxResults: 100,
pageToken: pageToken
});
files.items.forEach(sheet => {
finalRes.push(viewRangeValue(range, sheet.id));
})
pageToken = files.nextPageToken;
} while (pageToken);
const arrSum = array =>
array.reduce(
(sum, num) => sum + (Array.isArray(num) ? arrSum(num) : num * 1),
0
);
var max = Math.max.apply(null, finalRes.map(function(row){ return Math.max.apply(Math, row) })); //Gets the largest number
var min = Math.min.apply(null, finalRes.map(function(row){ return Math.min.apply(Math, row); })); //Gets the smallest number
var sum = arrSum(finalRes) // Gets the sum
console.log('RANGE VALUES: %s \nRANGE: %s \nTOTAL SHEET(s) FOUND: %s \n________________\nSUM OF VALUES: %s \nLargest Value: %s \nSmallest Value: %s',finalRes,range, files.items.length,sum,max,min)
}
function viewRangeValue(range, sheetID) {
var sid = sheetID;
var rn = range;
var parms = { valueRenderOption: 'UNFORMATTED_VALUE', dateTimeRenderOption: 'SERIAL_NUMBER' };
var res = Sheets.Spreadsheets.Values.get(sid, rn, parms);
return res.values.map(num => {return parseInt(num)});
}
Demonstration:
Sample Test Drive Folder (w/ 3 test Spreadsheet files):
Every C3 cell on each of these 3 files contain either 0,10 or 6 value.
On the Apps Script Editor, I've added the Drive & Sheets API on the services:
Result
After running the script:
Resources:
Advanced Drive Service
Drive API Files: list
Sheets API spreadsheets.values.get
Max Value of an array

Related

Importing API data via importJSON

Having a bit of trouble using importJSON for the first time in Google Sheets. My data is importing as truncated and I can't find any way to really filter things the way I'd like.
API source: https://prices.runescape.wiki/api/v1/osrs/1h
I'm using the following command: =IMPORTJSON(B1;B2)
where B1 is the source link, and B2 references any filters I've applied. So far I have no filters.
My result is a truncated list that displays as such:
data/2/avgHighPrice 166
data/2/highPriceVolume 798801
data/2/avgLowPrice 162
data/2/lowPriceVolume 561908
data/6/avgHighPrice 182132
data/6/highPriceVolume 7
data/6/avgLowPrice 180261
data/6/lowPriceVolume 37
data/8/avgHighPrice 195209
data/8/highPriceVolume 4
data/8/avgLowPrice 192880
data/8/lowPriceVolume 40
In the examples I've seen and worked with (primarily the example provided by the Addon), it will naturally pivot into a table. I can't even achieve that, which would be workable although I'm really only looking to ping the markers avgHighPrice and avgLowPrice.
EDIT:
I'm looking for results along the lines of this:
2
6
8
/avgLowPrice
162
180261
192880
/avgHighPrice
166
182132
195209
EDIT2:
So I have one more thing I was hoping to figure out. Using your script, I created another script to pull the names and item IDs
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','id','name'])
for (let p in eval('data.data')) {
try{result.push([p,data.item(p).ID,data.item(p).Name])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};
I'm wondering if it is possible to correlate the item Name with the Item ID from the initial pricing script. To start, the 1st script only list items that are tradeable, while the 2nd list ALL item IDs in the game. I'd essentially like to correlate the 1st and 2nd script to show as such
ID
Name
avgHighPrice
avgLowPrice
2
Cannonball
180261
192880
6
Cannon Base
182132
195209
Try this script (without any addon)
function prices(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/1h'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','avgHighPrice','avgLowPrice'])
for (let p in eval('data.data')) {
try{result.push([p,data.data.item(p).avgHighPrice,data.data.item(p).avgLowPrice])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};
You can retrieve informations for naming / from mapping as follows
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(["id","name","examine","members","lowalch","limit","value","highalch"])
json=eval('data')
json.forEach(function(elem){
result.push([elem.id.toString(),elem.name,elem.examine,elem.members,elem.lowalch,elem.limit,elem.value,elem.highalch])
})
return result
}
https://docs.google.com/spreadsheets/d/1HddcbLchYqwnsxKFT2tI4GFytL-LINA-3o9J3fvEPpE/copy
Integrated function
=pricesV2()
https://docs.google.com/spreadsheets/d/1HddcbLchYqwnsxKFT2tI4GFytL-LINA-3o9J3fvEPpE/copy
function pricesV2(){
var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
let myItems = new Map()
json=eval('data')
json.forEach(function(elem){myItems.set(elem.id.toString(),elem.name)})
var url='https://prices.runescape.wiki/api/v1/osrs/1h'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','name','avgHighPrice','avgLowPrice'])
for (let p in eval('data.data')) {
try{result.push([p,myItems.get(p),data.data.item(p).avgHighPrice,data.data.item(p).avgLowPrice])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};

Is it possible to use a script to enable edition in more than one sheet on Google Sheet?

I need to enable edition in more than one sheet to 60 users (using e-mail).
The user need to edit a few cells and all the other the edition need to be blocked.
Example: all the user should have the edition enable in sheets X, Y and Z, and the range of cells enabled to edition is B6:Q50. All the other cells need to be blocked and only the owned can edit.
Is it possible?
Solution:
On your main Google spreadsheet file, you can create a bound script and try this tweaked script below:
This script was based from Protect spreadsheet then unprotect specific cells and ranges with script & article of addEditors() method.
Script:
function main() { //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
addEditors();
for(var x=0; x<sheets.length; x++){
unlockCertainRanges(sheets[x]);
}
}
function addEditors(){ //Function to add editors
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var emails = [
'test1#example.com',
'test2#example.com',
'test3#example.com'
];
sheet.addEditors(emails);
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your sheets
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["B6:Q50"]; // ADD YOUR RANGES HERE TO BE EDITABLE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Sample Result:
After running the main function, the emails added on addEditors function are automatically added as editors of the file & they'll be abe to edit range B6:Q50 and the rest of the cells will be protected (only the owner of the spreadsheet file will be able to edit):
Sample Images:
Image 1: A user is not allowed to edit cell A1 (outside of B6:Q50 range on all sheets)
Image 2: User is allowed to edit cell B6 (within B6:Q50 range on all sheets)

Limiting query for one result where there is 2 values are matching

I am trying to query 2 long columns for agents' name, the issue is the names are repeated on 2 tables, one for the total sum of productivity and the other is for total sum of utilization.
The thing is when I query the columns it returns back the numbers for Productivity and Utilization all together.
How can I make the query to search only for Productivity alone and for Utilization alone?
Link is here: https://docs.google.com/spreadsheets/d/12Sydw6ejFobySHUj5JoYkAPbhr0mKoInCWxtHY1W4lk/edit#gid=0
Apps Script would be a better solution in this case. The code below works as follows:
Gets the names from Column D and Column A.
For each name of Column D, it will compare it with each name of Column A (that's the 2 for loops)
If the names coincide (first if), it will check the background color (second if) of the Column A name to accumulate Total Prod and Total Util.
Once it reaches the end of the Column A, writes the values in Total Prod and Total Util (Columns E and F) for each name in D.
function onOpen() { //Will run every time you open the sheet
//Gets the active Spreadsheet and sheet
let sprsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheet = sprsheet.getActiveSheet();
var lastRow = sheet.getLastRow();
var getNames = sheet.getRange(3, 1, lastRow).getValues(); //Names from row 2, col 1, until the last row
var totalNames = sheet.getRange("D4:D5").getValues(); //Change the range for more names
let prodColor = '#f2f4f7'; //hexadecimal codes of the background colors of names in A
let utilColor = '#cfe2f3'; //
for (var i = 0; i < totalNames.length; i++) {
var totalProd = 0, totalUtil = 0; //Starts at 0 for each name in D
for (var j = 0; j < getNames.length; j++) {
if (totalNames[i][0] == getNames[j][0]) {
if (sheet.getRange(j + 3, 1).getBackgroundObject().asRgbColor().asHexString() == prodColor) { //if colors coincide
totalProd += sheet.getRange(j + 3, 2).getValue();
} else if (sheet.getRange(j + 3, 1).getBackgroundObject().asRgbColor().asHexString() == utilColor) {
totalUtil += sheet.getRange(j + 3, 2).getValue();
}
}
}
sheet.getRange(i+4, 5, 1 ,2).setValues([[totalProd, totalUtil]]);
}
}
Note: You will have to run the code manually and accept permissions the first time you run it. After that it will run automatically each time you open the Sheet. It might take a few seconds for the code to run and to reflect changes on the Sheet.
To better understand loops and 2D arrays, I recommend you to take a look at this.
References:
Range Class
Get Values
Get BackgroundObject
Set Values
You can learn more about Apps Script and Sheets by following the Quickstart.

how can I copy data validations from one spreadsheet to another using google script

I am trying to write script that will get a cell that has Data validation from a control spreadsheet and add it to a location (same) in another spreadsheet.
I am using:
var myCell = SpreadsheetApp.getActive().getRange('D4');
var rule = myCell.getDataValidation();
but apparently the spreadsheet is not active during the script process so the rule is null.
If you can check this product forum, it was answered there.
Also it was cited in one of the SO post that this feature was currently not supported. But they gave a workaround to do it.
Copy the sheet from the original post, then follow the steps below:
Select a range of cells across which you want to copy a data
validation rule, relatively
From the Validation+ custom menu, select the appropriate option (all
references relative, columns absolute, or rows absolute)
The validation of the upper-left cell will be copied to the rest of
the range
Also, if you would like to create from scratch, I've post the same script that was specified in the SO post:
function onOpen()
{
SpreadsheetApp.getActiveSpreadsheet().addMenu
(
"Validation+",
[
{name: "Copy validation (all relative references)", functionName: "copyValidation"},
{name: "Copy validation (relative rows, absolute columns)", functionName: "copyValidationColumnsAbsolute"},
{name: "Copy validation (absolute rows, relative columns)", functionName: "copyValidationRowsAbsolute"}
]
);
}
function copyValidation(rowsAbsolute, columnsAbsolute)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var r = ss.getActiveRange();
var dv = r.getDataValidations();
var dvt = dv[0][0].getCriteriaType();
if (dvt != SpreadsheetApp.DataValidationCriteria.VALUE_IN_RANGE) return;
var dvv = dv[0][0].getCriteriaValues();
Logger.log(dvv);
for (var i = 0; i < dv.length; i++)
{
for (var j = i ? 0 : 1; j < dv[0].length; j++)
{
dv[i][j] = dv[0][0].copy().withCriteria(dvt, [dvv[0].offset(rowsAbsolute ? 0 : i, columnsAbsolute ? 0 : j), dvv[1]]).build();
}
}
r.setDataValidations(dv);
}
function copyValidationRowsAbsolute()
{
copyValidation(true, false);
}
function copyValidationColumnsAbsolute()
{
copyValidation(false, true);
}

Google script stops working after copying and authorizing

After making a copy of my perfectly working google sheet, together with the script that comes with it; the script itself does not work anymore in the copy.
I go through the steps of authorizing it as an app but it seems to hang when i run it on the copy. It times out after 540 odd seconds. It seems to be having trouble with DriveApp.createFolder (exectution time 540 seconds). Yet i have not changed anything in the script, works fine on the original one.
Here is the script, im pretty sure everything is fine here, it just doesn't want to work as soon as you make a copy of it. I need to make copies of this sheet for each person in my team but i can't at the moment.
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var date = SpreadsheetApp.getActiveSheet().getRange(3,2).getValue();
var time = SpreadsheetApp.getActiveSheet().getRange(4,2).getValue();
var site = SpreadsheetApp.getActiveSheet().getRange(2,2).getValue();
// iterate through all sheets in the spreadsheet and rename them according to cell B2
for( var j = 0 ; j < sheets.length; j++) {
var sourceSheet = sheets[j];
// get contents of cell B2
var newSheetName = sourceSheet.getRange("B2").getValue();
// rename sheet
sourceSheet.setName(newSheetName);
}
// create a folder from the named SNOWSURVEYS with date
var folder = DriveApp.createFolder('SNOWSURVEYS' + '_' + date + '_'+ site);
// append ".csv" extension to the sheet name
fileName = SpreadsheetApp.getActiveSheet().getName() + ".csv";
// convert all available sheet data to csv format
var csvFile = convertRangeToCsvFile_(fileName);
// create a file in the Docs List with the given name and the csv data
folder.createFile(fileName, csvFile);
var folderID = folder.getId();
var folderName = folder.getName();
var ui = UiApp.createApplication().setTitle('Your files have are in your google drive in a folder called ' + folder);
var p = ui.createVerticalPanel();
ui.add(p);
p.add(ui.createAnchor('Click here to go straight to your files ',folder.getUrl()));
SpreadsheetApp.getActive().show(ui)
}
Execution transcript printscreen

Resources