@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.
Files changed (193) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/.mcp.json +25 -0
  3. package/AGENTS.md +244 -0
  4. package/CHANGELOG.md +265 -0
  5. package/LICENSE +21 -0
  6. package/README.md +211 -0
  7. package/bin/build-plugin.js +30 -0
  8. package/bin/cli.js +1064 -0
  9. package/bin/commands/add.js +533 -0
  10. package/bin/commands/add.test.js +77 -0
  11. package/bin/commands/build-desktop.js +166 -0
  12. package/bin/commands/changelog.js +443 -0
  13. package/bin/commands/diff.js +325 -0
  14. package/bin/commands/lint.js +419 -0
  15. package/bin/commands/lint.test.js +103 -0
  16. package/bin/commands/mcp-setup.js +246 -0
  17. package/bin/commands/pull.js +287 -0
  18. package/bin/commands/pull.test.js +36 -0
  19. package/bin/commands/push.js +231 -0
  20. package/bin/commands/push.test.js +14 -0
  21. package/bin/commands/search.js +344 -0
  22. package/bin/commands/search.test.js +115 -0
  23. package/bin/commands/setup.js +545 -0
  24. package/bin/commands/setup.test.js +46 -0
  25. package/bin/commands/sync-profile.js +405 -0
  26. package/bin/commands/sync-profile.test.js +14 -0
  27. package/bin/commands/sync-source.js +418 -0
  28. package/bin/commands/sync-source.test.js +14 -0
  29. package/bin/commands/watch.js +206 -0
  30. package/bin/lib/generators/claude-plugin.js +266 -0
  31. package/bin/lib/generators/claude-plugin.test.js +110 -0
  32. package/bin/lib/generators/index.js +116 -0
  33. package/bin/lib/generators/shared.js +282 -0
  34. package/bin/lib/licensing/index.js +35 -0
  35. package/bin/lib/licensing/storage.js +364 -0
  36. package/bin/lib/licensing/storage.test.js +55 -0
  37. package/bin/lib/licensing/validator.js +213 -0
  38. package/bin/lib/licensing/validator.test.js +137 -0
  39. package/bin/lib/microsoft-mcp.js +176 -0
  40. package/bin/lib/microsoft-mcp.test.js +106 -0
  41. package/bin/lib/skills.js +84 -0
  42. package/bin/mcp/powerbi-modeling-launcher.js +38 -0
  43. package/bin/postinstall.js +44 -0
  44. package/bin/utils/errors.js +159 -0
  45. package/bin/utils/git.js +298 -0
  46. package/bin/utils/logger.js +142 -0
  47. package/bin/utils/mcp-detect.js +274 -0
  48. package/bin/utils/mcp-detect.test.js +105 -0
  49. package/bin/utils/pbix.js +305 -0
  50. package/bin/utils/pbix.test.js +37 -0
  51. package/bin/utils/profiles.js +312 -0
  52. package/bin/utils/projects.js +168 -0
  53. package/bin/utils/readline.js +206 -0
  54. package/bin/utils/readline.test.js +47 -0
  55. package/bin/utils/tui.js +314 -0
  56. package/bin/utils/tui.test.js +127 -0
  57. package/commands/contributions.md +265 -0
  58. package/commands/data-model-design.md +468 -0
  59. package/commands/dax-doctor.md +248 -0
  60. package/commands/fabric-scripts.md +452 -0
  61. package/commands/migration-assistant.md +290 -0
  62. package/commands/model-documenter.md +242 -0
  63. package/commands/pbi-connect.md +239 -0
  64. package/commands/project-kickoff.md +905 -0
  65. package/commands/report-layout.md +296 -0
  66. package/commands/rls-design.md +533 -0
  67. package/commands/theme-tweaker.md +624 -0
  68. package/config.example.json +23 -0
  69. package/config.json +23 -0
  70. package/desktop-extension/manifest.json +37 -0
  71. package/desktop-extension/package.json +10 -0
  72. package/desktop-extension/server.js +95 -0
  73. package/docs/openrouter-free-models.md +92 -0
  74. package/library/examples/README.md +151 -0
  75. package/library/examples/finance-reporting/README.md +351 -0
  76. package/library/examples/finance-reporting/data-model.md +267 -0
  77. package/library/examples/finance-reporting/measures.dax +557 -0
  78. package/library/examples/hr-analytics/README.md +371 -0
  79. package/library/examples/hr-analytics/data-model.md +315 -0
  80. package/library/examples/hr-analytics/measures.dax +460 -0
  81. package/library/examples/marketing-analytics/README.md +37 -0
  82. package/library/examples/marketing-analytics/data-model.md +62 -0
  83. package/library/examples/marketing-analytics/measures.dax +110 -0
  84. package/library/examples/retail-analytics/README.md +439 -0
  85. package/library/examples/retail-analytics/data-model.md +288 -0
  86. package/library/examples/retail-analytics/measures.dax +481 -0
  87. package/library/examples/supply-chain/README.md +37 -0
  88. package/library/examples/supply-chain/data-model.md +69 -0
  89. package/library/examples/supply-chain/measures.dax +77 -0
  90. package/library/examples/udf-library/README.md +228 -0
  91. package/library/examples/udf-library/functions.dax +571 -0
  92. package/library/snippets/dax/README.md +292 -0
  93. package/library/snippets/dax/business-domains.md +576 -0
  94. package/library/snippets/dax/calculate-patterns.md +276 -0
  95. package/library/snippets/dax/calculation-groups.md +489 -0
  96. package/library/snippets/dax/error-handling.md +495 -0
  97. package/library/snippets/dax/iterators-and-aggregations.md +474 -0
  98. package/library/snippets/dax/kpis-and-metrics.md +293 -0
  99. package/library/snippets/dax/rankings-and-topn.md +235 -0
  100. package/library/snippets/dax/security-patterns.md +413 -0
  101. package/library/snippets/dax/text-and-formatting.md +316 -0
  102. package/library/snippets/dax/time-intelligence.md +196 -0
  103. package/library/snippets/dax/user-defined-functions.md +477 -0
  104. package/library/snippets/dax/virtual-tables.md +546 -0
  105. package/library/snippets/excel-formulas/README.md +84 -0
  106. package/library/snippets/excel-formulas/aggregations.md +330 -0
  107. package/library/snippets/excel-formulas/dates-and-times.md +361 -0
  108. package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
  109. package/library/snippets/excel-formulas/lookups.md +169 -0
  110. package/library/snippets/excel-formulas/text-functions.md +363 -0
  111. package/library/snippets/governance/naming-conventions.md +97 -0
  112. package/library/snippets/governance/review-checklists.md +107 -0
  113. package/library/snippets/power-query/README.md +389 -0
  114. package/library/snippets/power-query/api-integration.md +707 -0
  115. package/library/snippets/power-query/connections.md +434 -0
  116. package/library/snippets/power-query/data-cleaning.md +298 -0
  117. package/library/snippets/power-query/error-handling.md +526 -0
  118. package/library/snippets/power-query/parameters.md +350 -0
  119. package/library/snippets/power-query/performance.md +506 -0
  120. package/library/snippets/power-query/transformations.md +330 -0
  121. package/library/snippets/report-design/accessibility.md +78 -0
  122. package/library/snippets/report-design/chart-selection.md +54 -0
  123. package/library/snippets/report-design/layout-patterns.md +87 -0
  124. package/library/templates/data-models/README.md +93 -0
  125. package/library/templates/data-models/finance-model.md +627 -0
  126. package/library/templates/data-models/retail-star-schema.md +473 -0
  127. package/library/templates/excel/README.md +83 -0
  128. package/library/templates/excel/budget-tracker.md +432 -0
  129. package/library/templates/excel/data-entry-form.md +533 -0
  130. package/library/templates/power-bi/README.md +72 -0
  131. package/library/templates/power-bi/finance-report.md +449 -0
  132. package/library/templates/power-bi/kpi-scorecard.md +461 -0
  133. package/library/templates/power-bi/sales-dashboard.md +281 -0
  134. package/library/themes/excel/README.md +436 -0
  135. package/library/themes/power-bi/README.md +271 -0
  136. package/library/themes/power-bi/accessible.json +307 -0
  137. package/library/themes/power-bi/bi-superpowers-default.json +858 -0
  138. package/library/themes/power-bi/corporate-blue.json +291 -0
  139. package/library/themes/power-bi/dark-mode.json +291 -0
  140. package/library/themes/power-bi/minimal.json +292 -0
  141. package/library/themes/power-bi/print-friendly.json +309 -0
  142. package/package.json +93 -0
  143. package/skills/contributions/SKILL.md +267 -0
  144. package/skills/data-model-design/SKILL.md +470 -0
  145. package/skills/data-modeling/SKILL.md +254 -0
  146. package/skills/data-quality/SKILL.md +664 -0
  147. package/skills/dax/SKILL.md +708 -0
  148. package/skills/dax-doctor/SKILL.md +250 -0
  149. package/skills/dax-udf/SKILL.md +489 -0
  150. package/skills/deployment/SKILL.md +320 -0
  151. package/skills/excel-formulas/SKILL.md +463 -0
  152. package/skills/fabric-scripts/SKILL.md +454 -0
  153. package/skills/fast-standard/SKILL.md +509 -0
  154. package/skills/governance/SKILL.md +205 -0
  155. package/skills/migration-assistant/SKILL.md +292 -0
  156. package/skills/model-documenter/SKILL.md +244 -0
  157. package/skills/pbi-connect/SKILL.md +241 -0
  158. package/skills/power-query/SKILL.md +406 -0
  159. package/skills/project-kickoff/SKILL.md +907 -0
  160. package/skills/query-performance/SKILL.md +480 -0
  161. package/skills/report-design/SKILL.md +207 -0
  162. package/skills/report-layout/SKILL.md +298 -0
  163. package/skills/rls-design/SKILL.md +535 -0
  164. package/skills/semantic-model/SKILL.md +237 -0
  165. package/skills/testing-validation/SKILL.md +643 -0
  166. package/skills/theme-tweaker/SKILL.md +626 -0
  167. package/src/content/base.md +237 -0
  168. package/src/content/mcp-requirements.json +69 -0
  169. package/src/content/routing.md +203 -0
  170. package/src/content/skills/contributions.md +259 -0
  171. package/src/content/skills/data-model-design.md +462 -0
  172. package/src/content/skills/data-modeling.md +246 -0
  173. package/src/content/skills/data-quality.md +656 -0
  174. package/src/content/skills/dax-doctor.md +242 -0
  175. package/src/content/skills/dax-udf.md +481 -0
  176. package/src/content/skills/dax.md +700 -0
  177. package/src/content/skills/deployment.md +312 -0
  178. package/src/content/skills/excel-formulas.md +455 -0
  179. package/src/content/skills/fabric-scripts.md +446 -0
  180. package/src/content/skills/fast-standard.md +501 -0
  181. package/src/content/skills/governance.md +197 -0
  182. package/src/content/skills/migration-assistant.md +284 -0
  183. package/src/content/skills/model-documenter.md +236 -0
  184. package/src/content/skills/pbi-connect.md +233 -0
  185. package/src/content/skills/power-query.md +398 -0
  186. package/src/content/skills/project-kickoff.md +899 -0
  187. package/src/content/skills/query-performance.md +472 -0
  188. package/src/content/skills/report-design.md +199 -0
  189. package/src/content/skills/report-layout.md +290 -0
  190. package/src/content/skills/rls-design.md +527 -0
  191. package/src/content/skills/semantic-model.md +229 -0
  192. package/src/content/skills/testing-validation.md +635 -0
  193. package/src/content/skills/theme-tweaker.md +618 -0
