Related
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"))}}
Looking for a way to import a long range of columns in to 1 list if a name from a different list matches any name in that particular column. I've tried a couple variations of query, filter, vlookup... Can't seem to find the right combination. Example..
List A on sheet 1 contains the names Jim, John and James. On a separate sheet, there are 5 columns containing names. Column 1 contains Jim, Alex and Ben. Column 2 contains Harold, Bob and Jimmy. Column 3 contains James, Jeremy and Felix. Column 4 contains James, Eric and Evan. Column 5 contains Sara, Jamie and Xavier. The end result should display the list in 1 column to contain the names - Jim, John, James, Alex, Ben, Jeremy, Felix, Eric and Evan. Columns 1, 3 and 4 would be imported to a single list because at least 1 name within those columns matched a name in the original list.
Example sheet.
UPDATED
You can try this sample implementation below using Apps Script custom function. Just copy and paste it to your spreadsheet file as a bound script:
NOTE: This script will check each name listed on the "Main List" & if each of them has matches on multiple columns on the second sheet Sheet2, then it will place all of the names on columns that contains any matched name on the "Imported List". The only catch using this implementation is that it'll run a bit slow when there's a huge amount of data to be processed.
SCRIPT
function onOpen(){ //Runs every time you open the spreadsheet
FINDMATCH();
}
function onEdit(){//Runs every time you make an edit to the sheet
FINDMATCH();
}
function FINDMATCH() { //Function to check each columns on "Sheet2" to see if it has names that match any names on the "Main List"
var main = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var mainList = [].concat.apply([], main.getRange("A4:A").getValues()).filter(String); //Main list data starts at A4
var sheet2 = SpreadsheetApp.getActive().getSheetByName("Sheet2"); //Name of the Sheet2
var result = mainList;
for(col=1; col <= sheet2.getDataRange().getLastColumn(); col++){
// E.G. if you only want to set the maximum of 2 rows as seen below, the "currentCol" code will only scan range A1:F3 on Sheet2 because the number 3 on the third parameter of the getRange() method will be the maximum row to be scanned.
var currentCol = [].concat.apply([], sheet2.getRange(1,col,2,1).getValues()).filter(String);
mainList.forEach(function(list){
currentCol.forEach(function(data){
if(data == list){
result.push(currentCol.toString());
return;
}
});
});
}
var data = FILTER(result.toString().trim().split(","));
main.getRange("AG4:AG").clearContent(); //Clears the old "Imported List" data before pasting updated list of names
main.getRange(4,33,data.length,1).setValues(data); //Updates the "Imported List"
}
function FILTER(array) { //Function that filters duplicate names
var data = array;
var newData = [];
var formattedData = [];
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if (row === newData[j] && row === newData[j]) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
newData.forEach(function(i){
formattedData.push([i]);
});
return formattedData;
}
Sample Result:
Sample Sheet
When spreadsheet gets updated with new data
I have a google sheet like this example to track scores for disc golf:
https://docs.google.com/spreadsheets/d/1uxDFXg2kivZWKICeVklugyXH1OWqsq_s5qXZYzgHkt8/edit?usp=sharing
It works great for tracking day to day scores but it would be really awesome to have a single sheet at the beginning that could say everyones total scores that they have gotten. Also note that the names may not be the same in each sheet.
So in this example I would want to have a new sheet that would automatically calculate the scores from the other sheets and show:
Mike 67,71,65
George 83,70
Phillip 79,72,65
John 66,71
Henry 69
I am very unfamiliar with excel formulas and have been struggling to get this started. Any help would be greatly appreciated.
You can use a Google Apps Script to accomplish what you are looking for. The idea of the code is that it will iterate over every Sheet in your Spreadsheet, gather all the players and all their values, and finally create a summary and put it into the "Summary" sheet (that sheet must exist in your Spreadsheet, with strictly the same name):
function updateSummary() {
var sheets = SpreadsheetApp.getActive().getSheets();
var summarySheet = SpreadsheetApp.getActive().getSheetByName('Summary');
var allScores = {};
for (var i=0; i<sheets.length; i++) {
if (sheets[i].getName() == 'Summary') continue;
var nColumns = sheets[i].getLastColumn();
var names = sheets[i].getRange(1, 1, 1, nColumns).getValues()[0];
var scores = sheets[i].getRange(20, 1, 1, nColumns).getValues()[0];
for (var j=0; j<nColumns; j++) {
var currentName = names[j];
var currentScore = scores[j];
if (!allScores.hasOwnProperty(currentName))
allScores[currentName] = [];
allScores[currentName].push(currentScore);
}
}
summarySheet.clear();
for (var key in allScores) {
var row = [key].concat(allScores[key]);
summarySheet.appendRow(row);
}
}
This will create, with the data given, the following data in the "Summary" Sheet:
Instead, if you prefer to have two columns as you described in your question (with the second one holding every score separated by commas), you would simply need to replace the last for-loop in the code above for the following one:
for (var key in allScores) {
var row = [key].concat(allScores[key].join(','));
summarySheet.appendRow(row);
}
Finally, you can create an image in the "Summary" sheet which can serve as a button to run the script. To do so:
Within your Sheet, click on Insert>Image>Image over cells.
Select any image of your choice.
Select the newly created image and click on the three dots icon that appears on the top-right corner of the image.
Click on "Assign script" and put the function name (in this case, updateSummary) and click on OK.
try:
=QUERY(TRANSPOSE({
'MikeGeorgePhillipJohn 121519'!A1:D20,
'MikeGeorgePhillipJohn 122019'!A1:D20,
'MikeJosephPhillipHenry 122719'!A1:D20}),
"select Col1,sum(Col20)
where Col1 is not null
group by Col1
label sum(Col20)''", 0)
=ARRAYFORMULA(SPLIT(TRANSPOSE(QUERY(QUERY(TRANSPOSE({
'MikeGeorgePhillipJohn 121519'!A1:D20,
'MikeGeorgePhillipJohn 122019'!A1:D20,
'MikeJosephPhillipHenry 122719'!A1:D20}),
"select max(Col20)
where Col1 is not null
group by Col20
pivot Col1", 0),,999^99)), " "))
I want to generate excel sheet from EPPluse which has group column header like below picture.Then How to build DataTable which handle this situation. please help me to get it done easily.
Thanks in advance
You want to use the Merge property on the ExcelRange object.
Here's an example:
using (var pck = new ExcelPackage(new FileInfo(#"c:\temp\Book1.xlsx")))
{
var ws = pck.Workbook.Worksheets["Sheet1"] ?? pck.Workbook.Worksheets.Add("Sheet1");
var rng = ws.Cells["BO1:BQ1"];
rng.Merge = true;
rng.Value = "Answer for Att 3";
pck.Save();
}
You can use this code to write your group headers, then use ExcelRange's LoadFromDataTable() method to write your DataTable directly to the worksheet starting from cell A2
I have a complex Google Sheet query that works great except when a Google Sheet doesn't have as many columns as I use in my formula.
Here's what the formula looks like now:
=sum(filter(query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='"&C2&"'",0),query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='PROJECT'",0) >=date(2017,1,1),query(INDIRECT("'" & A2 & "'!$A$7:$23"),"select Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH where B='PROJECT'",0) <=date(2017,12,31)))
It works great. But the problem is I run it against many worksheets and some don't have e.g. column AG,AH and end at AF at which point I get an error.
So what I need is a way to generate the string Q,R,S....[Name of Last Column in Sheet] and then I can use that instead of my hard-coded Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH but I cannot figure out how to do that.
Any help is greatly appreciated. Thanks!
Per comments above, final formula was:
LEFT("Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,AZ,BA,BB,BC ",2*Columns(INDIRECT(A2&"!1:1"))-33+IF(Columns(INDIRECT(A2&"!1:1"))>26,Columns(INDIRECT(A2&"!1:1"))-26,0))
where column A contains the list of worksheets (tabs) in the Google Sheet. Put this in B2, and then copied it down. I am not marking this as the correct answer since others gave a correct formula-based answer but this did the trick for me.
This can be done with built-in functions:
On a helper sheet, let say you name it, helper, fill up range with letters A to Z, let say A1:A26
Let say that on B1 you write the following formula:
=ArrayFormula({A1:A26;TRANSPOSE(SPLIT(JOIN(",",SUBSTITUTE(QUERY(TRANSPOSE(A1:A26)&A1:A26,,27)," ",",")),","))}) . This will create a list of column letter headers.
On each new worksheet use columns(1:1) to get the total number of columns.
To get your string of column headers, then you could use something like :
JOIN(",",OFFSET(helper!B1,16,0,columns(1:1)-16))
QUERY(helper!B:B,"select B limit "&columns(1:1)-7&" offset 7")
NOTE:
If you decide to have only one helper sheet and use it on several spreadsheets, then use
QUERY(IMPORTRANGE(your_url,"helper!B:B"),"select Col1 limit "&columns(1:1)-7&" offset 7")
This can be done with script. Without seeing you spreadsheet, it is hard to know exactly what you need, but this should be close. I get the variables from Sheet1 and return the formula to Sheet1. Adjust the sheet name to fit your needs. This will look at your data sheets based on the variable sheet name determine the last column. Determine the column letters and build the string the query needs. It then sets the new query formula. I added a menu to run it from.
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().addMenu(
'Create Data', [
{ name: 'Run', functionName: 'formula' },
]);
}
function formula(){
var ss= SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheetByName("Sheet1") //sheet where variables are
var sheet=s.getRange("A2").getValue()//variable sheet name
var sel=makeString(sheet) //get the select string of column letters
//Create formula and return to Sheet1 A3
var f= s.getRange("A3").setFormula('=sum(filter(query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'"&C2&"\'",0),query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'PROJECT\'",0) >=date(2017,1,1),query(INDIRECT("\'" & A2 & "\'!$A$7:$23"),"select '+sel+' where B=\'PROJECT\'",0) <=date(2017,12,31)))')
}
function makeString(sht){
var ss= SpreadsheetApp.getActiveSpreadsheet()
var s=ss.getSheetByName(sht)
var lc=s.getLastColumn()
var rng=s.getRange(1, 17, 1, lc).getValues()
var str=''
var ltr=[]
for(var i=17;i<rng[0].length+1;i++){
ltr[i]= columnToLetter(i)
str=ltr.join(',')
}
var str1=str.substr(17)
return str1
}
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
Let me know if you have any questions.