Good day! I have filtered out values I do not want from my table. I wish to delete them and move rows up. I am getting error on delete. Any advice where I go wrong?
let table1 = workbook.getTable("Table1");
table1.getColumnByName("Column172").getFilter().applyValuesFilter(["1", "2", "3"]);
let visibleTableRange = table1.getRange().getVisibleView();
let visibleTableRangeValues = visibleTableRange.getValues();
let stuff = report.getRangeByIndexes(0, 0,
visibleTableRange.getRowCount(), visibleTableRange.getColumnCount());
report.getRange("stuff").delete(ExcelScript.DeleteShiftDirection.up);
It errors out on the alst line of code.
You can try the code below:
function main(workbook: ExcelScript.Workbook) {
let sh: ExcelScript.Worksheet = workbook.getWorksheet("Report")
let table1 = workbook.getTable("Table1");
table1.getColumnByName("Column172").getFilter().applyValuesFilter(["1", "2", "3"]);
let visibleRows = table1.getRangeBetweenHeaderAndTotal().getVisibleView().getRows()
let firstVisibleRow = visibleRows[0].getRange().getRowIndex() + 1
let lastVisibleRow = visibleRows[visibleRows.length-1].getRange().getRowIndex() + 1
sh.getRange(`${firstVisibleRow}:${lastVisibleRow}`).delete(ExcelScript.DeleteShiftDirection.up)
table1.getColumnByName("Column172").getFilter().clear()
}
The code works by getting the row index of the first row in the table's visible range. And the row index of the last row in the table's visible range. With those two row numbers, you can use the getRange() with the worksheet to delete the row ranges.
One thing to note about this is that everything within the row range of the visible range is deleted. So if you have adjacent data next to the table that's located in the visible range, it will be deleted as well. So just an FYI.
Related
I hope someone can help me. Currently I have a table in which I need to move up or erase the first null of the last column only. This will let me to have all data in the same row.
enter image description here
I used conditional columns but I dont know how to refer the next row.
= Table.AddColumn(#"Changed Type", "Custom", each if [Column5] = null then [Column5] else 1)
enter image description here
This moves the rows up one in the last column.
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
x = {List.Combine({List.RemoveFirstN(List.Last(Table.ToColumns(Source)),1),{null}})},
y = List.RemoveLastN(Table.ToColumns(Source),1),
custom = Table.FromColumns(List.Combine({y,x}),Table.ColumnNames(Source))
in custom
This adds a new column with the former last column shifted up one row
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
columnname=List.Last(Table.ColumnNames(Source)),
shiftedList = List.RemoveFirstN(Table.Column(Source,columnname),1) & {null},
custom1 = Table.ToColumns(Source) & {shiftedList},
custom2 = Table.FromColumns(custom1,Table.ColumnNames(Source) & {"ShiftedRow"})
in custom2
After a long search, I found the below M code to reference data in a sheet and use it as a source for my query, Data is found in sheet 1 in the same workbook that contains my queries and data is a simple XLS report exported from SAP. the reason i don't use table, that when people use the sheet they may paste the SAP exported data in the sheet without using table.
please let me know if it is reliable and won't cause errors.
also how to change the below first line to let it use my first sheet in the current workbook as a source instead of a workbook from a folder path as I don't need that.
let
Source = Excel.Workbook(File.Contents("C:\...\Downloads\Test.xlsx"), null, true),
Sheet1_Sheet = Source{[Item="SAP",Kind="Sheet"]}[Data],
fTrimTable = (tbl as table, header as text) =>
let
t = Table.Buffer(tbl),
columns = List.Buffer(Table.ColumnNames(t)),
rowsToCheck = 100,
Column = List.Select(columns, each List.PositionOf(List.FirstN(Table.Column(t, _),rowsToCheck), header)>0){0},
Row = List.PositionOf(Table.Column(t, Column), header),
ScrollRows = Table.RemoveFirstN (t, Row),
ScrollColumns = Table.SelectColumns(ScrollRows, List.RemoveFirstN(columns, List.PositionOf(columns, Column))),
#"Promoted Headers" = Table.PromoteHeaders(ScrollColumns, [PromoteAllScalars=true])
in
#"Promoted Headers",
Trimmed = fTrimTable(Sheet1_Sheet, "Header100")
A looking for a way to get specific columns by name from several tables. My data comes in several sheets with different number of columns upto 38 columns so i cannot use getColumnById. I only need 7 columns from this.
First am converting all sheet ranges to tables, then am getting all tables.
What I want is to get specific columns by names and merge all into one table on a new sheet.
I followed example from Docs but am stuck at getting column name for each Table.
I know my header Values, shown in example below.
function main(workbook: ExcelScript.Workbook) {
let sheets = workbook.getWorksheets();
for (let sheet of sheets) {
sheet.getTables()[0].convertToRange();
sheet.addTable(sheet.getRange('A1').getUsedRange().getAddress(),true)
}
workbook.getWorksheet('Combined')?.delete();
const newSheet = workbook.addWorksheet('Combined');
const tables = workbook.getTables();
const headerValues = [['Column1', 'Column6', 'Column8', 'Column9','Column11', 'Column16', 'Column18', 'Column19']];
const targetRange = newSheet.getRange('A1').getResizedRange(headerValues.length - 1, headerValues[0].length - 1);
targetRange.setValues(headerValues);
const combinedTable = newSheet.addTable(targetRange.getAddress(), true);
for (let table of tables) {
let dataValues = table.getColumnByName( // this where am stuck //).getRangeBetweenHeaderAndTotal().getTexts();
let rowCount = table.getRowCount();
// If the table is not empty, add its rows to the combined table.
if (rowCount > 0) {
combinedTable.addRows(-1, dataValues);
}
}
}
Thanks for your help.
George
A few things:
In most circumstances for this scenario, I'd recommend iterating
through a specific set of table objects. Unfortunately, that's
difficult to do here. Every time you unlink and recreate a new table,
Excel may give your table a new name. That makes it difficult to
work with the table. You can get around this in your code by
capturing the table name before you unlink it, unlinking the table,
recreating the table, and setting the table name to the original one
captured. If you go that route then you could reliably work with the
table names
Because table names in this scenario can be a bit tricky, I'm going
to use the sheet names so that I can work with the sheets that contain
the underlying tables. This will allow us to use and get data from the
tables regardless of what they're named in the sheets.
Please see my code below:
function main(workbook: ExcelScript.Workbook) {
//JSON object called SheetAndColumnNames. On the left hand side is the sheet name.
//On the right hand side is an array with the column names for the table in the sheet.
//NOTE: replace the sheet and column names with your own values
let columnNames : string[] = ["ColA","ColB","ColC"]
const sheetAndColumnNames = {
"Sheet1": columnNames,
"Sheet2": columnNames
}
//JSON object called columnNamesAndCellValues. On the left hand side is the column name.
//On the right hand side is an array that will hold the values for the column in the table.
//NOTE: replace these column names with your own values
const columnNamesAndCellValues = {
"ColA": [],
"ColB": [],
"ColC": []
}
//Iterate through the sheetAndColumnNames object
for (let sheetName in sheetAndColumnNames) {
//Use sheet name from JSON object to get sheet
let sheet: ExcelScript.Worksheet = workbook.getWorksheet(sheetName)
//get table from the previously assigned sheet
let table: ExcelScript.Table = sheet.getTables()[0]
//get array of column names to be iterated on the sheet
let tableColumnNames: string[] = sheetAndColumnNames[sheetName]
//Iterate the array of table column names
tableColumnNames.forEach(columnName=> {
//get the dataBodyRange of the tableColumn
let tableColumn : ExcelScript.Range = table.getColumn(columnName).getRangeBetweenHeaderAndTotal()
//iterate through all of the values in the table column and add them to the columnNamesAndCellValues array for that column name
tableColumn.getValues().forEach(value=>{
columnNamesAndCellValues[columnName].push(value)
})
})
}
//Delete previous worksheet named Combined
workbook.getWorksheet("Combined")?.delete()
//Add new worksheet named Combined and assign to combinedSheet variable
let combinedSheet : ExcelScript.Worksheet = workbook.addWorksheet("Combined")
//Activate the combined sheet
combinedSheet.activate()
//get the header range for the table
let headerRange : ExcelScript.Range = combinedSheet.getRangeByIndexes(0,0,1,columnNames.length)
//set the header range to the column headers
headerRange.setValues([columnNames])
//iterate through the arrays returned by the columnNamesAndCellValues object to write to the Combined sheet
columnNames.forEach((column,index)=>{
combinedSheet.getRangeByIndexes(1, index, columnNamesAndCellValues[column].length, 1).setValues(columnNamesAndCellValues[column])
})
//Get the address for the current region of the data written from the tableColumnData array to the sheet
let combinedTableAddress : string = combinedSheet.getRange("A1").getSurroundingRegion().getAddress()
//Add the table to the sheet using the address and setting the hasHeaders boolean value to true
combinedSheet.addTable(combinedTableAddress,true)
}
I have a table on a spreadsheet and I want to delete all the existing data. I use the code below which works except when the table is already empty.
// Get the row count
let rowCount = table.getRangeBetweenHeaderAndTotal().getRowCount();
// Delete all the rows
table.deleteRowsAt(0,rowCount);
The problem is that rowCount will return 1 even if the table is empty. When deleteRowsAt tries to delete a row in an empty table it returns an error.
In VBA we can use table.ListRows.Count and this will return 0 if the table is empty.
Example: I have 3 rows in the table
If I select all the rows and delete them from the Table I get this:
This table now has no rows but I have no way to get this result. As I said, in VBA we would use table.ListRows.Count and this would return 0 but I cannot seem to find the equivalent for Office Scripts.
UPDATE:
We now have getRowCount API on the table that you can use to solve this scenario. It'll return the actual rows (not counting the header or expansion row).
// Assuming there's a table in the workbook named Table1
let rowCount = workbook.getTable('Table1').getRowCount(); // Table1
// Assuming there's a table in the workbook
let rowCount = workbook.getTables()[0].getRowCount(); // First table
====
OLD ANSWER
I think we are lacking an API that will provide row count. We'll add that to the backlog.
A workaround is this -
function main(workbook: ExcelScript.Workbook) {
let table = workbook.getTable("Table26");
let rowCount = table.getRangeBetweenHeaderAndTotal().getRowCount();
try {
table.deleteRowsAt(0, rowCount);
} catch (e) {
if (rowCount === 1 && e.code === 'InvalidArgument') {
console.log("This error means there's no row to delete.")
}
}
}
If row count is 1 and the error code is a specific one that's returned when we delete the 'insert-row', then we can ignore the error.
Again, a better approach would be to use an API on the table object to get the row count and delete rows only when row count >= 1. We'll investigate further to improve the experience.
I ran some quick tests deleting 5,000 rows (5 columns, different data types) using deleteRowsAt() and it consistently took 20 to 30 seconds. But ~10,000 rows or more and I got a timeout every time, the script never finished. Calling the script from Flow also gave me gateway timeouts with multiple reattempts before failing.
table.deleteRowsAt(0, rowCount);
But using getRangeBetweenHeaderAndTotal() to reference a range for deletion worked well, 200,000 rows of the same test data in about 2 seconds. The documentation mentions ranges are faster, they certainly are in my tests.
table.getRangeBetweenHeaderAndTotal().delete(0);
I have data which users submit in columns. I am trying to convert them from columnar into stacked rows so a database can read them as single columns, rather than having to pull in X columns to capture the data.
You can see in the Columnar Example screenshot where I am presently, and in the Stacked Example where I want to be.
I can do this in either Google Sheets using App Script - load the data as columnar then convert to stacked and move to BigQuery - OR - use Google Cloud Storage to load the columnar data then use Node to convert to stacked when moving to BigQuery.
Either way, this needs to get into BigQuery as the Stacked Example.
Any ideas on how to do this?
The function below does the following:
Get all data from the sheet where your original data is located (called Source, please change accordingly) with getDataRange.
Remove the headers from the retrieved array and append them to your destination sheet (called Target) with shift, slice and appendRow.
Iterate through the rest of rows with a forEach and, for each row, iterate through each successive group of four columns with a for loop, appending (1) the first for columns of each row and (2) each successive group of four columns in a new row in your destination sheet.
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sourceSheet = ss.getSheetByName('Source'); // Change accordingly
var targetSheet = ss.getSheetByName('Target'); // Change accordingly
targetSheet.clearContents(); // Removes all old content from target sheet before appending new data (remove this if you don't want that)
var sourceValues = sourceSheet.getDataRange().getValues();
var numCols = 4; // Number of desired columns in destination spreadsheet
var headers = sourceValues.shift().slice(0, numCols * 2);
targetSheet.appendRow(headers); // Remove if the destination sheet already has headers
sourceValues.forEach(function(row) { // Iterate through each row in source sheet
for (var i = numCols; i < row.length; i += numCols) { // Iterate through each group of four columns (excluding first four) in each row
var part1 = row.slice(0, numCols); // First four columns
var part2 = row.slice(i, i + numCols); // Each successive group of four
var targetRow = part1.concat(part2); // Concatenate the four first columns with each group of four
targetSheet.appendRow(targetRow); // Append row (8 columns)
}
});
}