How to convert query to arrayformula - google-sheets

I would like to ask on how to convert this query to an arrayformula:
=query({unique(filter(A$2:A,B$2:B=B2)),sequence(rows(unique(filter(A$2:A,B$2:B=B2))))},"select Col2 where Col1 = '"&A2&"'")
I also attached this in a gsheet: https://docs.google.com/spreadsheets/d/1oFbGsP42fMphedY7wtZ7C3focVDJxxkVXHdf6aC3_D4/edit#gid=0
The idea is to count sequence number that will restart to 1 for the same item if month is different month (but it wont necessarily to crosscheck with month because thinking to concat to others but is still gonna be based on the unique id here)

try:
=ARRAYFORMULA(IFNA(VLOOKUP(A2:A&B2:B, SORT(SPLIT(FLATTEN(TRANSPOSE(
SPLIT(FLATTEN(QUERY(QUERY(QUERY({A2:A&B2:B&"×"&
VLOOKUP(A2:A, {A2:A, TEXT(ROW(A2:A), "00000")}, 2, ), B2:B},
"select max(Col1) group by Col1 pivot Col2"),
"offset 1", 0),,9^9)), " "))&"×"&SEQUENCE(COUNTUNIQUE(A2:A))), "×")), 3, )))

One option would be to use an Apps Script custom function. To achieve this, follow these steps:
In your spreadsheet, select Tools > Script editor to open a script bound to your file.
Copy this function in the script editor, and save the project (check inline comments):
Using date:
function myFunction(values) {
values = values.filter(r => r[0].length); // Remove empty rows
return values.map((row,i) => { // Loop through rows
const [item, date] = row;
const month = date.getMonth();
const year = date.getFullYear();
const monthRows = values.filter(r => r[1].getMonth() === month && r[1].getFullYear() === year); // Filter month rows
const itemIndex = [...new Set(monthRows.map(r => r[0]))].indexOf(item); // Check index of this item in current month
return itemIndex + 1;
});
}
Now, if you go back to your spreadsheet, you can use this function like any in-built one. You just have to provide the appropriate ranges as function arguments (in this case, A2:B):
Using ID number:
function myFunction(values) {
return values.map((row,i) => { // Loop through rows
const [item, id] = row;
if (item.length) {
const idRows = values.filter(r => id === r[1]); // Filter id
const itemIndex = [...new Set(idRows.map(r => r[0]))].filter(String).indexOf(item); // Check index of this item in current id
return itemIndex + 1;
}
});
}
Reference:
Custom Functions in Google Sheets

Related

Google sheets script to auto add a new row after every unique value in column A

Please assist with script for Google sheets to auto add a new row after every unique value in column A
I think this code will help you
const sheetName = 'Tabellenblatt3';
function strAfterUnique(){
let sheet = SpreadsheetApp.getActive().getSheetByName(sheetName),
colARange = sheet.getDataRange().getValues().map(dataRow => dataRow[0]),
duplicates = [...new Set(colARange.filter((value, index, self) => self.indexOf(value) !== index))];
for (let i=colARange.length-2; i>=0; i--) {
if (!duplicates.some(value => value === colARange[i]) && colARange[i+1] !=='' ) sheet.insertRowAfter(i+1)
}
}

Google sheet array formula with Filter & Join

