Google sheet array formula with Filter & Join - google-sheets

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])```

Related

How to use dynamic array formula to filter and format data

I am facing a problem related to the dynamic array.
I have data in the below format.
And I want to convert to this format.
Here is the sheet link.
I am using this formula to filter Fruits category.
={FILTER(A5:D11,B5:B11="Fruits");SUM( FILTER(D5:D11,B5:B11="Fruits"))}
But it gives this error
In ARRAY_LITERAL, an Array Literal was missing values for one or more rows
NOTE: Data should be pulled dynamically from the formula, as the data may change.
To build the result table without hard coding category names in the formula, use the recently introduced lambda functions, like this:
={
lambda(
data, categories, headers, totalsHeader, blankRow, selectPrice,
reduce(
headers, query(unique(categories), "where Col1 is not null", 0),
lambda(
resultTable, filterKey,
{
resultTable;
lambda(
filterData,
{
filterData;
{ totalsHeader, query(filterData, selectPrice, 0) };
blankRow
}
)(filter(data, categories = filterKey))
}
)
)
)(
B5:D,
B5:B,
B4:D4,
{ "", "Total:" },
{ "", "", "" },
"select sum(Col3) label sum(Col3) '' "
);
{ "", "Grand Total:", sum(D5:D) }
}
See { array expressions }, filter(), query(), reduce() and lambda().
The formula will repeat each category name on several rows. If they get in the way, you can hide them from view by using a conditional formatting custom formula rule.
I did some tests to add all the information in just one formula. It will change the format you want, but it will still divide all the information.
Here is the formula:
={"Fruits:","";QUERY(B5:D,"select C, D where B ='Fruits'");
{"Total:",SUMIF(B5:D,"Fruits",D5:D)};"","";
"Vegetables:","";QUERY(B5:D,"select C, D where B ='Vegetables'");
{"Total:",SUMIF(B5:D,"Vegetables",D5:D);"","";
"condiments:","";QUERY(B5:D,"select C, D where B ='condiments'");
{"Total:",SUMIF(B5:D,"condiments",D5:D)};"","";
"Grand Total:",SUM(D5:D)}}
Note:
I added : and the end of each category in the formula so they will look like Fruits: and the table will look like this:
The formula opens with { to open an array in Google Sheets, and you use , to separate columns to write a row of data, and ; to separate the rows to help you write a column of data. After that, you use } to close the array. For example:
{"1","2";"3","4"}
It will print:
So basically, I organize the data with arrays of the same amounts of columns. The first one with part
= { => To open the array.
"Fruits:",""; => This create a cell with "Fruits:" + an empty cell.
QUERY(B5:D,"select C, D where B ='Fruits'"); => which is
already on an array of 2 columns.
{"Total:",SUMIF(B5:D,"Fruits",D5:D)}; => Creates the "Total" cell + the sum
of values that has Fruits in column B.
"",""; => Which will create an empty row to separate the information
for the next set of arrays.
You do the same pattern for the other categories.
} => to end the initial array.
You can add a "Conditional formatting" that will change the text with : to bold automatically.
Reference:
QUERY function
SUMIF
ARRAYFORMULA
I suggest you read on: https://stackoverflow.com/a/58042211/5632629
the first part of your formula outputs a grid of 4×3 cells
the second part of your formula outputs a single cell
if you want to combine it properly use:
={FILTER(A5:D11, B5:B11="Fruits");
{"","","Totals",SUM(FILTER(D5:D11, B5:B11="Fruits"))}}
or:
={FILTER(B5:D11, B5:B11="Fruits");
{"","Totals",SUM(FILTER(D5:D11, B5:B11="Fruits"))}}

How to convert query to arrayformula

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

Google Sheet IFS combine with FILTER Function

I have the following Google Seet Table
Old New New2
W01
W02 W04
W03 W05 W06
I want to create a formular that transforms the table to this one
Old New
W02 W04
W03 W05
W05 W06
So any time a switch from Old to New or New to New2 happens should be displayed.
I wrote the following formular but i always get an error:
= IFS(B1 = "";""; AND(NOT(B1 = ""); NICHT(C1 = ""));FILTER({A1\ B1}; NICHT(A1=""));NICHT(B1 = "");FILTER({B1\ C1}; NICHT(B1="")))
Has anybody an idea?
Concatenate the results of two Query calls:
={
QUERY(A1:B4,
"select A,B where B<>''");
QUERY(B1:C4,
"select B,C where C<>'' label B '', C ''", 1)
}
or in German locale:
={
QUERY(A1:B4;
"select A,B where B<>''");
QUERY(B1:C4;
"select B,C where C<>'' label B '', C ''"; 1)
}
The label statements in the second query are necessary to suppress the column labels since you want to treat certain columns in New1 as Old.

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

How can I combine multiple functions with ArrayFormula

I've the below formula using ImportRange and Query along with Join and Split working correctly:
=join(" / ", QUERY(IMPORTRANGE("Google-Sheet-ID","RawData!A:AC"),"select Col25 where Col1 = " & JOIN(" OR Col1 = ", split(V2:V,"+")), 0))
Also, I've the below ArrayFormula with Split function working smoothly:
=ARRAYFORMULA(if(len(V2:V)=0,,split(V2:V,"+")))
But When I tried combining them together using the below formula:
=ARRAYFORMULA(if(len(V2:V)=0,,join(" / ", QUERY(IMPORTRANGE("Google-Sheet-ID","RawData!A:AC"),"select Col25 where Col1 = " & JOIN(" OR Col1 = ", split(V2:V,"+")), 0))))
It failed, and gave me the below error:
Error
Function SPLIT parameter 1 value should be non-empty.
Here is my sheet for your testing.
UPDATE
I changed it to:
=ARRAYFORMULA(if(len(C2:C)=0,,JOIN(" OR Col1 = ", ARRAYFORMULA(if(len(C2:C)=0,,split(C2:C,"+"))))))
So my full formula is:
=ARRAYFORMULA(
if(
len(C2:C)=0,,
join(" / ",
QUERY(
IMPORTRANGE("14iNSavtvjRU0XipPWIMKyHNwXTA85P_CafFTsIPHI6c","RawData!A:AC"),"select Col25 where Col1 = " &
ARRAYFORMULA(
if(len(C2:C)=0,,
JOIN(" OR Col1 = ",
ARRAYFORMULA(
if(
len(C2:C)=0,,split(C2:C,"+")
)
)
)
)
),
0
))))
And now getting the error:
Error
JOIN range must be a single row or a single column.
I believe this formula on the tab called MK.Testing will pull the info you're hoping for.
=QUERY(IMPORTRANGE("14iNSavtvjRU0XipPWIMKyHNwXTA85P_CafFTsIPHI6c","RawData!A:AC"),"select Col25 where Col1="&TEXTJOIN(" or Col1=",TRUE,A2:A))
I think you might have been overcomplicating things? This formula just forms a text string out of the shipment IDs to use in a query. one thing that may be tripping you up is that query() is very particular about the type of data in a column. Your shipment IDs can be numbers, or they can be number letter combos, but not both. That is, if you have some shipment IDs that contain letters and others that don't, it will be more difficult to get a query that works. (though not impossible). For the sake of helping you though, it's important that your sample IDs reflect the real ones in this way as accurately as possible.
How about doing this with Apps Script? You can get the values from the Sheet2, Shipment Ids, and the Ids from MK.Testing and compare them. If they coincide, the you copy the ETA into the Column C of MK. Testing:
function myFunction() {
var sprsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet2 = sprsheet.getSheetByName("Sheet2");
var mkTesting = sprsheet.getSheetByName("MK.Testing");
var shipmentId = sheet2.getRange("A2:A").getValues();
var idList = mkTesting.getRange("A2:A").getValues();
for (var i = 0; i < shipmentId.length; i++){
for (var j = 0; j < idList.length; j++){
if (idList[j][0] == ""){break;} //Stops if there is an empty cell in Mk.Testing's column A
if (idList[j][0] === shipmentId[i][0]){
var eta = sheet2.getRange("E"+(i+2)).getValue();
mkTesting.getRange("C"+(j+2)).setValue(eta);
}
}
}
}
References:
SpreadsheetApp Class
Range Class

Resources