Question Updated for Clarity
I have the below formula running as intended. However I would like to limit the results found in A2:C to 2 of each SKU found. the below table has A11134 which has 4 results, but I want to only see the first 2. so I would see (A11134 E01D1 48) & (A11134 F06C2 48)
function PreparePrepPivotTable() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('PrepSheet');
sheet.getRange('G2').activate().setFormula('=QUERY({\'LOCATIONS\'!$A$2:$C},"SELECT * WHERE Col1
MATCHES \'" & JOIN("|",FILTER(O2:O, NOT(ISBLANK(O2:O)))) & "\' ")');
}
Thanks In Advance
NEW ANSWER
Ok in this case I would suggest using a script to obtain as much as SKU you want.
First you use this function to filter the O coulmn and get your matching set. This set will contain a counter initially set to 0.
function GetNSKUS() {
var ss =SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('PrepSheet');
//Building the matching set.
var matchingValues = ss.getRange("O2:O").getValues().filter(value => value[0] != '').map(nonBlankValue => [nonBlankValue[0], 0]);
//Filtering out the desired number of values
var values = sheet.getRange("LOCATIONS!$A$2:$C").getValues().filter(value => advancedFilter(value[0], matchingValues));
let cols = values[0].length;
let rows = values.length;
//Printing out the found rows starting from G2
sheet.getRange(2, 7, rows, cols).setValues(values);
}
You will then filter your LOCATION Sheet range with this function. When a match is done the counter will increment allowing you to filter up to the desired values.
function advancedFilter(value, toMatch) {
let matched = toMatch.filter(couple => couple[0] === value);
if (matched.length > 0) {
if (matched[0][1]<2) { //Here you can decide when to stop returning SKUs
matched[0][1] += 1;
return true;
}
}
return false;
}
OLD ANSWER
You can use limit to reduce your query output. In your case if you want only to return the first 2 rows matching your query you can use the following:
function PreparePrepPivotTable() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('PrepSheet');
sheet.getRange('G2').activate().setFormula('=QUERY({\'LOCATIONS\'!$A$2:$C},"SELECT * WHERE Col1 MATCHES \'" & JOIN("|",FILTER(O2:O, NOT(ISBLANK(O2:O)))) & "\' LIMIT 2")');
}
References:
Limit
Related
I am copying data from a spreadsheet titled after the specific month and placing it in my main spreadsheet. I have successfully copied the data into range K80:K94 on my Daily Hub sheet.
In range K80:K94 I now want to add a checkbox in column M if there is a value in column K. For example if there is a value in K80 and K81 there would be a checkbox in M80 and M81. I feel like this should be fairly straightforward, however I have tried a few different options including using IsBlank() and nothing seems to be working.
function dailyhubhabits() {
var montha = new Array(12);
montha[0] = "JANUARY";
montha[1] = "FEBRUARY";
montha[2] = "MARCH";
montha[3] = "APRIL";
montha[4] = "MAY";
montha[5] = "JUNE";
montha[6] = "JULY";
montha[7] = "AUGUST";
montha[8] = "SEPTEMBER";
montha[9] = "OCTOBER";
montha[10] = "NOVEMBER";
montha[11] = "DECEMBER";
var dailyhabitshubmonth = new Date();
var getdhmonth = montha[dailyhabitshubmonth.getMonth()];
Logger.log(getdhmonth);
var mhs = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(getdhmonth);
var monthhabitsogdata = mhs.getRange("C56:E70");
var gethabits = monthhabitsogdata.getValues();
Logger.log(gethabits);
var dhs = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DAILY HUB");
var habitsarea = dhs.getRange("K80:K94");
monthhabitsogdata.copyTo(habitsarea);
//THIS IS WHERE I AM HAVING TROUBLE
var datavalues = dhs.getRange("K80:K94").getValues();
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0].length != 0) {
dhs.getRange(i+1,14).insertCheckboxes();
}
}
}
You want to insert a checkbox on Column M when there is a value in the same row of column K.
There are two problems with this part of your script:
evaluating whether the cell has a value
defining the target range for the checkbox
Does the cell have a value?
length returns the number of records in an array, but it is not a good method for determining whether a cell contains a value. This is a popular topic; you might care to read Google Spreadheets Scripts: check if cell is empty for several methods.
a better approach is !== ""
Defining the target cell
dhs.getRange(i+1,14).insertCheckboxes(); - there are two problems here
Column M is 13
i starts at zero, so the first range value would be .getRange(1,14) = Cell N1.
so you need a variable that defines the startRow, such as:
var startRow = 80
REPLACE
//THIS IS WHERE I AM HAVING TROUBLE
var datavalues = dhs.getRange("K80:K94").getValues();
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0].length != 0) {
dhs.getRange(i+1,14).insertCheckboxes();
}
}
WITH
var startRow = 80
var endRow = 94
var datavalues = dhs.getRange("K"+startRow+":K"+endRow).getValues()
var data_leng = datavalues.length;
for(var i=0; i<data_leng; i++) {
if(datavalues[i][0] !=="") {
dhs.getRange(i+startRow,13).insertCheckboxes()
}
}
SUGGESTION
In my understanding, here's your goal:
Check values in K80:K94
Insert a checkbox on a row in M that is adjacent to a row that isn't empty in the K80:K94 range.
Perhaps you could try this sample script to replace your current line on the section in inserting the check-boxes:
/** SUGGESTION
* 1. Iterate through the values in range K80:K94 & identify which aren't empty.
* 2. Get each non-empty values' row numbers.
* 3. To reduce runtime execution in the loop, if there are consecutive non-empty values, set them as a range (e.g. M80:M81). Otherwise a single value will be set as a single range (e.g. M83);
* 4. Iterate through these ranges & insert the checkboxes.
*/
var range = SpreadsheetApp.getActive().getRange('K80:K94');
var temp_values = range.getValues().map((x, i) => x != '' ? [x, (range.getLastRow() - (range.getNumRows() - i) + 1)].flat() : '*');
var ranges = temp_values.join().split('*').map(y => (y.replace(/[a-zA-Z,]+/g, '-')).split('-').filter(x => x != ''));
ranges.map(z => [...new Set([z[0], z[z.length - 1]])]).forEach(
row => row.length > 1 ? SpreadsheetApp.getActive().getRange(`M${row[0]}:M${row[1]}`).insertCheckboxes() :
SpreadsheetApp.getActive().getRange(`M${row[0]}`).insertCheckboxes()
);
/** End */
This sample script runs faster vs your current implementation as it shortens the data to be processed in the loop
Demo
Sample sheet
After running the script
I am doing a kind of VLOOKUP operation in a column with about 3K cells. I am using the following function to do it. I commented on what the code is doing in the function, but to summarize:
It creates a map from values to search for from a table with metadata
It iterates each value of a given range, and searches for coincidences in the previous map
If coincidences are found, it uses the index to capture the second column of the metadata table
Finally, sets the value captured in another cell
This is the code:
function questions_categories() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("data_processed");
// get metadata. This will work as the table to look into
// Column B contains the matching element
// Column C contains the string to return
var metadata = ss.getSheetByName("metadata").getRange('B2:C').getValues()
// Just get the different values from the column B
var dataList = metadata.map(x => x[0])
// Used to define the last cell where to apply the vlookup
var Avals = sheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// define the range to apply the "vlookup"
const questions_range = sheet.getRange("Q2:Q" + Alast);
forEachRangeCell(questions_range, (cell) => {
var searchValue = cell.getValue();
// is the value to search in the dataList we defined previously?
var index = dataList.indexOf(searchValue);
if (index === -1) {
// if not, throw an error
throw new Error('Value not found')
} else {
// if the value is there, use the index in which that appears to get the value of column C
var foundValue = metadata[index][1]
// set the value in two columns to the right
cell.offset(0, 2).setValue(`${foundValue}`);
}
})
}
forEachRangeCell() is a helper function to iterate through the range.
This works very well, but it resolves 3-4 cells per second, which is not very efficient if I need to check thousands of data. I was wondering if there is a more performant way to achieve the same result.
To improve performance, use Range.setValues() instead of Range.setValue(), like this:
function questions_categories() {
const ss = SpreadsheetApp.getActive();
const source = { values: ss.getRange('metadata!B2:C').getValues() };
const target = { range: ss.getRange('data_processed!Q2:Q') };
source.keys = source.values.map(row => row[0]);
target.keys = target.range.getValues().flat();
const result = target.keys.map(key => [source.values[source.keys.indexOf(key)]?.[1]]);
target.range.offset(0, 2).setValues(result);
}
See Apps Script best practices.
I am using Google ads script to check if a certain text is already exist in my spreadsheet file.
If not, I want the function to return -1, if exist return the row it was found in.
I am using a loop to run on all the cells in the column and check each one.
In order to test it, I took of the cell's data and run the test, but it didn't find them equal...
here is the log for row #5:
row # 5 contains : mobileapp::2-com.rustybrick.shabbat but we look for : mobileapp::2-com.rustybrick.shabbat
enclose the script I use:
function findInColumn(column, data)
{
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var SHEET_NAME = 'גליון 1';
var ss = SpreadsheetApp.openByUrl(SPREDSHEET_FILE);
var sheet = ss.getSheetByName(SHEET_NAME);
var column = sheet.getRange(column + ":" + column);
var lastRow = sheet.getLastRow() + 1;
var data1 = data;
var values = column.getValues();
var row = 0;
while ( values[row] !== data1 && row < lastRow )
{
row++;
if(values[row] === data1)
{
return row;
}
else
{
Logger.log("row # " + row + " contains : " + values[row] + " but we look for : " + data1);
}
}
return -1;
}
So, my two questions are:
Why the script didn't recognize it's a match?
Is there a better or quicker way to do this search? now there are only 1K rows in the file, but it will become much higher soon
Findings
After replicating your script, here's what I have found:
The var values = column.getValues() will only contain the the value on row 1 of the column because column = sheet.getRange(column + ":" + column); will only get value of range Row 1, Column 1 (e.g. the column paramter is set as 1). Thus, there's no match found.
Solution
If I understand it correctly, here are your goals:
Check & find a match on each cells of a column
Return the row # of the matched cell OR return -1 if there's no match found
Find a better or quicker way to do the search
You may try this simpler script:
A switch statement was used on the sample script instead of an if statement as it is more efficient, given you will have more than 1k of total rows. See
function findInColumn(column, data)
{
var ss = SpreadsheetApp.openByUrl(SPREDSHEET_FILE_URL).getSheetByName('גליון 1');
var total_rows = ss.getDataRange().getValues().length;
var result = -1;
Logger.log("==================\nCHECKING COLUMN #"+column + "\n==================\nDATA TO FIND:\n"+data+"\n==================");
for(row=1; row<=total_rows; row++){
var currentRowValue = ss.getRange(row,column).getValue();
switch(currentRowValue) {
case currentRowValue = data:
Logger.log("Found a match on row #"+row);
result = row;
break;
}
}
return result;
}
Test Results
1. Sample גליון 1 Sheet with test values on Column 1:
2. Ran the findInColumn(column, data) with 1 &
"mobileapp::2-com.rustybrick.shabbat" as parameter values on a testing function and the result returned row 3.0 :
3. If there will be no match found, the returned result will be -1.0:
This is what I want to do:
I have a source sheet called "BD Visitas".
This sheet contains some data, which will later be the data that I want to copy to another sheet.
I have a target sheet called "Ficha mascota".
This sheet contains a range of rows where I want to paste the data from the source sheet "BD Visitas".
The issue is the following:
On the destination sheet, in cell "D18" the name of the pet is written.
Take the value of this cell, perform a search in column B of the source sheet "BD Visitas" and look for the name matches.
After finding the matching rows, get the last 15 matches. That is, from the last row to the first row (from bottom to top).
When you get the last 15 matching rows, then copy the columns C, G, H, I (source sheet) and paste the values in the range of rows 39 to 53, in the columns C, D, G, M of the destination sheet "Ficha mascota".
However, it must be copied in reverse order, that is, the last match of the source sheet must be copied to row 39 of the destination sheet. The penultimate match of the source sheet must be copied to row 40 of the target sheet. And so on. Therefore, in row 53 of the target sheet the first match within the range of 15 matches will be pasted.
The work done that I have is the following:
function get15lastRowsBDvisitasToFichaMascota() {
const libro = SpreadsheetApp.getActiveSpreadsheet();
const hojaOrigen = libro.getSheetByName('BD visitas');
const hojaDestino = libro.getSheetByName('Ficha mascota');
// Rango destino
var rowsDestino = hojaDestino.getRange("C39:U53");
var rowsDestinoR = rowsDestino.getNumRows();
var rowsDestinoUF = rowsDestino.getLastRow();
// Coincidencia nombre mascota
var nombreVisor = hojaDestino.getRange('D18').getValue();
var tablaDeBusquedaNombre = hojaOrigen.getRange('B7:J').getValues();
var listaDeBusquedaNombre = tablaDeBusquedaNombre.map(function(row){return row[0]});
var busquedaNombre = listaDeBusquedaNombre.indexOf(nombreVisor);
var coincidenciaNombre = tablaDeBusquedaNombre[busquedaNombre][0];
var fechaVisita = tablaDeBusquedaNombre[busquedaNombre][1];
var motivoVisita = tablaDeBusquedaNombre[busquedaNombre][5];
var sintomasVisita = tablaDeBusquedaNombre[busquedaNombre][6];
var observacionesVisita = tablaDeBusquedaNombre[busquedaNombre][7];
// Rangos copia y pega
var obj = [
{ src: fechaVisita, dst: "C" + rowsDestinoUF }, // Fecha
{ src: motivoVisita, dst: "D" + rowsDestinoUF }, // Motivo
{ src: sintomasVisita, dst: "G" + rowsDestinoUF }, // Sintomas
{ src: observacionesVisita, dst: "M" + rowsDestinoUF }, // Observaciones
];
Logger.log(obj);
obj.forEach(({src, dst}) => hojaOrigen.getRange(src).copyTo(hojaDestino.getRange(dst), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false));
}
But I am stuck. I do not know how to call to paste the fixed ones of the source sheet within the range of rows of the destination sheet.
I also don't know how to get the rows to be pasted on the destination sheet in reverse order.
I am new to coding with google apps script and javascript. I have learned a lot in a few days and I have been able to do many things, however, my capacity is still limited and I miss a lot.
Any help is well appreciated. Thannk you very much.
Sorry for my poor English.
It seems you're trying to search for the pet name using indexOf(), but that would just bring the first match and, from what I understand, you want to get up to 15 matches.
One way of doing that would be:
Iterate over the rows of the source sheet and check if the name in column B matches the one specified by cell D18 in the destination sheet. Since you want to get them in reverse order, you could start from the last row and go up until you get to the desired number (15) or until you check all values.
When there is a match, you can get the values from the columns you want and store them in an array.
Next, you write the values to the destination spreadsheet. Since your destination columns are not all consecutive and supposing you don't want to delete any existing data in the middle of the range, using setValues() probably wouldn't help in this case. One alternative would be to write the values one by one using setValue(), but that isn't very efficient. Perhaps a better way would be to write the values column by column, as the rows are consecutive.
The above can be implemented like this:
function get15lastRowsBDvisitasToFichaMascota() {
const libro = SpreadsheetApp.getActiveSpreadsheet();
const hojaOrigen = libro.getSheetByName('BD visitas');
const hojaDestino = libro.getSheetByName('Ficha mascota');
// Rango destino
var rowsDestino = hojaDestino.getRange("C39:U53");
var rowsDestinoR = rowsDestino.getNumRows();
var rowsDestinoUF = rowsDestino.getLastRow();
// Coincidencia nombre mascota
var nombreVisor = hojaDestino.getRange('D18').getValue();
var tablaDeBusquedaNombre = hojaOrigen.getRange('B7:J' + hojaOrigen.getLastRow()).getValues();
var maxMascotas = 15;
var dadosMascotas = [];
for (var i = tablaDeBusquedaNombre.length - 1; dadosMascotas.length <= maxMascotas && i >= 0; i--) {
if (tablaDeBusquedaNombre[i][0] === nombreVisor) {
var fechaVisita = tablaDeBusquedaNombre[i][1];
var motivoVisita = tablaDeBusquedaNombre[i][5];
var sintomasVisita = tablaDeBusquedaNombre[i][6];
var observacionesVisita = tablaDeBusquedaNombre[i][7];
dadosMascotas.push([fechaVisita, motivoVisita, sintomasVisita, observacionesVisita]);
}
}
if (dadosMascotas.length === 0) {
return;
}
var hojaDestinoCols = ['C', 'D', 'G', 'M'];
var primeraRowDestino = rowsDestino.getRow()
for (var i = 0; i < hojaDestinoCols.length; i++) {
// Get the values per column
var colValues = dadosMascotas.map(function (row) { return [row[i]] });
// Write the column values starting from the first row of the destination range
hojaDestino.getRange(
hojaDestinoCols[i] + primeraRowDestino +
':' +
hojaDestinoCols[i] + (primeraRowDestino + dadosMascotas.length - 1))
.setValues(colValues);
}
}
I have an array of columns that I want to keep. I've got that code below:
//Keep only columns user wants.
if(keepColumnsArray != ""){
for (var col = sourceShtLC; col > 0; col--) {
if (keepColumnsArray.indexOf(col) == -1) {
// This isn't a keeper, delete it
destSht.deleteColumn(col);
}
}
}
What I'd like to do now is arrange the columns following the order the keepColumnsArray has them.
Samples:
var keepColumnsArray = [3,2,1,4,5]
Using the above sample I want column 3 to be the first column, column 2 to be the second, column 1 to be the 3rd, column 4 to be the 4th and column 5 to be the 5th.
Current Order:
The order I want it. As you can see it's the same order the array is in.
Solution:
Rather than deleting the columns first I used code from the accepted answer to move the columns I want to keep to the front. In this case Columns 1 through 5 I kept and then I deleted the rest because all that was left were columns I did not need. Here is my final code.
//Use order of array to reorder columns and then delete the rest of the columns
var offset = keepColumnsArray.length;
destSht.insertColumns(1, offset);
keepColumnsArray.forEach(function(e, i) {
destSht.getRange(1, (e + offset), destSht.getLastRow(), 1).copyTo(destSht.getRange(1, i + 1));
});
destSht.deleteColumns(offset + 1, sourceShtLC); //Keep only columns user wants.
You want to arrange the columns on the sheet with var keepColumnsArray = [3,2,1,4,5].
For example, you want to arrange from the columns 1, 2, 3, 4, 5 to 3, 2, 1, 4, 5.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Pattern 1:
In this case, moveColumns is used. The flow of this script is as follows.
Create an array object including the original index and destination index of the columns.
Sort the array.
The columns are arranged using moveColumns.
Sample script:
function myFunction() {
var keepColumnsArray = [3,2,1,4,5];
var obj = keepColumnsArray.reduce(function(ar, e, i) {
ar.push({from: i + 1, to: e});
return ar;
}, []);
obj.sort(function(a, b) {return a.to < b.to ? -1 : 1});
var sheet = SpreadsheetApp.getActiveSheet();
obj.forEach(function(o) {
var columnSpec = sheet.getRange(1, o.from);
if (o.from != o.to) sheet.moveColumns(columnSpec, o.to);
for (var j = 0; j < obj.length; j++) {
if (obj[j].from < o.from) obj[j].from += 1;
}
});
}
Pattern 2:
In this case, each column is copied with the order of keepColumnsArray using a temporal sheet, and put the arranged columns to the original sheet.
Sample script:
function myFunction() {
var keepColumnsArray = [3,2,1,4,5];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var temp = ss.insertSheet("temp");
sheet.activate();
keepColumnsArray.forEach(function(e, i) {
sheet.getRange(1, e, sheet.getLastRow(), 1).copyTo(temp.getRange(1, i + 1));
});
temp.getDataRange().copyTo(sheet.getRange(1, 1));
ss.deleteSheet(temp);
}
Reference:
moveColumns(columnSpec, destinationIndex)
Added:
From OP's comment, In this sample script, the columns are inserted and put the arranged columns.
Instead of creating a temp sheet can we not add 5 columns to the beginning and then copy them to those new columns?
Sample script:
function myFunction() {
var keepColumnsArray = [3,2,1,4,5];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var offset = keepColumnsArray.length;
sheet.insertColumns(1, offset);
keepColumnsArray.forEach(function(e, i) {
sheet.getRange(1, e + offset, sheet.getLastRow(), 1).copyTo(sheet.getRange(1, i + 1));
});
sheet.deleteColumns(offset, offset);
}
Problem
Filtering out columns by criteria
Reordering columns by criteria
Solution
First, add logic that filters and sorts your values. One of the possible algorithms is as follows: map source such that each row is mapped such that each cell is cell by order index from source and filter columns such that its index is a keep index.
var input = [3,2,1,4,5];
var initial = [
['Column1','Column2','Column3','Column4','Column5','Column6','Column7'],
['2first','2second','2third','2fourth','2fifth','2sixth','2seventh'],
];
function filterAndReorder (source,order) {
return source
.map(function(row,r){
return row.map(function(cell,c){
return source[r][order[c]-1];
})
.filter(function(cell,c){
return cell !== undefined;
});
})
}
var output = filterAndReorder(initial,input);
console.log(output);
Then, use the fact that setValues() accepts 2D Array and replaces Range contents:
var lrow = destSht.getLastRow();
var range = destSht.getRange(1,1,lrow,5);
var inputVals = range.getValues();
var keepColumnsArray = []; //obtain the keep Array somehow;
var outputVals = filterAndReorder(inputVals,keepColumnsArray);
var range.setValues(outputVals);
Notes
keepColumnsArray is an Array so the != "" is redundant (unless you actually expect it to be an empty string in which case I would suggest rewriting the logic that outputs the Array - it will save you at least 1 op + save you debug time in the future).
As a general rule of thumb, please, avoid using I/O as much as possible (especially in a loop) and keep input close to start of the logic and output to the end. deleteColumn is an I/O and thus should at least be performed in batch.
UPD if you reorder is partial (that is, there are columns unchanged), you can fold the resulting empty columns via deleteColumns()
References
filter() method ref on MDN
map() method ref on MDN