@luquimbo/bi-superpowers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- package/src/content/skills/theme-tweaker.md +618 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# Performance Optimization in Power Query
|
|
2
|
+
|
|
3
|
+
Best practices and patterns for optimizing Power Query performance, with emphasis on query folding.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Query Folding Basics
|
|
8
|
+
|
|
9
|
+
### What is Query Folding?
|
|
10
|
+
|
|
11
|
+
Query folding translates Power Query (M) operations into native database queries (SQL). This pushes processing to the data source, reducing data transfer and improving performance.
|
|
12
|
+
|
|
13
|
+
### Check if Step Folds
|
|
14
|
+
|
|
15
|
+
Right-click any step > "View Native Query"
|
|
16
|
+
- If available: Step folds
|
|
17
|
+
- If grayed out: Step breaks folding
|
|
18
|
+
|
|
19
|
+
### Operations That Typically Fold
|
|
20
|
+
|
|
21
|
+
| Operation | SQL Equivalent |
|
|
22
|
+
|-----------|---------------|
|
|
23
|
+
| `Table.SelectRows` | WHERE |
|
|
24
|
+
| `Table.SelectColumns` | SELECT |
|
|
25
|
+
| `Table.Sort` | ORDER BY |
|
|
26
|
+
| `Table.Group` | GROUP BY |
|
|
27
|
+
| `Table.NestedJoin` | JOIN |
|
|
28
|
+
| `Table.Distinct` | DISTINCT |
|
|
29
|
+
| `Table.FirstN` | TOP / LIMIT |
|
|
30
|
+
| `Table.Skip` | OFFSET |
|
|
31
|
+
|
|
32
|
+
### Operations That Break Folding
|
|
33
|
+
|
|
34
|
+
| Operation | Why |
|
|
35
|
+
|-----------|-----|
|
|
36
|
+
| `Table.AddColumn` with custom function | Custom M logic |
|
|
37
|
+
| `Table.Buffer` | Explicit materialization |
|
|
38
|
+
| `Table.FromList` | Memory operations |
|
|
39
|
+
| `List.Generate` | Iterative logic |
|
|
40
|
+
| `Text.Proper`, `Text.Trim` (sometimes) | Source-specific |
|
|
41
|
+
| Accessing other queries | Cross-query dependency |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Optimize Filter Order
|
|
46
|
+
|
|
47
|
+
### Push Filters Early
|
|
48
|
+
|
|
49
|
+
```m
|
|
50
|
+
let
|
|
51
|
+
// GOOD: Filter first, then transform
|
|
52
|
+
Source = Sql.Database("server", "database"),
|
|
53
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
54
|
+
|
|
55
|
+
// Step 1: Filter (folds to WHERE)
|
|
56
|
+
FilteredRows = Table.SelectRows(Sales, each [Year] = 2024),
|
|
57
|
+
|
|
58
|
+
// Step 2: Select columns (folds to SELECT)
|
|
59
|
+
SelectColumns = Table.SelectColumns(FilteredRows, {"Date", "Amount", "CustomerID"}),
|
|
60
|
+
|
|
61
|
+
// Step 3: Transform (may break folding - but on smaller dataset)
|
|
62
|
+
Transformed = Table.TransformColumns(SelectColumns, {{"Amount", each _ * 1.1}})
|
|
63
|
+
in
|
|
64
|
+
Transformed
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Anti-Pattern: Late Filtering
|
|
68
|
+
|
|
69
|
+
```m
|
|
70
|
+
let
|
|
71
|
+
// BAD: Transform first, filter later
|
|
72
|
+
Source = Sql.Database("server", "database"),
|
|
73
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
74
|
+
|
|
75
|
+
// This might break folding
|
|
76
|
+
Transformed = Table.TransformColumns(Sales, {{"Amount", each _ * 1.1}}),
|
|
77
|
+
|
|
78
|
+
// Now filtering happens in memory (slow!)
|
|
79
|
+
FilteredRows = Table.SelectRows(Transformed, each [Year] = 2024)
|
|
80
|
+
in
|
|
81
|
+
FilteredRows
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Column Selection Optimization
|
|
87
|
+
|
|
88
|
+
### Select Only Needed Columns
|
|
89
|
+
|
|
90
|
+
```m
|
|
91
|
+
let
|
|
92
|
+
Source = Sql.Database("server", "database"),
|
|
93
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
94
|
+
|
|
95
|
+
// GOOD: Select columns early
|
|
96
|
+
SelectedColumns = Table.SelectColumns(Sales, {
|
|
97
|
+
"OrderID", "CustomerID", "Amount", "OrderDate"
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
// Remaining operations on fewer columns
|
|
101
|
+
Filtered = Table.SelectRows(SelectedColumns, each [Amount] > 100)
|
|
102
|
+
in
|
|
103
|
+
Filtered
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Remove Unnecessary Columns
|
|
107
|
+
|
|
108
|
+
```m
|
|
109
|
+
let
|
|
110
|
+
Source = YourSource,
|
|
111
|
+
|
|
112
|
+
// Remove columns you don't need
|
|
113
|
+
RemovedColumns = Table.RemoveColumns(Source, {
|
|
114
|
+
"InternalID", "CreatedBy", "ModifiedBy", "Notes"
|
|
115
|
+
})
|
|
116
|
+
in
|
|
117
|
+
RemovedColumns
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Avoid Row-by-Row Operations
|
|
123
|
+
|
|
124
|
+
### Use Native Functions Instead
|
|
125
|
+
|
|
126
|
+
```m
|
|
127
|
+
let
|
|
128
|
+
Source = YourSource,
|
|
129
|
+
|
|
130
|
+
// BAD: Row-by-row with List operations
|
|
131
|
+
BadPattern = Table.AddColumn(Source, "Total",
|
|
132
|
+
each List.Sum({[Price1], [Price2], [Price3]})
|
|
133
|
+
),
|
|
134
|
+
|
|
135
|
+
// GOOD: Direct column reference
|
|
136
|
+
GoodPattern = Table.AddColumn(Source, "Total",
|
|
137
|
+
each [Price1] + [Price2] + [Price3], type number
|
|
138
|
+
)
|
|
139
|
+
in
|
|
140
|
+
GoodPattern
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Bulk Transformations
|
|
144
|
+
|
|
145
|
+
```m
|
|
146
|
+
let
|
|
147
|
+
Source = YourSource,
|
|
148
|
+
|
|
149
|
+
// BAD: Multiple Table.AddColumn calls
|
|
150
|
+
Step1 = Table.AddColumn(Source, "Year", each Date.Year([Date])),
|
|
151
|
+
Step2 = Table.AddColumn(Step1, "Month", each Date.Month([Date])),
|
|
152
|
+
Step3 = Table.AddColumn(Step2, "Day", each Date.Day([Date])),
|
|
153
|
+
|
|
154
|
+
// BETTER: Transform existing columns where possible
|
|
155
|
+
BetterPattern = Table.TransformColumns(Source, {
|
|
156
|
+
{"DateColumn", Date.Year, Int64.Type} // Replaces column with year
|
|
157
|
+
})
|
|
158
|
+
in
|
|
159
|
+
Step3 // or BetterPattern depending on need
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Table.Buffer Usage
|
|
165
|
+
|
|
166
|
+
### When to Buffer
|
|
167
|
+
|
|
168
|
+
```m
|
|
169
|
+
let
|
|
170
|
+
Source = YourSource,
|
|
171
|
+
|
|
172
|
+
// Buffer when table is used multiple times
|
|
173
|
+
BufferedSource = Table.Buffer(Source),
|
|
174
|
+
|
|
175
|
+
// Multiple references to buffered table
|
|
176
|
+
MaxValue = List.Max(BufferedSource[Amount]),
|
|
177
|
+
MinValue = List.Min(BufferedSource[Amount]),
|
|
178
|
+
|
|
179
|
+
Result = Table.AddColumn(BufferedSource, "Normalized",
|
|
180
|
+
each ([Amount] - MinValue) / (MaxValue - MinValue)
|
|
181
|
+
)
|
|
182
|
+
in
|
|
183
|
+
Result
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### When NOT to Buffer
|
|
187
|
+
|
|
188
|
+
```m
|
|
189
|
+
let
|
|
190
|
+
// DON'T buffer if query folds
|
|
191
|
+
Source = Sql.Database("server", "database"),
|
|
192
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
193
|
+
|
|
194
|
+
// This would break folding and load all data
|
|
195
|
+
// AVOID: Buffered = Table.Buffer(Sales)
|
|
196
|
+
|
|
197
|
+
// Let the filter fold instead
|
|
198
|
+
Filtered = Table.SelectRows(Sales, each [Year] = 2024)
|
|
199
|
+
in
|
|
200
|
+
Filtered
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Partitioning Strategies
|
|
206
|
+
|
|
207
|
+
### Process Large Data in Chunks
|
|
208
|
+
|
|
209
|
+
```m
|
|
210
|
+
let
|
|
211
|
+
// Get distinct years for partitioning
|
|
212
|
+
Source = Sql.Database("server", "database"),
|
|
213
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
214
|
+
|
|
215
|
+
Years = List.Distinct(Table.Column(Sales, "Year")),
|
|
216
|
+
|
|
217
|
+
// Process each year separately (folded)
|
|
218
|
+
ProcessYear = (year as number) =>
|
|
219
|
+
let
|
|
220
|
+
Filtered = Table.SelectRows(Sales, each [Year] = year),
|
|
221
|
+
Transformed = Table.TransformColumns(Filtered, {
|
|
222
|
+
{"Amount", each _ * 1.1}
|
|
223
|
+
})
|
|
224
|
+
in
|
|
225
|
+
Transformed,
|
|
226
|
+
|
|
227
|
+
// Combine results
|
|
228
|
+
AllYears = Table.Combine(List.Transform(Years, ProcessYear))
|
|
229
|
+
in
|
|
230
|
+
AllYears
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Incremental Refresh Pattern
|
|
234
|
+
|
|
235
|
+
```m
|
|
236
|
+
let
|
|
237
|
+
// Parameters for incremental refresh
|
|
238
|
+
RangeStart = #datetime(2024, 1, 1, 0, 0, 0),
|
|
239
|
+
RangeEnd = #datetime(2024, 12, 31, 23, 59, 59),
|
|
240
|
+
|
|
241
|
+
Source = Sql.Database("server", "database"),
|
|
242
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
243
|
+
|
|
244
|
+
// Date filter - required for incremental refresh
|
|
245
|
+
FilteredByDate = Table.SelectRows(Sales, each
|
|
246
|
+
[OrderDate] >= RangeStart and [OrderDate] < RangeEnd
|
|
247
|
+
)
|
|
248
|
+
in
|
|
249
|
+
FilteredByDate
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Data Type Optimization
|
|
255
|
+
|
|
256
|
+
### Set Types Early
|
|
257
|
+
|
|
258
|
+
```m
|
|
259
|
+
let
|
|
260
|
+
Source = YourSource,
|
|
261
|
+
|
|
262
|
+
// Set types immediately after source
|
|
263
|
+
TypedTable = Table.TransformColumnTypes(Source, {
|
|
264
|
+
{"OrderID", Int64.Type},
|
|
265
|
+
{"CustomerID", Int64.Type},
|
|
266
|
+
{"Amount", Currency.Type},
|
|
267
|
+
{"OrderDate", type date},
|
|
268
|
+
{"CustomerName", type text}
|
|
269
|
+
})
|
|
270
|
+
in
|
|
271
|
+
TypedTable
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Avoid Implicit Type Conversion
|
|
275
|
+
|
|
276
|
+
```m
|
|
277
|
+
let
|
|
278
|
+
Source = YourSource,
|
|
279
|
+
|
|
280
|
+
// BAD: Implicit conversion in comparison
|
|
281
|
+
BadFilter = Table.SelectRows(Source, each [Year] = "2024"), // Text comparison
|
|
282
|
+
|
|
283
|
+
// GOOD: Explicit types
|
|
284
|
+
GoodFilter = Table.SelectRows(Source, each [Year] = 2024) // Number comparison
|
|
285
|
+
in
|
|
286
|
+
GoodFilter
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Reduce Query Dependencies
|
|
292
|
+
|
|
293
|
+
### Avoid Circular References
|
|
294
|
+
|
|
295
|
+
```m
|
|
296
|
+
// Separate staging and transformation queries
|
|
297
|
+
|
|
298
|
+
// Query: Sales_Staging (disable load)
|
|
299
|
+
let
|
|
300
|
+
Source = Sql.Database("server", "database"),
|
|
301
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
302
|
+
Typed = Table.TransformColumnTypes(Sales, {
|
|
303
|
+
{"Amount", type number},
|
|
304
|
+
{"Date", type date}
|
|
305
|
+
})
|
|
306
|
+
in
|
|
307
|
+
Typed
|
|
308
|
+
|
|
309
|
+
// Query: Sales_Fact (enable load)
|
|
310
|
+
let
|
|
311
|
+
Source = Sales_Staging, // Reference staging query
|
|
312
|
+
Filtered = Table.SelectRows(Source, each [Year] = 2024)
|
|
313
|
+
in
|
|
314
|
+
Filtered
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Use Parameters Instead of Queries
|
|
318
|
+
|
|
319
|
+
```m
|
|
320
|
+
let
|
|
321
|
+
// Reference parameter instead of query
|
|
322
|
+
CurrentYear = CurrentYearParameter, // Defined as parameter
|
|
323
|
+
|
|
324
|
+
Source = Sql.Database("server", "database"),
|
|
325
|
+
Sales = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
326
|
+
|
|
327
|
+
Filtered = Table.SelectRows(Sales, each [Year] = CurrentYear)
|
|
328
|
+
in
|
|
329
|
+
Filtered
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Native SQL When Needed
|
|
335
|
+
|
|
336
|
+
### Use Value.NativeQuery
|
|
337
|
+
|
|
338
|
+
```m
|
|
339
|
+
let
|
|
340
|
+
Source = Sql.Database("server", "database"),
|
|
341
|
+
|
|
342
|
+
// Complex query that's hard to fold
|
|
343
|
+
NativeQuery = Value.NativeQuery(
|
|
344
|
+
Source,
|
|
345
|
+
"SELECT
|
|
346
|
+
CustomerID,
|
|
347
|
+
SUM(Amount) as TotalAmount,
|
|
348
|
+
COUNT(*) as OrderCount
|
|
349
|
+
FROM Sales
|
|
350
|
+
WHERE Year = 2024
|
|
351
|
+
GROUP BY CustomerID
|
|
352
|
+
HAVING SUM(Amount) > 10000"
|
|
353
|
+
)
|
|
354
|
+
in
|
|
355
|
+
NativeQuery
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Parameterized Native Query
|
|
359
|
+
|
|
360
|
+
```m
|
|
361
|
+
let
|
|
362
|
+
Source = Sql.Database("server", "database"),
|
|
363
|
+
|
|
364
|
+
// Using parameter to prevent SQL injection
|
|
365
|
+
YearValue = 2024,
|
|
366
|
+
|
|
367
|
+
Query = Value.NativeQuery(
|
|
368
|
+
Source,
|
|
369
|
+
"SELECT * FROM Sales WHERE Year = @year",
|
|
370
|
+
[year = YearValue]
|
|
371
|
+
)
|
|
372
|
+
in
|
|
373
|
+
Query
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Memory Management
|
|
379
|
+
|
|
380
|
+
### Avoid Large Lists in Memory
|
|
381
|
+
|
|
382
|
+
```m
|
|
383
|
+
let
|
|
384
|
+
Source = YourSource,
|
|
385
|
+
|
|
386
|
+
// BAD: Creates large list in memory
|
|
387
|
+
BadPattern = List.Buffer(Table.Column(Source, "ID")),
|
|
388
|
+
|
|
389
|
+
// GOOD: Process without full materialization
|
|
390
|
+
GoodPattern = Table.SelectRows(Source, each [Status] = "Active")
|
|
391
|
+
in
|
|
392
|
+
GoodPattern
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Stream Processing
|
|
396
|
+
|
|
397
|
+
```m
|
|
398
|
+
let
|
|
399
|
+
// For very large files, enable streaming
|
|
400
|
+
Source = Csv.Document(
|
|
401
|
+
File.Contents("C:\large_file.csv"),
|
|
402
|
+
[Delimiter=",", QuoteStyle=QuoteStyle.None]
|
|
403
|
+
),
|
|
404
|
+
|
|
405
|
+
// First filter - reduces data in memory
|
|
406
|
+
Filtered = Table.SelectRows(Source, each [Status] = "Active"),
|
|
407
|
+
|
|
408
|
+
// Then type
|
|
409
|
+
Typed = Table.TransformColumnTypes(Filtered, {{"Amount", type number}})
|
|
410
|
+
in
|
|
411
|
+
Typed
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Query Diagnostics
|
|
417
|
+
|
|
418
|
+
### Enable Query Diagnostics
|
|
419
|
+
|
|
420
|
+
1. Tools > Options > Diagnostics
|
|
421
|
+
2. Enable "Query Diagnostics"
|
|
422
|
+
3. Tools > Start Diagnostics
|
|
423
|
+
4. Refresh query
|
|
424
|
+
5. Tools > Stop Diagnostics
|
|
425
|
+
6. Analyze the diagnostic tables
|
|
426
|
+
|
|
427
|
+
### Key Metrics to Monitor
|
|
428
|
+
|
|
429
|
+
| Metric | Meaning |
|
|
430
|
+
|--------|---------|
|
|
431
|
+
| Duration | Total time for step |
|
|
432
|
+
| Row Count | Data volume |
|
|
433
|
+
| Data Source Query | Native query sent |
|
|
434
|
+
| Exclusive Duration | Time without dependencies |
|
|
435
|
+
| Resource | Which data source |
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Performance Checklist
|
|
440
|
+
|
|
441
|
+
### Before Deploying
|
|
442
|
+
|
|
443
|
+
- [ ] Filter early in the query
|
|
444
|
+
- [ ] Select only needed columns
|
|
445
|
+
- [ ] Verify query folding (right-click > View Native Query)
|
|
446
|
+
- [ ] Set data types explicitly
|
|
447
|
+
- [ ] Remove or disable staging queries from load
|
|
448
|
+
- [ ] Check for unnecessary Table.Buffer calls
|
|
449
|
+
- [ ] Review query dependencies
|
|
450
|
+
- [ ] Test with production data volumes
|
|
451
|
+
- [ ] Enable query diagnostics for complex queries
|
|
452
|
+
|
|
453
|
+
### Common Issues
|
|
454
|
+
|
|
455
|
+
| Symptom | Likely Cause | Fix |
|
|
456
|
+
|---------|--------------|-----|
|
|
457
|
+
| Slow refresh | Broken folding | Move filters up, simplify transforms |
|
|
458
|
+
| Memory errors | Large data in memory | Use Table.Buffer sparingly, filter early |
|
|
459
|
+
| Timeout | Complex query | Use native SQL, partition data |
|
|
460
|
+
| Slow step | Row-by-row processing | Use bulk operations |
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Folding Quick Reference
|
|
465
|
+
|
|
466
|
+
### Functions That Typically Fold
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
Table.SelectRows
|
|
470
|
+
Table.SelectColumns
|
|
471
|
+
Table.RemoveColumns
|
|
472
|
+
Table.Sort
|
|
473
|
+
Table.Group
|
|
474
|
+
Table.NestedJoin
|
|
475
|
+
Table.Distinct
|
|
476
|
+
Table.FirstN
|
|
477
|
+
Table.Skip
|
|
478
|
+
Table.TransformColumnTypes (basic types)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Functions That Typically Break Folding
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
Table.AddColumn (custom functions)
|
|
485
|
+
Table.Buffer
|
|
486
|
+
Table.FromList
|
|
487
|
+
Table.TransformColumns (complex transforms)
|
|
488
|
+
List.* functions in row context
|
|
489
|
+
Custom M functions
|
|
490
|
+
References to other queries
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Verify Folding
|
|
494
|
+
|
|
495
|
+
```m
|
|
496
|
+
// Add this to see the native query
|
|
497
|
+
let
|
|
498
|
+
Source = Sql.Database("server", "database"),
|
|
499
|
+
Query = Source{[Schema="dbo", Item="Sales"]}[Data],
|
|
500
|
+
Filtered = Table.SelectRows(Query, each [Year] = 2024),
|
|
501
|
+
|
|
502
|
+
// This will show the SQL if folding works
|
|
503
|
+
NativeQuery = Value.NativeQuery(Source, "", [EnableFolding=true])
|
|
504
|
+
in
|
|
505
|
+
Filtered
|
|
506
|
+
```
|