Yes, you can automate Excel without writing a single line of VBA. But if you’re reaching for Power Query or scripting before mastering Dynamic Arrays and spill ranges, you’re adding complexity that solves problems you don’t have yet.
Quick Answer
The fastest, safest, and most maintainable way to automate Excel is with dynamic array formulas (FILTER, SORT, UNIQUE, SEQUENCE) combined with structured references in Excel Tables — especially when paired with XLOOKUP and LET. This approach recalculates instantly, requires zero manual refreshes, and survives copy-paste, row insertions, and sheet renaming better than any macro or Power Query flow.
All the Methods
| Method | Steps | Best For | Limitations |
|---|---|---|---|
| Dynamic Array Formulas | Enter =FILTER(A2:C100,B2:B100>50000), press Enter | Real-time dashboards, live reports, sales pipelines | Requires Excel 365 or Excel 2021+ |
| Power Query (Get & Transform) | Data → Get Data → From Table/Range → Apply steps → Close & Load | Cleaning messy CSVs, merging 12+ sources, scheduled refreshes | Output lives in new sheet unless loaded to Data Model; no cell-level control |
| Excel Macros (VBA) | Alt + F11 → Insert Module → Paste code → Run with Alt + F8 | Repetitive formatting, email triggers, legacy system exports | Breaks on macro security changes; not portable across Mac/Windows |
| IF + INDIRECT + Named Ranges | Define Name "SalesData" =Sheet1!$A$2:$D$200 → Use =INDIRECT("SalesData") | Cross-sheet reports where source range changes weekly | Volatile — slows large workbooks; breaks on sheet rename unless named properly |
| Office Scripts (Web/Online only) | Automate tab → Record actions → Edit TypeScript → Run from button | Teams-integrated workflows, Excel Online users | No desktop support; limited object model vs VBA |
| AutoFill + Flash Fill (Ctrl+E) | Type first result → Select cell + Ctrl+E → Excel infers pattern | Splitting names, cleaning phone numbers, standardizing titles | One-time transform only — no live update or dependency chain |
Method 1 Deep Dive
Let’s say your Sales team logs deals in a table starting at A1:
| Name | Company | Deal Size ($) | Close Date |
|---|---|---|---|
| Sarah Chen | Acme Corp | $45,200 | 2024-03-15 |
| Diego Morales | Nexus Labs | $89,700 | 2024-04-02 |
| Priya Patel | Stellar Inc | $12,400 | 2024-02-28 |
| Marcus Lee | Veridian Systems | $67,900 | 2024-03-22 |
| Anya Dubois | Lumina Group | $31,100 | 2024-04-10 |
You need an auto-updating list of all deals over $50,000, sorted by size, with company name and close date — and it must grow/shrink as new rows are added to the source.
Here’s how: First, convert your raw data into an Excel Table (Ctrl+T). Name it SalesLog. Then go to cell G1 and type:
=SORT(FILTER(SalesLog,{1,1,0,1}),3,-1)
This spills results into G1:I6 automatically. The {1,1,0,1} tells FILTER to return columns 1 (Name), 2 (Company), and 4 (Close Date) — skipping Deal Size. The ,3,-1 sorts by column 3 of the filtered output (which is Close Date) descending.
What makes this elegant is that if someone adds a row to SalesLog tomorrow with $72,000 and a date of 2024-04-18, the spilled range expands — no manual drag, no macro trigger, no refresh button. And because it’s all formula-driven, you can reference G1# elsewhere (e.g., =COUNTA(G1#)) to count active high-value deals.
Surprising tip: Use LET to make this readable and reusable. In H1, try:
=LET(
highDeals, FILTER(SalesLog,INDEX(SalesLog,,3)>50000),
SORT(highDeals,{1,2,4},1)
)
Now column order matches source, and the logic is self-documenting. Change the 50000 to a cell reference like $K$1, and suddenly your threshold is adjustable — no editing formulas.
Method 2 Deep Dive
Power Query isn’t just for ETL — it’s Excel’s most underrated automation engine for recurring imports. Say your finance team drops a new Q2_Spend_Report.csv into C:\Reports\Monthly\ every Friday at 9 a.m. You need to append it to last month’s data, remove duplicates, flag outliers, and load into a pivot-ready table — all before your 10 a.m. standup.
Start with Data → Get Data → From File → From Folder. Point to C:\Reports\Monthly\. In the preview, click the Combine & Load button (not “Transform Data”). That opens Power Query Editor with a sample file already loaded. Click the gear icon next to “Source” in the Applied Steps pane — now you’ll see the actual folder path embedded.
Next, add this custom step: Click Advanced Editor and replace the code with:
let
Source = Folder.Files("C:\Reports\Monthly"),
FilterCSV = Table.SelectRows(Source, each [Extension] = ".csv"),
PromoteHeaders = Table.TransformColumns(FilterCSV, {"Content", each Csv.FromBinary(_, [Delimiter=",", Encoding=1252, QuoteStyle=QuoteStyle.None])}),
ExpandContent = Table.ExpandTableColumn(PromoteHeaders, "Content", {"Vendor", "Amount", "Category", "Date"}),
CleanDates = Table.TransformColumnTypes(ExpandContent,{{"Date", type date}}),
FlagOutliers = Table.AddColumn(CleanDates, "IsOutlier", each [Amount] > List.Median(CleanDates[Amount]) * 2.5),
RemoveDuplicates = Table.Distinct(FlagOutliers, {"Vendor", "Date", "Amount"})
in
RemoveDuplicates
This does five things: filters for CSVs only, promotes headers, cleans dates, flags amounts >2.5× median (a robust outlier check), and dedupes on key fields. No more sorting manually and eyeballing duplicates.
Here’s the counterintuitive part: Don’t click “Close & Load” yet. Instead, click Close & Load To…, choose “Only Create Connection”, and uncheck “Add this data to the Data Model”. Then create a new worksheet and use Data → Existing Connections to insert a Table linked to that query. Why? Because now you can right-click the table → Refresh anytime — or set it to auto-refresh on open via File → Options → Advanced → “Refresh data when opening the file”.
Even better: Press Alt + D + F + F to refresh all queries at once. That’s faster than hunting through tabs.
Cheat Sheet
| Task | Formula / Shortcut | Notes |
|---|---|---|
| Auto-filter top 5 deals | =TAKE(SORT(SalesLog,3,-1),5) |
Spills 5 rows; updates if new data added |
| Refresh all Power Queries | Alt + D + F + F | Works even if queries are hidden or disconnected |
| Extract unique vendors | =UNIQUE(INDEX(SalesLog,,1)) |
Assumes vendor names are in column 1 of table |
| Dynamic running total | =SCAN(0,SalesLog[Deal Size],LAMBDA(a,b,a+b)) |
Spills down entire column; recalculates on edit |
| Find last non-blank row | =XMATCH(TRUE,INDEX(SalesLog[Name],0)<>"",0,-1) |
Returns row number within table — safe inside LET |