I need to build a table based on the following data:
Ref
Product
R1
ProdA
R2
ProdC
R1
ProdB
R3
ProdA
R4
ProdC
And here the result I need:
My Product
All Ref
ProdA
R1#R3
ProdC
R2#R4
The particularity is that the 'My Product' column is computed elsewhere. So I need an arrayformula based on 'My Product' column to look in the first table to build the 'All Ref' column. You follow me?
I know that Arrayformula is not compatible with filter and join ... I expect a solution like this one Google sheet array formula + Join + Filter but not sure to understand all steps and if really adapted to my case study.
Hope you can help.
You could try something like this:
CREDIT: player0 for the method shared to similar questions
=ARRAYFORMULA(substitute(REGEXREPLACE(TRIM(SPLIT(TRANSPOSE(
QUERY(QUERY({B2:B&"😊", A2:A&"#"},
"select max(Col2)
where Col1 !=''
group by Col2
pivot Col1"),,999^99)), "😊")), "#$", )," ",""))
Step by step:
Instead of the workaround hacks I implemented a simple joinMatching(matches, values, texts, [sep]) function in Google Apps Script.
In your case it would be just =joinMatching(MyProductColumn, ProductColumn, RefColumn, "#").
Source:
// Google Apps Script to join texts in a range where values in second range equal to the provided match value
// Solves the need for `arrayformula(join(',', filter()))`, which does not work in Google Sheets
// Instead you can pass a range of match values and get a range of joined texts back
const identity = data => data
const onRange = (data, fn, args, combine = identity) =>
Array.isArray(data)
? combine(data.map(value => onRange(value, fn, args)))
: fn(data, ...(args || []))
const _joinMatching = (match, values, texts, sep = '\n') => {
const columns = texts[0]?.length
if (!columns) return ''
const row = i => Math.floor(i / columns)
const col = i => i % columns
const value = i => values[row(i)][col(i)]
return (
// JSON.stringify(match) +
texts
.flat()
// .map((t, i) => `[${row(i)}:${col(i)}] ${t} (${JSON.stringify(value(i))})`)
.filter((_, i) => value(i) === match)
.join(sep)
)
}
const joinMatching = (matches, values, texts, sep) =>
onRange(matches, _joinMatching, [values, texts, sep])```

Is there a way in google sheets to move an entire row automatically based on drop down options inside of one of the columns and change dates

What I'm looking to do is take my google sheet that contains columns A through N. What I want to do is take the rows, except for row 1 which is a header for all the column, and have them automatically move to the bottom of the same spreadsheet based on the drop down options in column E. Once the option has been selected that signifies it's done I want the entire row to automatically move to the bottom of the spreadsheet. In addition to that I also want column D, which is the due date for the event, to update by adding one year to the date in that column as the new due date will be one year from the previous. Is this possible? Thanks for the help.
function onEdit(e) {
const row = e.range.getRow();
const col = e.range.getColumn();
const as = e.source.getActiveSheet();
if(as.getName() == "Sheet1" && col == 5 && row >1 && !as.getRange(row,col).getValue()=='MPS')
{
const row_new = as.getRange(row,1,1,col);
row_new.copyTo(as.getRange(as.getLastRow()+1,1,1,col));
as.deleteRow(row)
}
}
You should change the way you are comparing the column 5
I've used the following example:
Instead of '==' use '===' comparison because double '=' is not comparing the value but the type of the object. Having said that here's my approach:
function onEdit(e) {
const row = e.range.getRow();
const col = e.range.getColumn();
const as = e.source.getActiveSheet();
const newDate = new Date();
// No due date
if (as.getRange(row,3).getValue() > newDate) {
return;
}
if(as.getName() === "Sheet1" && col === 5 && row >1 && as.getRange(row,col).getValue()!=='MPS')
{
let row_new = as.getRange(row,1,1,col);
// Add 1 day to your due date
let oldValues = row_new.getValues();
oldValues[0][3] = new Date(oldValues[0][3].setDate(newDate.getDate() + 1));
// Set the new date
row_new.setValues(oldValues);
row_new.copyTo(as.getRange(as.getLastRow()+1,1,1,col));
as.deleteRow(row);
}
}
Where at the very beginning there's a comparison between dates in order to determine if the column D (Date) is greater than the current date or not, then in order to set the new Date I make use of the Class Date and added 1 day to the date extracted from the column D.
References
Range.getValues
Range.setValues

Advance manual unpivot of data table [duplicate]

I am trying to produce a "reverse pivot" function. I have searched long and hard for such a function, but cannot find one that is already out there.
I have a summary table with anywhere up to 20 columns and hundreds of rows, however I would like to convert it into a flat list so I can import to a database (or even use the flat data to create more pivot tables from!)
So, I have data in this format:
Customer 1
Customer 2
Customer 3
Product 1
1
2
3
Product 2
4
5
6
Product 3
7
8
9
And need to convert it to this format:
Customer | Product | Qty
-----------+-----------+----
Customer 1 | Product 1 | 1
Customer 1 | Product 2 | 4
Customer 1 | Product 3 | 7
Customer 2 | Product 1 | 2
Customer 2 | Product 2 | 5
Customer 2 | Product 3 | 8
Customer 3 | Product 1 | 3
Customer 3 | Product 2 | 6
Customer 3 | Product 3 | 9
I have created a function that will read the range from sheet1 and append the re-formatted rows at the bottom of the same sheet, however I am trying to get it working so I can have the function on sheet2 that will read the whole range from sheet1.
No matter what I try, I can't seem to get it to work, and was wondering if anybody could give me any pointers?
Here is what I have so far:
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
heads = values[0]
for (var i = 1; i <= numRows - 1; i++) {
for (var j = 1; j <= values[0].length - 1; j++) {
var row = [values[i][0], values[0][j], values[i][j]];
sheet.appendRow(row)
}
}
};
I wrote a simple general custom function, which is 100% reusable you can unpivot / reverse pivot a table of any size.
In your case you could use it like this: =unpivot(A1:D4,1,1,"customer","sales")
So you can use it just like any built-in array function in spreadsheet.
Please see here 2 examples:
https://docs.google.com/spreadsheets/d/12TBoX2UI_Yu2MA2ZN3p9f-cZsySE4et1slwpgjZbSzw/edit#gid=422214765
The following is the source:
/**
* Unpivot a pivot table of any size.
*
* #param {A1:D30} data The pivot table.
* #param {1} fixColumns Number of columns, after which pivoted values begin. Default 1.
* #param {1} fixRows Number of rows (1 or 2), after which pivoted values begin. Default 1.
* #param {"city"} titlePivot The title of horizontal pivot values. Default "column".
* #param {"distance"[,...]} titleValue The title of pivot table values. Default "value".
* #return The unpivoted table
* #customfunction
*/
function unpivot(data,fixColumns,fixRows,titlePivot,titleValue) {
var fixColumns = fixColumns || 1; // how many columns are fixed
var fixRows = fixRows || 1; // how many rows are fixed
var titlePivot = titlePivot || 'column';
var titleValue = titleValue || 'value';
var ret=[],i,j,row,uniqueCols=1;
// we handle only 2 dimension arrays
if (!Array.isArray(data) || data.length < fixRows || !Array.isArray(data[0]) || data[0].length < fixColumns)
throw new Error('no data');
// we handle max 2 fixed rows
if (fixRows > 2)
throw new Error('max 2 fixed rows are allowed');
// fill empty cells in the first row with value set last in previous columns (for 2 fixed rows)
var tmp = '';
for (j=0;j<data[0].length;j++)
if (data[0][j] != '')
tmp = data[0][j];
else
data[0][j] = tmp;
// for 2 fixed rows calculate unique column number
if (fixRows == 2)
{
uniqueCols = 0;
tmp = {};
for (j=fixColumns;j<data[1].length;j++)
if (typeof tmp[ data[1][j] ] == 'undefined')
{
tmp[ data[1][j] ] = 1;
uniqueCols++;
}
}
// return first row: fix column titles + pivoted values column title + values column title(s)
row = [];
for (j=0;j<fixColumns;j++) row.push(fixRows == 2 ? data[0][j]||data[1][j] : data[0][j]); // for 2 fixed rows we try to find the title in row 1 and row 2
for (j=3;j<arguments.length;j++) row.push(arguments[j]);
ret.push(row);
// processing rows (skipping the fixed columns, then dedicating a new row for each pivoted value)
for (i=fixRows; i<data.length && data[i].length > 0; i++)
{
// skip totally empty or only whitespace containing rows
if (data[i].join('').replace(/\s+/g,'').length == 0 ) continue;
// unpivot the row
row = [];
for (j=0;j<fixColumns && j<data[i].length;j++)
row.push(data[i][j]);
for (j=fixColumns;j<data[i].length;j+=uniqueCols)
ret.push(
row.concat([data[0][j]]) // the first row title value
.concat(data[i].slice(j,j+uniqueCols)) // pivoted values
);
}
return ret;
}
That is basically array manipulation... below is a code that does what you want and writes back the result below existing data.
You can of course adapt it to write on a new sheet if you prefer.
function transformData(){
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();//read whole sheet
var output = [];
var headers = data.shift();// get headers
var empty = headers.shift();//remove empty cell on the left
var products = [];
for(var d in data){
var p = data[d].shift();//get product names in first column of each row
products.push(p);//store
}
Logger.log('headers = '+headers);
Logger.log('products = '+products);
Logger.log('data only ='+data);
for(var h in headers){
for(var p in products){ // iterate with 2 loops (headers and products)
var row = [];
row.push(headers[h]);
row.push(products[p]);
row.push(data[p][h])
output.push(row);//collect data in separate rows in output array
}
}
Logger.log('output array = '+output);
sheet.getRange(sheet.getLastRow()+1,1,output.length,output[0].length).setValues(output);
}
to automatically write the result in a new sheet replace last line of code with these :
var ns = SpreadsheetApp.getActive().getSheets().length+1
SpreadsheetApp.getActiveSpreadsheet().insertSheet('New Sheet'+ns,ns).getRange(1,1,output.length,output[0].length).setValues(output);
google-sheets-formula
With the advent of new LAMBDA and MAKEARRAY functions, we can unpivot the data without string manipulation. This works by creating a sequence of appropriate index numbers for the new array, which should be faster than string manipulation.
=ARRAYFORMULA(LAMBDA(range,s_cols,
QUERY(
MAKEARRAY(ROWS(range)*(COLUMNS(range)-s_cols),s_cols+1,
LAMBDA(i,j,
TO_TEXT(
INDEX(range,
ROUNDDOWN(1+(i-1)/(COLUMNS(range)-s_cols)),
if(j>s_cols,MOD(i-1,COLUMNS(range)-s_cols)+s_cols+1,j)
)
)
)
),"where Col"&s_cols+1&" is not null"
)
)(A1:C10,2))
Or as a named function(UNPIVOT(range,s_cols)):
=ARRAYFORMULA(
QUERY(
MAKEARRAY(ROWS(range)*(COLUMNS(range)-s_cols),s_cols+1,
LAMBDA(i,j,
TO_TEXT(
INDEX(range,
ROUNDDOWN(1+(i-1)/(COLUMNS(range)-s_cols)),
if(j>s_cols,MOD(i-1,COLUMNS(range)-s_cols)+s_cols+1,j)
)
)
)
),"where Col"&s_cols+1&" is not null"
)
)
Arguments:
range: The range to unpivot. Eg:A1:C10
s_cols: The number of static columns on the left.Eg:2
google-apps-script
Using simple, yet powerful loops on V8 engine:
/**
* Unpivots the given data
*
* #return Unpivoted data from array
* #param {A1:C4} arr 2D Input Array
* #param {1=} ignoreCols [optional] Number of columns on the left to ignore
* #customfunction
*/
const unpivot = (arr, ignoreCols = 1) =>
((j, out) => {
while (++j < arr[0].length)
((i) => {
while (++i < arr.length)
out.push([arr[0][j], ...arr[i].slice(0, ignoreCols), arr[i][j]]);
})(0);
return out;
})(ignoreCols - 1, []);
Usage:
=UNPIVOT(A1:C4)
=UNPIVOT(A1:F4,3)//3 static cols on left
={{"Customer","Products","Qty"};UNPIVOT(A1:D4)}//add headers
Live demo:
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
const arr = [
[' ', ' Customer 1 ', ' Customer 2 ', ' Customer 3'],
['Product 1 ', ' 1 ', ' 2 ', ' 3'],
['Product 2 ', ' 4 ', ' 5 ', ' 6'],
['Product 3 ', ' 7 ', ' 8 ', ' 9'],
];
console.log("Input table")
console.table(arr)
/**
* Unpivots the given data
*
* #return Unpivoted data from array
* #param {A1:C4} arr 2D Input Array
* #param {1=} ignoreCols [optional] Number of columns on the left to ignore
* #customfunction
*/
const unpivot = (arr, ignoreCols = 1) =>
((j, out) => {
while (++j < arr[0].length)
((i) => {
while (++i < arr.length)
out.push([arr[0][j], ...arr[i].slice(0, ignoreCols), arr[i][j]]);
})(0);
return out;
})(ignoreCols - 1, []);
console.log("Output table")
console.table(unpivot(arr));
console.log("Output table with 2 static columns")
console.table(unpivot(arr,2));
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Check history for older deprecated functions
Use FLATTEN. It converts any array into single column.
Here's the formula for unpivot:
=ARRAYFORMULA(SPLIT(FLATTEN(A2:A12&"💣"&B1:F1&"💣"&B2:F12),"💣"))
FLATTEN creates 1-column array of Item1💣Date1💣67455 strings, which we then split.
Please copy the sample file to try.
Shorter:
=index(SPLIT(FLATTEN(A2:A12&"💣"&B1:F1&"💣"&B2:F12),"💣"))
Please also see this solution.
It uses INDIRECT and settings, so the formula looks like a more general solution:
I didn't think you had enough array formula answers so here's another one.
Test Data (Sheet 1)
Formula for customer
=ArrayFormula(hlookup(int((row(indirect("1:"&Tuples))-1)/Rows)+2,{COLUMN(Sheet1!$1:$1);Sheet1!$1:$1},2))
(uses a bit of math to make it repeat and hlookup to find correct column in column headers)
Formula for product
=ArrayFormula(vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$A},2))
(similar approach using mod and vlookup to find correct row in row headers)
Formula for quantity
=ArrayFormula(vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$Z},int((row(indirect("1:"&Tuples))-1)/Rows)+3))
(extension of above approach to find both row and column in 2d array)
Then combining these three formulas into a query to filter out any blank values for quantity
=ArrayFormula(query(
{hlookup(int((row(indirect("1:"&Tuples))-1)/Rows)+2, {COLUMN(Sheet1!$1:$1);Sheet1!$1:$1},2),
vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$A},2),
vlookup(mod(row(indirect("1:"&Tuples))-1,Rows)+2,{row(Sheet1!$A:$A),Sheet1!$A:$Z},int((row(indirect("1:"&Tuples))-1)/Rows)+3)},
"select * where Col3 is not null"))
Note
The named ranges Rows and Cols are obtained from the first column and row of the data using counta and Tuples is their product. The separate formulas
=counta(Sheet1!A:A)
=counta(Sheet1!1:1)
and
=counta(Sheet1!A:A)*counta(Sheet1!1:1)
could be included in the main formula if required with some loss of readability.
For reference, here is the 'standard' split/join solution (with 50K data limit) adapted for the present situation:
=ArrayFormula(split(transpose(split(textjoin("♫",true,transpose(if(Sheet1!B2:Z="","",Sheet1!B1:1&"♪"&Sheet1!A2:A&"♪"&Sheet1!B2:Z))),"♫")),"♪"))
This is also fairly slow (processing 2401 array elements). If you restrict the computation to the actual dimensions of the data, it is much faster for small datasets:
=ArrayFormula(split(transpose(split(textjoin("♫",true,transpose(if(Sheet1!B2:index(Sheet1!B2:Z,counta(Sheet1!A:A),counta(Sheet1!1:1))="","",Sheet1!B1:index(Sheet1!B1:1,counta(Sheet1!1:1))&"♪"&Sheet1!A2:index(Sheet1!A2:A,counta(Sheet1!A:A))&"♪"&Sheet1!B2:index(Sheet1!B2:Z,counta(Sheet1!A:A),counta(Sheet1!1:1))))),"♫")),"♪"))
=ARRAYFORMULA({"Customer", "Product", "Qty";
QUERY(TRIM(SPLIT(TRANSPOSE(SPLIT(TRANSPOSE(QUERY(TRANSPOSE(QUERY(TRANSPOSE(
IF(B2:Z<>"", B1:1&"♠"&A2:A&"♠"&B2:Z&"♦", )), , 999^99)), , 999^99)), "♦")), "♠")),
"where Col1<>'' order by Col1")})
Here another alternative:
=arrayformula
(
{ "PRODUCT","CUSTOMER","QTY";
split
( transpose ( split
( textjoin("✫" ,false,filter(Sheet2!A2:A,Sheet2!A2:A<>"") & "✤" &
filter(Sheet2!B1:1,Sheet2!B1:1<>""))
,"✫",true,false)),"✤",true,false
),
transpose ( split ( textjoin ( "✤", false, transpose ( filter
(
indirect( "Sheet2!B2:" & MID(address(1,COUNTA( Sheet2!B1:1)+1), 2,
FIND("$",address(1,COUNTA( Sheet2!B1:1)+1),2)-2)
)
, Sheet2!A2:A<>""
))),"✤",true,false)
)
}
)
Explanation:
1. "PRODUCT","CUSTOMER","QTY"
-- Use for giving title
2. split
( transpose ( split
( textjoin("✫" ,false,filter(Sheet2!A2:A,Sheet2!A2:A<>"") & "✤" &
filter(Sheet2!B1:1,Sheet2!B1:1<>""))
,"✫",true,false)),"✤",true,false
)
-- Use for distributing Row1 and ColumnA, to be Product and Customer Columns
3. transpose ( split ( textjoin ( "✤", false, transpose ( filter
(
indirect( "Sheet2!B2:" & MID(address(1,COUNTA( Sheet2!B1:1)+1), 2,
FIND("$",address(1,COUNTA( Sheet2!B1:1)+1),2)-2)
)
, Sheet2!A2:A<>""
))),"✤",true,false)
)
--use to distributed data qty to Qty Column
Sheet2 Pict:
Result Sheet Pict:
Input Sheet
This function will handle many customers and many products and it will sum the quantities of multiple customer/product entries and summarize it into one simple table.
The Code:
function rPVT() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var osh=ss.getSheetByName('Sheet2');
osh.clearContents();
var vA=sh.getDataRange().getValues();
var itoh={};
var pObj={};
vA[0].forEach(function(h,i){if(h){itoh[i]=h;}});
for(var i=1;i<vA.length;i++) {
for(var j=1;j<vA[i].length;j++) {
if(!pObj.hasOwnProperty(itoh[j])){pObj[itoh[j]]={};}
if(!pObj[itoh[j]].hasOwnProperty(vA[i][0])){pObj[itoh[j]][vA[i][0]]=vA[i][j];}else{pObj[itoh[j]][vA[i][0]]+=(vA[i][j]);}
}
}
var oA=[['Customer','Product','Quantity']];
Object.keys(pObj).forEach(function(ik){Object.keys(pObj[ik]).forEach(function(jk){oA.push([ik,jk,pObj[ik][jk]]);});});
osh.getRange(1,1,oA.length,oA[0].length).setValues(oA);
}
Output Sheet:
The following function reads Sheet2 which is the output of the above function and returns it to the original format.
function PVT() {
var ss=SpreadsheetApp.getActive();
var sh2=ss.getSheetByName('Sheet2');
var sh3=ss.getSheetByName('Sheet3');
sh3.clearContents();
var vA=sh2.getRange(2,1,sh2.getLastRow()-1,sh2.getLastColumn()).getValues();
pObj={};
vA.forEach(function(r,i){if(!pObj.hasOwnProperty(r[1])){pObj[r[1]]={};}if(!pObj[r[1]].hasOwnProperty(r[0])){pObj[r[1]][r[0]]=r[2];}else{pObj[r[1]][r[0]]+=r[2];}});
var oA=[];
var ikeys=Object.keys(pObj);
var jkeys=Object.keys(pObj[ikeys[0]]);
var hkeys=jkeys.slice();
hkeys.unshift('');
oA.push(hkeys);
ikeys.forEach(function(ik,i){var row=[];row.push(ik);jkeys.forEach(function(jk,j){row.push(pObj[ik][jk]);});oA.push(row);});
sh3.getRange(1,1,oA.length,oA[0].length).setValues(oA);
}
If your data has a single unique key column, this spreadsheet may have what you need.
Your unpivot sheet will contain:
The key column =OFFSET(data!$A$1,INT((ROW()-2)/5)+1,0)
The column header column =OFFSET(data!$A$1,0,IF(MOD(ROW()-1,5)=0,5,MOD(ROW()-1,5)))
The cell value column =INDEX(data!$A$1:$F$100,MATCH(A2,data!$A$1:$A$100,FALSE),MATCH(B2,data!$A$1:$F$1,FALSE))
where 5 is the number of columns to unpivot.
I did not make the spreadsheet. I happened across it in the same search that led me to this question.
One range refrence
This will work regardless of the number of customers and products. with one range reference in this case (A1:D4)
=ArrayFormula({SPLIT("Customer|Product|Qty","|");
QUERY(LAMBDA(r,SPLIT(FLATTEN(
QUERY({r}, " Select Col1 ", 1)&"+"&
QUERY({r}, " select "& TEXTJOIN(",",1,REGEXREPLACE("Col#", "#", SEQUENCE(COLUMNS(QUERY(r, " select * limit 0 ", 1))-1,1,2,1)&""))&" limit 0 ", 1)&"+"&
QUERY({QUERY({r}, " Select "& TEXTJOIN(",",1,REGEXREPLACE("Col#", "#", SEQUENCE(COLUMNS(QUERY({r}, " select * where Col1 <> '' ", 1))-1,1,2,1)&""))&" ", 0)},
" Select * where Col1 is not null ")),"+"))(A1:D4)," Select * Where Col2 <> '' ")})
Demonstration
This woks well when you have this table "on the left" as an output of another formula.
in this case simulated with the range A1:G15
20 columns and hundreds of rows
Named function
Pending...
Used formulas help
ARRAYFORMULA - SPLIT - QUERY - LAMBDA - FLATTEN - TEXTJOINREGEXREPLACE - SEQUENCE - COLUMNS - NOT

How to collapse sheets query result into a single cell

I have a sheets query that almost does what I want but I need a bit of help to get to the last step.
=QUERY(Sales!$A$2:$C,"SELECT B, SUM(C)
WHERE A='"&B3&"'
GROUP BY B
ORDER BY SUM(C) DESC
LIMIT 3
LABEL SUM(C) ''
FORMAT SUM(C) '$##,##0' "
,0)
This gives me the result in C3:D4. What I want is in E3.
I have two goals. First output the data stacked and joined like in cell E3. Second, the ideal solution is an array formula in C3 that does this for all the 'Partners'.
Sample Data
As always thanks in advance for the assistance and education!
Here is how I solved it. Maybe not the optimal solution because I need to duplicate the calculation, but it does exactly what I need. I've added my solution to the example data.
=REGEXREPLACE(REGEXREPLACE(textjoin(" ♦ ",0,
QUERY({Sales!$A$2:$A,Sales!$B$2:$B,Sales!$C$2:$C},"SELECT Col2, SUM(Col3)
WHERE Col1='"&B3&"'
GROUP BY Col2
ORDER BY SUM(Col3) DESC
LIMIT 3
LABEL SUM(Col3) ''
FORMAT SUM(Col3) '$##,##0' "
,0)
)," ♦ \$"," = \$")," ♦ ",CHAR(10))
One way to accomplish this is creating a custom function in Google Apps Script. To achieve this, follow these steps:
In your spreadsheet, select Tools > Script editor to open a script bound to your file.
Copy this function in the script editor, and save the project:
function GET_TOP_CUSTOMERS(partners) {
const sheet = SpreadsheetApp.getActive().getSheetByName("Sales");
const data = sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues();
return partners.map(row => row[0]).map(partner => {
const partnerRows = data.filter(row => row[0] === partner);
const uniqueCustomers = [...new Set(partnerRows.map(row => row[1]))];
const topCustomers = uniqueCustomers.map(customer => {
return [customer, partnerRows.filter(row => row[1] === customer)
.reduce((acc, current) => {
return acc + current[2] }, 0)];
}).sort((a,b) => b[1] - a[1]).slice(0, 3);
return topCustomers.map(customer => customer[0] + "; $" + customer[1].toFixed())
.join("\n");
});
}
Now, if you go back to your spreadsheet, you can use this function like any in-built one. You just have to provide the appropriate range (in this case it would be B3:B12), as you can see here:
Reference:
Custom Functions in Google Sheets

Resources