Спрашивающий имеет три листа в определенном столбцовом макете - по сути, несколько строк на набор данных и несколько столбцов (по одному на каждый период времени).Эти листы не заменяются, но была запрошена краткая версия, в которой можно было бы использовать фильтрацию, чтобы эффективно сосредоточиться на соответствующих данных.Таким образом, каждый лист должен быть переведен из столбчатого формата в построчный формат.
Сам процесс прост.Исходные данные включали 64 продукта при 8 строках данных на продукт.Выходные записи были @ 1350.
Код опрашивающего зависал при преобразовании данных в выходной формат.Важно использовать 8 строк данных для каждого продукта, и код включает проверку того, что частное от общего числа строк данных, деленное на восемь, является целым числом.Кроме того, имена листа источника и листа вывода называются по имени (getSheetByName
), так что код может быть легко применен к любому именованному входному листу и любому названному выходному листу.Единственным условием является то, что оба листа должны существовать заранее.
Первоначальное разрешение проблемы с кодом спрашивающего было успешным, и использование методологии getDataRange
и getValues
до цикла значительно улучшило производительность,Есть две петли;один с вертикальной ориентацией, перемещающийся по строкам данных;и второй с горизонтальной ориентацией, перемещающийся через связанные со временем столбцы.Однако изначально производительность была очень неэффективной, и код заканчивался по тайм-ауту до завершения.
Я изменил код, чтобы построить один двумерный массив и сохранить его на выходном листе только один раз в конце цикла.Это оказало драматическое влияние на производительность.Общее время выполнения сократилось с нескольких минут до менее чем 5 секунд.
function so5243560403() {
// Build a clean Output Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var SourceSheet = ss.getSheetByName("Category2");
var Target = ss.getSheetByName("Category3Output");
// Various variables
var SourceHeaderRow = 9;
var RowsPerProduct = 8;
var ProductLengthTruncate = 11;
var SourceArray = [];
var i = 0;
var w = 0;
// get the bottom of the column
var ColAvals = SourceSheet.getRange("A" + (SourceHeaderRow + 1) + ":A").getValues();
var ColAlast = ColAvals.filter(String).length;
//Logger.log("Last row in column A with data"+ColAlast); //DEBUG
var NumberofProducts = ColAlast / RowsPerProduct;
var lastRow = SourceSheet.getLastRow();
// Count the products and confirm eight rows each
var prodtest = isInt1(NumberofProducts);
if (!prodtest) {
// Logger.log("NOT an integer!");
SpreadsheetApp.getUi().alert("Number of Rows divided by rows by Product isn't an integer");
return false;
}
// Get data to clear Target ready for new data
var TargetlastRow = Target.getLastRow();
var TargetlastColumn = Target.getLastColumn();
// clear the content before re-building
Target.getRange(2, 1, TargetlastRow, TargetlastColumn).clear({
contentsOnly: true
});
// Get ALL the data on the SourceSheet
var SourceRange = SourceSheet.getDataRange();
var SourceValues = SourceRange.getValues();
// create loop for rows of data; first row of data in array=9
for (i = SourceHeaderRow; i < (SourceHeaderRow + ColAlast); i = i + 8) {
// create loop for weeks (Week 1=Col5, Week 2=Col6... Week 52=Col56, etc) (actual column numbers are +1)
for (w = 1; w < 53; w++) {
// Test to see whether there's a value for Display; the only field ALWAYS populated
if (SourceValues[i + 1][w + 4]) {
// Get Product and data fields
var Prodlen = SourceValues[i][3].length;
var prodedit = SourceValues[i][3].substring(11, (SourceValues[i][3].length));
var product = prodedit.trim();
var catalogue = SourceValues[i][w + 4];
var display = SourceValues[i + 1][w + 4];
var ESP = SourceValues[i + 3][w + 4];
var mechanic = SourceValues[i + 6][w + 4];
var join1 = product+" | "+display+" | "+mechanic;
var join2 = display+" | "+product+" | "+mechanic;
// Start building an array
SourceArray.push([w, product, catalogue, display, ESP, mechanic,join1,join2]);
} // end if data exists - process this week
} // end w - this week loop
} // end i - this row loop
// Copy the data from the array to the Target sheet
// count number of rows
var SourceArraylen = SourceArray.length;
// first row is #2, allowing for header row
// first column = A
// number of rows = length of array
// number of columns = 6 (the fields puched to the array
var TargetRange = Target.getRange(2, 1, SourceArraylen, 8);
// set the array values on the Target sheet
TargetRange.setValues(SourceArray);
}
function isInt1(value) {
return !isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10));
}
ОБНОВЛЕНИЕ
Второй элемент кода спрашивающего связан с обновлением данных до "Выходной лист »по мере внесения изменений в листы« Категория ».Код задающего вопросы для обновления был в порядке, но отсутствовал перевод исходного диапазона на листе категорий для установления целевого диапазона на выходном листе.
Решение включает в себя рубрику, основанную на математической последовательности чисел.В этом случае математическая последовательность представляет собой номера строк для продуктов на исходном листе;каждый продукт занимает 8 строк, а первая строка # 10, поэтому последовательность 10,18,26,34 ....
onEdit возвращает диапазон измененной ячейки и getRow
и getColumn
может использоваться для определения координат измененной ячейки.Задача состоит в том, чтобы, зная фактический номер строки, который был изменен, установить, какой номер в последовательности строк (и, следовательно, название продукта) представляет фактический номер строки.Также крайне маловероятно (восемь к одному), что измененная строка будет совпадать с первой строкой для номера продукта.
Поэтому для математических последовательностей необходимо применять алгоритм - дважды.Формула для определения n-го числа в последовательности чисел имеет вид An = A1 + (D x (n-1)), где A1 - число для первой строки (в нашем случае 10), D = разница между каждым числомв последовательности (в нашем случае 8), а n = номер в последовательности (в нашем случае измененный номер строки).
Первый проход - установить номер позиции в последовательности чисел(товарные группы) представлены фактически измененной строкой.Весьма вероятно, что результат не является целым числом, т. Е. Он не совпадает с первым рядом для группы продуктов.Таким образом, результат округляется до ближайшего целого числа, и алгоритм обрабатывается второй раз.
Однако на этот раз мы знаем позицию порядкового номера и решаем найти значение числа.В этом случае формула имеет вид ((An-A1) / D) +1.Это вернет номер строки на листе источника, соответствующий первой строке для соответствующей группы продуктов.Мы используем это, чтобы определить, какой тип поля был изменен (Категория, Отображение и т. Д.).
Номер столбца указывает номер недели.Неделя 1 начинается в столбце F, поэтому get column
позволяет нам определить, произошло ли изменение в столбце недели (или оно было слева от столбца F).Если слева, то «не моя проблема», если в F или выше, то это нужно отметить.
Наконец, мы делаем getRangeValue
для листа цели и ищем совпадениеномер недели в столбце A И сокращенное название продукта в столбце B. Это дает координаты setValue
для нового значения, отслеживаемого из OnEdit.
function OnEdit(e) {
// Update relevant Outputsheets on changes in Category sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Establish variables
var s1 = "Category1";
var s2 = "Category2";
var s3 = "Category3";
var tsuffix = "Output";
//Logger.log("Sheet information");//DEBUG
//Logger.log("The sheets to track are s1= "+s1+", s2 = "+s2+", and s3 = "+s3+", and the Output suffix is "+tsuffix+". For example s1output = "+s1+tsuffix);// DEBUG
var TargetSheet = "";
var weekscolumnstart = 6; // Column F
var startrow = 10; // applies to the Source sheet
var rowsperProduct = 8; // applies to the source sheet
var changedfield = 0;
var changedfieldname = "";
var n = 0;
// Collect data from the event
var range = e.range;
var oldValue = e.oldValue;
var value = e.value
var source = e.source;
var sheet = source.getActiveSheet();
var ssname = sheet.getName();
// Logger.log("Range: "+range.getA1Notation()+", old value = "+oldValue+", new value = "+value+", source = "+source+", ss = "+sheet+", sheet name = "+ssname); //DEBUG
// get the co-ordinates of the change
var SourceRow = range.getRow();
var SourceColumn = range.getColumn();
// Logger.log("the Column is "+SourceColumn+", and the Row is "+SourceRow);// DEBUG
// the weeks range to the right, from column F (va = weekscolumnstart). So by knowing the column number of the even, we can calculate the week number that applied to the change.
var weeknumber = (SourceColumn - weekscolumnstart + 1);
switch (ssname) { // the field references are used in a GetValue statement where the column is a reference to a specific column
case s1:
TargetSheet = s1 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
case s2:
TargetSheet = s2 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
case s3:
TargetSheet = s3 + tsuffix;
//Logger.log("The Source sheet was "+ssname+", so the Target sheet is "+TargetSheet);// DEBUG
break;
default:
TargetSheet = 0;
//Logger.log("The change was made in a sheet that we don't need to track.");
} // end switch
// get product and other change information if the change is on a tracked sheet and in a relevant column.
// evalue for the event on a non-relevant sheet or in a non-relevant column
if (TargetSheet == 0 || weeknumber <= 0) {
// do nothing
} else {
//Logger.log("before calculating line number; the TargetSheet is "+TargetSheet);
// The source has eight rows of data per Product; there is no predictability about which one of the eight will be chnaged for a given product.
// However the sequence of all the rows follows a mathenmatical sequence, so by knowing the row, it is possible to determine the product grouping
// And by knowing the product grouping, it is possible to determine the first row of the product group.
//
// The formula for the position of a number n a mathematical sequence is: = an=a1+d(n-1)
// where an = the "nth" number in the sequence (equates to the nth Product); a1 = the start row (var=startrow); d = difference between each group (var=rowsperProduct) and n=the actual row number.
// In the first instance we know the row number from the event data, so we work backwards to solve for the position of that number in the sequence.
//
// 1) calculate the starting row for this product
// 2) (Row number - starting row) divided by rowsperProduct) plus one.
// 3) There's only a one-in eight chance that it is an integer, so round down to get first row of this product sequence
// 4) Then we work forwards; since we know the nth number, we can calculate the row number for the first row for that product.
// 5) starting row plus (rowsperproduct x (seqwuence number minus 1))
// By knowing the first row in the product sequence, and the row number that was chnaged, we can calculate which data set was chnaged.
var productseq = (((SourceRow - startrow) / rowsperProduct) + 1);
var productseqround = Math.floor(productseq);
var productline = (startrow + (rowsperProduct * (productseqround - 1)));
//Logger.log("the row number is "+SourceRow+", but the sequence number for this product is "+productseqround+", and the startrow for this product group = "+productline); //DEBUG
// identify the field that has changed
// Source Row number less Productline
// if 0 = Catalogue
// if 1= Display
// if 3 = ESP
// if 6 = Mechanic
var LineNumber = (SourceRow - productline);
//Logger.log("the calculated Line number = "+LineNumber); //DEBUG
switch (LineNumber) { // the field references are used in a GetValue statement where the column is a reference to a specific column
case 0:
changedfield = 3;
changedfieldname = "Catalogue";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 1:
changedfield = 4;
changedfieldname = "Display";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 3:
changedfield = 5;
changedfieldname = "ESP";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
case 6:
changedfield = 6;
changedfieldname = "Mechanic";
//Logger.log("the changed field was "+changedfieldname); // DEBUG
break;
default:
//Logger.log("the changed field was none of the above");
changedfield = 0;
} //end switch
} //end if
// OK, let's get this party started..
// evaluate the sheet
if (TargetSheet == 0) {
//Logger.log("Do nothing because it's not on a sheet we need to worry about"); //DEBUG
}
// evaluate the week applying to the change
else if (weeknumber <= 0) {
//Logger.log("whatever was changed wasn't one of the key fields"); //DEBUG
}
//evaluate the changed field
else if (changedfield == 0) {
//Logger.log("Do nothing because it's not a field that we're not worried about"); //DEBUG
}
// looks OK to go ahead
else {
//Logger.log("the field was changed for week# "+weeknumber+", lets find the product");
// trim the Product Code for searching on the Output Sheet
var LongProdName = sheet.getRange(productline, 4).getValue();
var Prodedit = LongProdName.substring(11, (LongProdName.length));
var ShortProdName = Prodedit.trim();
//Logger.log("the Product Name is "+LongProdName+", shortened to: "+ShortProdName);// DEBUG
// test for existence of the TargetSheet
var sheeterror = 1; // use this variable as the canary in the coal mine. Set to 1, prima facie error
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TargetSheet);
if (target != null) { // test for exitentce of target;
sheeterror = 0; // if target sheet exists, then set sheeterror to zero; that is, sound the all clear
}
if (sheeterror != 0) { // now test for a sheet erorr; anything other than zero means there is a problem
SpreadsheetApp.getUi().alert("WARNING#1: \n\n The Output Sheet: " + TargetSheet + " does NOT exist.\n\n Product: " + LongProdName + ", \nWeek: " + weeknumber + ",\nField: " + changedfieldname + ", \nold value = " + oldValue + " \n new value = " + value + ".\n Date: " + (new Date()));
// Logger.log("ERROR: The Outout sheet:" + TargetSheet + " doesn't exist. Data changed on sheet:" + ssname + ", Product: " + LongProdName + ", Week# " + weeknumber + ", Field: " + changedfieldname + ", old value=" + oldValue + ", new value=" + value + ", Date " + (new Date())); //DEBUG
return false;
}
// set the data range for the Output sheet and get the data
var TargetRange = target.getDataRange();
var TargetValues = TargetRange.getValues();
// setup the search string
// Logger.log("target Range = "+TargetRange.getA1Notation()+", search string = '"+ShortProdName+"', week# is "+weeknumber); // DEBUG
// Logger.log("TargetValues length = "+TargetValues.length);
// so lets find a match
var outputmatch = 1; // use this variable as the canary in the coal mine for not finding a match. Set to 1 = prima facie error
for (n = 0; n < TargetValues.length; ++n) {
// iterate row by row and match the week (Column A) and Name (Column B)
//Logger.log("n = "+n+", product = "+ShortProdName+", week = "+weeknumber);
if (TargetValues[n][1] == ShortProdName && TargetValues[n][0] == weeknumber) {
// when we find the result (row number), add plus one to accout for the array starting at zero.
var result = n + 1;
// Logger.log("Found a match, n = "+result); //DEBUG
// create the co-ordinates for the output cell
// row number = result, column = chnagedfield calculated earlier
// Logger.log("update range: row = "+result+", column = "+changedfield); //DEBUG
var updatecell = target.getRange(result, changedfield);
//Logger.log("The update cell is "+updatecell.getA1Notation()); // DEBUG
// Update the cell for the new value
updatecell.setValue(value);
// Fix values for Display/Mechanic if they were updated
if (changedfieldname == "Display") {
var displayvalue = value;
} else {
var displayvalue = TargetValues[n][3];
}
if (changedfieldname == "Mechanic") {
var mechanicvalue = value;
} else {
var mechanicvalue = TargetValues[n][5];
}
// define the join1 parameters
var join1 = TargetValues[n][1] + " | " + displayvalue + " | " + mechanicvalue; // Bundle, Display, Mechanic
// set the range for join 1
var updatejoin1 = target.getRange(result, 7);
// update join1
updatejoin1.setValue(join1);
// define the join2 parameters
var join2 = displayvalue + " | " + TargetValues[n][1] + " | " + mechanicvalue; // Display, Bundle, Mechanic
// set the range for join 2
var updatejoin2 = target.getRange(result, 8);
// update join2
updatejoin2.setValue(join2);
// the outputmatch value to zero
outputmatch = 0;
//Logger.log("The update cell is "+updatecell.getA1Notation()+", and the new value is "+ value); //DEBUG
//Logger.log("SUMMARY: Data changed on sheet:" + ssname + ", saved to Output sheet:" + TargetSheet + ", range: " + range.getA1Notation() + ", Product: " + LongProdName + ", Week# " + weeknumber + ", Field: " + changedfieldname + ", old value=" + oldValue + ", new value=" + value + ", Date " + (new Date())); //DEBUG
return false;
}
} // end for n
if (outputmatch != 0) { // now test for a faliure to update the output sheet; anything other than zero means there is a problem
// create an error message if we were unable to find a match and could not update the output sheet field
SpreadsheetApp.getUi().alert("WARNING#2: There was an unidentified problem.\n\n Output Sheet: " + TargetSheet + " does NOT appear to have been updated.\n\n Product: " + LongProdName + ", \nWeek: " + weeknumber + ",\nField: " + changedfieldname + ", \nold value = " + oldValue + " \n new value = " + value + ".\n Date: " + (new Date()));
return false;
}
} // end if
}