skills/power-bi-dax/SKILL.md
Write, execute, and optimize DAX queries and measures for Power BI semantic models using pbi-cli. Invoke this skill whenever the user mentions DAX, queries data in Power BI, writes calculations, creates measures, asks about EVALUATE, SUMMARIZECOLUMNS, CALCULATE, time intelligence, or wants to analyze/aggregate data from a semantic model. Also invoke when the user asks to run a query, test a formula, or check row counts. This skill contains critical guidance on passing DAX expressions via CLI arguments -- multi-line DAX (VAR/RETURN) requires special handling.
npx skillsauth add julianobarbosa/claude-code-skills power-bi-daxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Execute and validate DAX queries against connected Power BI models.
pipx install pbi-cli-tool
pbi-cli skills install
pbi connect
# Inline query
pbi dax execute "EVALUATE TOPN(10, Sales)"
# From file
pbi dax execute --file query.dax
# From stdin (piping)
cat query.dax | pbi dax execute -
echo "EVALUATE Sales" | pbi dax execute -
# With options
pbi dax execute "EVALUATE Sales" --max-rows 100
pbi dax execute "EVALUATE Sales" --timeout 300 # Custom timeout (seconds)
# JSON output for scripting
pbi --json dax execute "EVALUATE Sales"
When passing DAX as a -e argument, the shell collapses newlines into a single line. Simple expressions like SUM(Sales[Amount]) work fine, but multi-line DAX using VAR/RETURN breaks because the DAX parser needs line breaks between those keywords.
Why this matters: A measure like VAR x = [Total Sales] VAR y = [Sales PY] RETURN DIVIDE(x - y, y) will fail with a syntax error because the engine sees it as one continuous line without statement separators.
Workarounds (pick one):
# Option 1: Pipe from stdin (recommended for measures)
echo 'VAR TotalSales = SUM(Sales[Amount])
VAR TotalCost = SUM(Sales[Cost])
RETURN TotalSales - TotalCost' | pbi measure create "Profit" -e - -t Sales
# Option 2: Write to a .dax file and use --file (for queries)
echo 'EVALUATE
ROW("Result",
VAR x = SUM(Sales[Amount])
RETURN x
)' > query.dax
pbi dax execute --file query.dax
Single-line alternatives (preferred when possible):
For simple ratio/growth measures, use inline patterns instead of VAR/RETURN:
# Instead of: VAR x = SUM(...) / VAR y = SUM(...) / RETURN DIVIDE(x, y)
# Use inline DIVIDE -- it handles division-by-zero gracefully (returns BLANK):
pbi measure create "Margin %" \
-e "DIVIDE(SUM(Sales[Amount]) - SUM(Sales[Cost]), SUM(Sales[Amount]))" \
-t Sales --format-string "0.0%"
# Instead of: VAR current = [Total Sales] / VAR prev = [Sales PY] / RETURN DIVIDE(...)
# Reference measures directly in DIVIDE:
pbi measure create "YoY %" \
-e "DIVIDE([Total Sales] - [PY Sales], [PY Sales])" \
-t Sales --format-string "0.0%"
pbi dax validate "EVALUATE Sales"
pbi dax validate --file query.dax
pbi dax clear-cache # Clear the formula engine cache
# Simple aggregation
pbi measure create "Total Sales" -e "SUM(Sales[Amount])" -t Sales
# Time intelligence
pbi measure create "YTD Sales" -e "TOTALYTD(SUM(Sales[Amount]), Calendar[Date])" -t Sales
# Previous year comparison
pbi measure create "PY Sales" -e "CALCULATE([Total Sales], SAMEPERIODLASTYEAR(Calendar[Date]))" -t Sales
# Year-over-year change
pbi measure create "YoY %" -e "DIVIDE([Total Sales] - [PY Sales], [PY Sales])" -t Sales --format-string "0.0%"
# List all tables
pbi dax execute "EVALUATE INFO.TABLES()"
# List columns in a table
pbi dax execute "EVALUATE INFO.COLUMNS()"
# Preview table data
pbi dax execute "EVALUATE TOPN(10, Sales)"
# Count rows
pbi dax execute "EVALUATE ROW(\"Count\", COUNTROWS(Sales))"
# Basic sum
pbi dax execute "EVALUATE ROW(\"Total\", SUM(Sales[Amount]))"
# Group by with aggregation
pbi dax execute "EVALUATE SUMMARIZECOLUMNS(Products[Category], \"Total\", SUM(Sales[Amount]))"
# Multiple aggregations
pbi dax execute "
EVALUATE
SUMMARIZECOLUMNS(
Products[Category],
\"Total Sales\", SUM(Sales[Amount]),
\"Avg Price\", AVERAGE(Sales[UnitPrice]),
\"Count\", COUNTROWS(Sales)
)
"
# CALCULATE with filter
pbi dax execute "
EVALUATE
ROW(\"Online Sales\", CALCULATE(SUM(Sales[Amount]), Sales[Channel] = \"Online\"))
"
# FILTER with complex condition
pbi dax execute "
EVALUATE
FILTER(
SUMMARIZECOLUMNS(Products[Name], \"Total\", SUM(Sales[Amount])),
[Total] > 1000
)
"
# Year-to-date
pbi dax execute "
EVALUATE
ROW(\"YTD\", TOTALYTD(SUM(Sales[Amount]), Calendar[Date]))
"
# Rolling 12 months
pbi dax execute "
EVALUATE
ROW(\"R12\", CALCULATE(
SUM(Sales[Amount]),
DATESINPERIOD(Calendar[Date], MAX(Calendar[Date]), -12, MONTH)
))
"
# Top products by sales
pbi dax execute "
EVALUATE
TOPN(
10,
ADDCOLUMNS(
VALUES(Products[Name]),
\"Total\", CALCULATE(SUM(Sales[Amount]))
),
[Total], DESC
)
"
--max-rows to limit result sets during developmentpbi dax clear-cache before benchmarkingSUMMARIZECOLUMNS over SUMMARIZE for groupingCALCULATE with simple filters instead of nested FILTERSUMX, FILTER) on large tables when aggregations suffice-e: Shell collapses newlines into spaces, and the DAX parser needs them as statement separators. VAR x = ... VAR y = ... RETURN ... on one line throws a "syntax error" with no useful pointer. Pipe from stdin or use --file.CALCULATE(SUM(x), ALL(table)) vs ALLEXCEPT vs ALLSELECTED: All three return plausible numbers — only one is correct. ALL clears everything on that table; ALLEXCEPT keeps only listed columns; ALLSELECTED respects outer filters. The wrong choice yields silently wrong totals.SUMMARIZECOLUMNS drops rows where every measure is BLANK: A "missing category" in your output is often this filter, not missing data. Wrap measures in COALESCE(measure, 0) if the row must appear.DIVIDE([a], [b]) returns BLANK on divide-by-zero, not 0: Blanks propagate into downstream charts as gaps rather than zeros. Pass the third arg explicitly: DIVIDE([a], [b], 0) when zero behavior is intended.pbi dax clear-cache only clears the formula engine cache: Storage engine cache survives. For an honest cold-query benchmark, also restart Desktop or re-attach the connection — repeat runs otherwise look unrealistically fast.INFO.TABLES() returns hidden tables too: Calculation groups, auto-date hierarchies, and translation tables show up alongside user tables. Filter [IsHidden] = FALSE to get just what a report author sees.development
End-to-end branch delivery: commit (no AI attribution) → push → open a pull request → ensure a Board work item exists (create one per task, assigned to the configured user, if none) and link it → after merge, clean up branch and worktree. Auto-detects the platform from the remote — Azure Repos + Boards (azure-devops-node-api SDK; OAuth Bearer push fallback via `az`) or GitHub (Octokit; `gh` for auth). Scripts are TypeScript, run via `bun`. Use whenever asked to "ship", "ship it", "ship this branch", "open a PR", "push and open a PR", "raise a PR", "deliver this", "send this for review", or "create a PR and link the work item" — and when a direct push to main is blocked and the change needs to go through a PR instead.
testing
Brief description of what this skill does. Include specific triggers - when should Claude use this skill? Example triggers, file types, or keywords that indicate this skill applies.
tools
Manage and troubleshoot PATH configuration in zsh. Use when adding tools to PATH (bun, nvm, Python venv, cargo, go), diagnosing "command not found" errors, validating PATH entries, or organizing shell configuration in .zshrc and .zshrc.local files.
tools
Zabbix monitoring system automation via API and Python. Use when: (1) Managing hosts, templates, items, triggers, or host groups, (2) Automating monitoring configuration, (3) Sending data via Zabbix trapper/sender, (4) Querying historical data or events, (5) Bulk operations on Zabbix objects, (6) Maintenance window management, (7) User/permission management