@@ -0,0 +1,434 @@
1
+ # Data Source Connections
2
+
3
+ ## Overview
4
+ Power Query patterns for connecting to various data sources including databases, cloud services, APIs, and files.
5
+
6
+ ---
7
+
8
+ ## SQL Server
9
+
10
+ ### Basic Connection
11
+ ```m
12
+ let
13
+ Source = Sql.Database("server-name", "database-name")
14
+ in
15
+ Source
16
+ ```
17
+
18
+ ### With Native Query
19
+ ```m
20
+ let
21
+ Source = Sql.Database("server-name", "database-name", [
22
+ Query = "
23
+ SELECT
24
+ CustomerID,
25
+ CustomerName,
26
+ Email,
27
+ CreatedDate
28
+ FROM dbo.Customers
29
+ WHERE IsActive = 1
30
+ "
31
+ ])
32
+ in
33
+ Source
34
+ ```
35
+
36
+ ### With Parameters
37
+ ```m
38
+ let
39
+ Source = Sql.Database(param_ServerName, param_DatabaseName, [
40
+ Query = "
41
+ SELECT * FROM dbo.Sales
42
+ WHERE OrderDate >= '" & Date.ToText(param_StartDate, "yyyy-MM-dd") & "'
43
+ ",
44
+ CommandTimeout = #duration(0, 0, 10, 0) // 10 minutes
45
+ ])
46
+ in
47
+ Source
48
+ ```
49
+
50
+ ### Navigation to Table
51
+ ```m
52
+ let
53
+ Source = Sql.Database("server-name", "database-name"),
54
+ Sales = Source{[Schema="dbo", Item="Sales"]}[Data]
55
+ in
56
+ Sales
57
+ ```
58
+
59
+ ---
60
+
61
+ ## SharePoint
62
+
63
+ ### SharePoint List
64
+ ```m
65
+ let
66
+ Source = SharePoint.Tables(
67
+ "https://company.sharepoint.com/sites/SiteName",
68
+ [Implementation = "2.0", ViewMode = "All"]
69
+ ),
70
+ ListData = Source{[Title="ListName"]}[Items],
71
+ // Remove SharePoint metadata columns
72
+ RemovedColumns = Table.SelectColumns(ListData, {"Column1", "Column2", "Column3"})
73
+ in
74
+ RemovedColumns
75
+ ```
76
+
77
+ ### SharePoint Folder (Files)
78
+ ```m
79
+ let
80
+ Source = SharePoint.Files(
81
+ "https://company.sharepoint.com/sites/SiteName",
82
+ [ApiVersion = 15]
83
+ ),
84
+ // Filter to specific folder
85
+ FilteredFolder = Table.SelectRows(Source, each
86
+ Text.Contains([Folder Path], "/Documents/Reports/")
87
+ ),
88
+ // Filter to Excel files
89
+ FilteredFiles = Table.SelectRows(FilteredFolder, each
90
+ Text.EndsWith([Name], ".xlsx")
91
+ )
92
+ in
93
+ FilteredFiles
94
+ ```
95
+
96
+ ### SharePoint Excel File
97
+ ```m
98
+ let
99
+ Source = SharePoint.Files(
100
+ "https://company.sharepoint.com/sites/SiteName",
101
+ [ApiVersion = 15]
102
+ ),
103
+ // Find specific file
104
+ FileRow = Table.SelectRows(Source, each [Name] = "SalesData.xlsx"){0},
105
+ // Load Excel content
106
+ ExcelContent = Excel.Workbook(FileRow[Content], true, true),
107
+ // Get specific sheet
108
+ Sheet1 = ExcelContent{[Item="Sheet1", Kind="Sheet"]}[Data]
109
+ in
110
+ Sheet1
111
+ ```
112
+
113
+ ---
114
+
115
+ ## REST API / Web Services
116
+
117
+ ### Basic JSON API
118
+ ```m
119
+ let
120
+ Source = Json.Document(
121
+ Web.Contents("https://api.example.com/data")
122
+ ),
123
+ ToTable = Table.FromRecords(Source)
124
+ in
125
+ ToTable
126
+ ```
127
+
128
+ ### With Authentication Header
129
+ ```m
130
+ let
131
+ Source = Json.Document(
132
+ Web.Contents(
133
+ "https://api.example.com/data",
134
+ [
135
+ Headers = [
136
+ #"Authorization" = "Bearer " & param_APIToken,
137
+ #"Content-Type" = "application/json"
138
+ ]
139
+ ]
140
+ )
141
+ )
142
+ in
143
+ Source
144
+ ```
145
+
146
+ ### With Query Parameters
147
+ ```m
148
+ let
149
+ Source = Json.Document(
150
+ Web.Contents(
151
+ "https://api.example.com/data",
152
+ [
153
+ Query = [
154
+ startDate = Date.ToText(param_StartDate, "yyyy-MM-dd"),
155
+ endDate = Date.ToText(param_EndDate, "yyyy-MM-dd"),
156
+ limit = "1000",
157
+ page = "1"
158
+ ]
159
+ ]
160
+ )
161
+ )
162
+ in
163
+ Source
164
+ ```
165
+
166
+ ### Pagination Pattern
167
+ ```m
168
+ let
169
+ // Function to get one page
170
+ GetPage = (pageNum as number) as table =>
171
+ let
172
+ Source = Json.Document(
173
+ Web.Contents(
174
+ "https://api.example.com/data",
175
+ [Query = [page = Text.From(pageNum), limit = "100"]]
176
+ )
177
+ ),
178
+ ToTable = Table.FromRecords(Source[data])
179
+ in
180
+ ToTable,
181
+
182
+ // Get all pages
183
+ PageCount = 10, // Or dynamically determine
184
+ Pages = List.Generate(
185
+ () => [Page = 1, Data = GetPage(1)],
186
+ each [Page] <= PageCount,
187
+ each [Page = [Page] + 1, Data = GetPage([Page] + 1)],
188
+ each [Data]
189
+ ),
190
+ Combined = Table.Combine(Pages)
191
+ in
192
+ Combined
193
+ ```
194
+
195
+ ### POST Request
196
+ ```m
197
+ let
198
+ Body = Json.FromValue([
199
+ startDate = Date.ToText(param_StartDate, "yyyy-MM-dd"),
200
+ filters = {[field = "status", value = "active"]}
201
+ ]),
202
+ Source = Json.Document(
203
+ Web.Contents(
204
+ "https://api.example.com/query",
205
+ [
206
+ Headers = [
207
+ #"Authorization" = "Bearer " & param_APIToken,
208
+ #"Content-Type" = "application/json"
209
+ ],
210
+ Content = Body
211
+ ]
212
+ )
213
+ )
214
+ in
215
+ Source
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Excel Files
221
+
222
+ ### Local Excel File
223
+ ```m
224
+ let
225
+ Source = Excel.Workbook(
226
+ File.Contents(param_FilePath),
227
+ true, // Use first row as headers
228
+ true // Infer types
229
+ ),
230
+ Sheet1 = Source{[Item="Sheet1", Kind="Sheet"]}[Data]
231
+ in
232
+ Sheet1
233
+ ```
234
+
235
+ ### Excel Table (Named Range)
236
+ ```m
237
+ let
238
+ Source = Excel.Workbook(File.Contents(param_FilePath), true, true),
239
+ SalesTable = Source{[Item="tbl_Sales", Kind="Table"]}[Data]
240
+ in
241
+ SalesTable
242
+ ```
243
+
244
+ ### Current Workbook (Excel Only)
245
+ ```m
246
+ let
247
+ Source = Excel.CurrentWorkbook(){[Name="tbl_Data"]}[Content]
248
+ in
249
+ Source
250
+ ```
251
+
252
+ ---
253
+
254
+ ## CSV Files
255
+
256
+ ### Basic CSV
257
+ ```m
258
+ let
259
+ Source = Csv.Document(
260
+ File.Contents(param_FilePath),
261
+ [
262
+ Delimiter = ",",
263
+ Encoding = 65001, // UTF-8
264
+ QuoteStyle = QuoteStyle.Csv
265
+ ]
266
+ ),
267
+ PromotedHeaders = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
268
+ in
269
+ PromotedHeaders
270
+ ```
271
+
272
+ ### CSV with Specific Columns
273
+ ```m
274
+ let
275
+ Source = Csv.Document(
276
+ File.Contents(param_FilePath),
277
+ [
278
+ Delimiter = ",",
279
+ Columns = 5,
280
+ Encoding = 65001,
281
+ QuoteStyle = QuoteStyle.Csv
282
+ ]
283
+ )
284
+ in
285
+ Source
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Folder of Files
291
+
292
+ ### Combine All Files in Folder
293
+ ```m
294
+ let
295
+ Source = Folder.Files(param_FolderPath),
296
+ // Filter to specific file type
297
+ FilteredFiles = Table.SelectRows(Source, each Text.EndsWith([Name], ".csv")),
298
+ // Add file content
299
+ AddContent = Table.AddColumn(FilteredFiles, "Data", each
300
+ Csv.Document([Content], [Delimiter=",", Encoding=65001])
301
+ ),
302
+ // Expand all rows
303
+ ExpandedData = Table.ExpandTableColumn(AddContent, "Data",
304
+ Table.ColumnNames(AddContent{0}[Data])
305
+ ),
306
+ // Keep data columns plus filename for reference
307
+ SelectColumns = Table.SelectColumns(ExpandedData,
308
+ List.Combine({{"Name"}, Table.ColumnNames(FilteredFiles{0}[Data])})
309
+ )
310
+ in
311
+ SelectColumns
312
+ ```
313
+
314
+ ### Binary Combine Pattern (Power Query UI)
315
+ ```m
316
+ // This is generated by "Combine Files" in Power Query
317
+ let
318
+ Source = Folder.Files(param_FolderPath),
319
+ FilteredHiddenFiles = Table.SelectRows(Source, each [Attributes]?[Hidden]? <> true),
320
+ InvokeCustomFunction = Table.AddColumn(FilteredHiddenFiles, "Transform File", each #"Transform File"([Content])),
321
+ RemovedOtherColumns = Table.SelectColumns(InvokeCustomFunction, {"Name", "Transform File"}),
322
+ ExpandedTable = Table.ExpandTableColumn(RemovedOtherColumns, "Transform File", Table.ColumnNames(#"Transform File"(#"Sample File")))
323
+ in
324
+ ExpandedTable
325
+ ```
326
+
327
+ ---
328
+
329
+ ## OData
330
+
331
+ ### Basic OData Connection
332
+ ```m
333
+ let
334
+ Source = OData.Feed("https://services.odata.org/V4/Northwind/Northwind.svc/"),
335
+ Products = Source{[Name="Products", Signature="table"]}[Data]
336
+ in
337
+ Products
338
+ ```
339
+
340
+ ### With Query Options
341
+ ```m
342
+ let
343
+ Source = OData.Feed(
344
+ "https://api.example.com/odata/Sales",
345
+ null,
346
+ [
347
+ Query = [
348
+ #"$filter" = "Year eq 2024",
349
+ #"$select" = "ID,Date,Amount",
350
+ #"$orderby" = "Date desc"
351
+ ]
352
+ ]
353
+ )
354
+ in
355
+ Source
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Azure
361
+
362
+ ### Azure SQL Database
363
+ ```m
364
+ let
365
+ Source = Sql.Database(
366
+ "your-server.database.windows.net",
367
+ "your-database",
368
+ [
369
+ Query = "SELECT * FROM dbo.Sales WHERE Year = 2024"
370
+ ]
371
+ )
372
+ in
373
+ Source
374
+ ```
375
+
376
+ ### Azure Blob Storage
377
+ ```m
378
+ let
379
+ Source = AzureStorage.Blobs("https://youraccount.blob.core.windows.net/container"),
380
+ FilteredFiles = Table.SelectRows(Source, each Text.EndsWith([Name], ".csv")),
381
+ FirstFile = FilteredFiles{0}[Content],
382
+ Data = Csv.Document(FirstFile, [Delimiter=",", Encoding=65001])
383
+ in
384
+ Data
385
+ ```
386
+
387
+ ### Azure Data Lake Gen2
388
+ ```m
389
+ let
390
+ Source = AzureStorage.DataLake("https://youraccount.dfs.core.windows.net/filesystem"),
391
+ NavigateToFolder = Source{[Folder="path/to/folder"]}[Content],
392
+ CsvFile = NavigateToFolder{[Name="data.csv"]}[Content],
393
+ Data = Csv.Document(CsvFile, [Delimiter=","])
394
+ in
395
+ Data
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Error Handling for Connections
401
+
402
+ ### Try-Otherwise Pattern
403
+ ```m
404
+ let
405
+ Source = try Sql.Database(param_ServerName, param_DatabaseName) otherwise null,
406
+ Result = if Source = null then
407
+ #table({"Error"}, {{"Connection failed"}})
408
+ else
409
+ Source{[Schema="dbo", Item="Sales"]}[Data]
410
+ in
411
+ Result
412
+ ```
413
+
414
+ ### Timeout Handling
415
+ ```m
416
+ let
417
+ Source = Sql.Database(
418
+ param_ServerName,
419
+ param_DatabaseName,
420
+ [
421
+ CommandTimeout = #duration(0, 0, 5, 0), // 5 minutes
422
+ CreateNavigationProperties = false // Faster load
423
+ ]
424
+ )
425
+ in
426
+ Source
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Related Resources
432
+
433
+ - [Parameters](./parameters.md)
434
+ - [Power Query Skill](../../skills/power-query/SKILL.md)
@@ -0,0 +1,298 @@
1
+ # Data Cleaning Power Query Patterns
2
+
3
+ Reusable M code for cleaning and preparing data.
4
+
5
+ ## Remove Empty and Null Values
6
+
7
+ ### Remove Null Rows
8
+ ```m
9
+ let
10
+ Source = YourSource,
11
+ // Remove rows where specific column is null
12
+ FilteredRows = Table.SelectRows(Source, each [ColumnName] <> null)
13
+ in
14
+ FilteredRows
15
+ ```
16
+
17
+ ### Remove Empty Strings
18
+ ```m
19
+ let
20
+ Source = YourSource,
21
+ FilteredRows = Table.SelectRows(
22
+ Source,
23
+ each [ColumnName] <> null and [ColumnName] <> ""
24
+ )
25
+ in
26
+ FilteredRows
27
+ ```
28
+
29
+ ### Remove Rows with Any Null
30
+ ```m
31
+ let
32
+ Source = YourSource,
33
+ ColumnNames = Table.ColumnNames(Source),
34
+ FilteredRows = Table.SelectRows(
35
+ Source,
36
+ each not List.Contains(
37
+ List.Transform(ColumnNames, (col) => Record.Field(_, col)),
38
+ null
39
+ )
40
+ )
41
+ in
42
+ FilteredRows
43
+ ```
44
+
45
+ ### Replace Null with Default
46
+ ```m
47
+ let
48
+ Source = YourSource,
49
+ ReplacedNull = Table.ReplaceValue(
50
+ Source,
51
+ null,
52
+ 0,
53
+ Replacer.ReplaceValue,
54
+ {"NumericColumn"}
55
+ )
56
+ in
57
+ ReplacedNull
58
+ ```
59
+
60
+ ## Text Cleaning
61
+
62
+ ### Trim and Clean Text
63
+ ```m
64
+ let
65
+ Source = YourSource,
66
+ Cleaned = Table.TransformColumns(
67
+ Source,
68
+ {{"TextColumn", each Text.Trim(Text.Clean(_)), type text}}
69
+ )
70
+ in
71
+ Cleaned
72
+ ```
73
+
74
+ ### Standardize Case
75
+ ```m
76
+ let
77
+ Source = YourSource,
78
+ // Proper Case
79
+ ProperCase = Table.TransformColumns(
80
+ Source,
81
+ {{"Name", Text.Proper, type text}}
82
+ ),
83
+ // Upper Case
84
+ UpperCase = Table.TransformColumns(
85
+ Source,
86
+ {{"Code", Text.Upper, type text}}
87
+ ),
88
+ // Lower Case
89
+ LowerCase = Table.TransformColumns(
90
+ Source,
91
+ {{"Email", Text.Lower, type text}}
92
+ )
93
+ in
94
+ ProperCase
95
+ ```
96
+
97
+ ### Remove Extra Spaces
98
+ ```m
99
+ let
100
+ Source = YourSource,
101
+ // Replace multiple spaces with single space
102
+ CleanSpaces = Table.TransformColumns(
103
+ Source,
104
+ {{"TextColumn", each Text.Combine(
105
+ List.Select(Text.Split(_, " "), each _ <> ""),
106
+ " "
107
+ ), type text}}
108
+ )
109
+ in
110
+ CleanSpaces
111
+ ```
112
+
113
+ ### Remove Special Characters
114
+ ```m
115
+ let
116
+ Source = YourSource,
117
+ RemoveSpecial = Table.TransformColumns(
118
+ Source,
119
+ {{"TextColumn", each Text.Select(_, {"A".."Z", "a".."z", "0".."9", " "}), type text}}
120
+ )
121
+ in
122
+ RemoveSpecial
123
+ ```
124
+
125
+ ## Duplicate Handling
126
+
127
+ ### Remove Duplicate Rows
128
+ ```m
129
+ let
130
+ Source = YourSource,
131
+ RemovedDuplicates = Table.Distinct(Source)
132
+ in
133
+ RemovedDuplicates
134
+ ```
135
+
136
+ ### Remove Duplicates by Key Column
137
+ ```m
138
+ let
139
+ Source = YourSource,
140
+ RemovedDuplicates = Table.Distinct(Source, {"KeyColumn"})
141
+ in
142
+ RemovedDuplicates
143
+ ```
144
+
145
+ ### Keep First/Last Occurrence
146
+ ```m
147
+ let
148
+ Source = YourSource,
149
+ Sorted = Table.Sort(Source, {{"Date", Order.Descending}}),
150
+ // Keeps first (most recent) per group
151
+ KeepFirst = Table.Distinct(Sorted, {"CustomerID"})
152
+ in
153
+ KeepFirst
154
+ ```
155
+
156
+ ### Flag Duplicates
157
+ ```m
158
+ let
159
+ Source = YourSource,
160
+ Grouped = Table.Group(
161
+ Source,
162
+ {"KeyColumn"},
163
+ {{"Count", each Table.RowCount(_), Int64.Type}}
164
+ ),
165
+ Merged = Table.NestedJoin(Source, {"KeyColumn"}, Grouped, {"KeyColumn"}, "Counts", JoinKind.LeftOuter),
166
+ Expanded = Table.ExpandTableColumn(Merged, "Counts", {"Count"}),
167
+ FlagDuplicates = Table.AddColumn(Expanded, "IsDuplicate", each [Count] > 1, type logical)
168
+ in
169
+ FlagDuplicates
170
+ ```
171
+
172
+ ## Data Type Fixes
173
+
174
+ ### Safe Number Conversion
175
+ ```m
176
+ let
177
+ Source = YourSource,
178
+ SafeNumber = Table.TransformColumns(
179
+ Source,
180
+ {{"Amount", each try Number.From(_) otherwise null, type number}}
181
+ )
182
+ in
183
+ SafeNumber
184
+ ```
185
+
186
+ ### Safe Date Conversion
187
+ ```m
188
+ let
189
+ Source = YourSource,
190
+ SafeDate = Table.TransformColumns(
191
+ Source,
192
+ {{"DateColumn", each try Date.From(_) otherwise null, type date}}
193
+ )
194
+ in
195
+ SafeDate
196
+ ```
197
+
198
+ ### Parse Multiple Date Formats
199
+ ```m
200
+ let
201
+ Source = YourSource,
202
+ ParseDate = Table.TransformColumns(
203
+ Source,
204
+ {{"DateColumn", each
205
+ let
206
+ TryMDY = try Date.FromText(_, [Format="M/d/yyyy"]),
207
+ TryDMY = try Date.FromText(_, [Format="d/M/yyyy"]),
208
+ TryISO = try Date.FromText(_, [Format="yyyy-MM-dd"])
209
+ in
210
+ if TryMDY[HasError] = false then TryMDY[Value]
211
+ else if TryDMY[HasError] = false then TryDMY[Value]
212
+ else if TryISO[HasError] = false then TryISO[Value]
213
+ else null
214
+ , type date}}
215
+ )
216
+ in
217
+ ParseDate
218
+ ```
219
+
220
+ ## Standardization
221
+
222
+ ### Standardize Column Names
223
+ ```m
224
+ let
225
+ Source = YourSource,
226
+ // Replace spaces with underscores, remove special chars
227
+ RenameColumns = Table.TransformColumnNames(
228
+ Source,
229
+ each Text.Replace(
230
+ Text.Replace(
231
+ Text.Replace(Text.Proper(_), " ", "_"),
232
+ "#", ""
233
+ ),
234
+ "(", ""
235
+ )
236
+ )
237
+ in
238
+ RenameColumns
239
+ ```
240
+
241
+ ### Map Values to Standard Names
242
+ ```m
243
+ let
244
+ Source = YourSource,
245
+ MappingTable = #table(
246
+ {"Original", "Standard"},
247
+ {
248
+ {"NY", "New York"},
249
+ {"NYC", "New York"},
250
+ {"CA", "California"},
251
+ {"Cali", "California"}
252
+ }
253
+ ),
254
+ Merged = Table.NestedJoin(Source, {"State"}, MappingTable, {"Original"}, "Mapping", JoinKind.LeftOuter),
255
+ Expanded = Table.ExpandTableColumn(Merged, "Mapping", {"Standard"}),
256
+ Final = Table.AddColumn(Expanded, "CleanState", each if [Standard] = null then [State] else [Standard])
257
+ in
258
+ Final
259
+ ```
260
+
261
+ ## Error Handling
262
+
263
+ ### Replace Errors with Null
264
+ ```m
265
+ let
266
+ Source = YourSource,
267
+ ReplacedErrors = Table.ReplaceErrorValues(
268
+ Source,
269
+ {{"Column1", null}, {"Column2", null}}
270
+ )
271
+ in
272
+ ReplacedErrors
273
+ ```
274
+
275
+ ### Log Errors to Separate Table
276
+ ```m
277
+ let
278
+ Source = YourSource,
279
+ AddErrorFlag = Table.AddColumn(
280
+ Source,
281
+ "HasError",
282
+ each try [Amount] > 0 otherwise true,
283
+ type logical
284
+ ),
285
+ ErrorRows = Table.SelectRows(AddErrorFlag, each [HasError] = true),
286
+ CleanRows = Table.SelectRows(AddErrorFlag, each [HasError] = false)
287
+ in
288
+ CleanRows
289
+ // ErrorRows can be returned separately for logging
290
+ ```
291
+
292
+ ## Usage Notes
293
+
294
+ - Always preview results after each transformation
295
+ - Use `try...otherwise` for safe operations
296
+ - Consider query folding when working with databases
297
+ - Document complex transformations with comments
298
+ - Test with edge cases (empty strings, nulls, special characters)