@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,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)
|