Skip to main content

Power BI Integration

Extension Credit

This guide uses the pbix extension by the DuckDB community. Thanks to the contributors who made Power BI file integration possible!

Query Power BI data models, DAX measures, and tabular models programmatically via REST API. Reuse existing BI logic without rebuilding, perfect for mobile apps, custom dashboards, and automated reporting.

Why Use Power BI with flAPI?​

"Your Power BI logic, now available everywhere."

Power BI contains valuable business logic:

  • Reuse DAX measures - complex calculations already defined
  • No rebuilding - leverage existing BI models
  • Mobile/custom apps - access BI data outside Power BI
  • Automated reports - scheduled data extraction
  • API for dashboards - build custom visualizations

flAPI advantages:

  • Read Power BI .pbix files directly
  • Cache for performance (Power BI can be slow)
  • Add custom authentication layers
  • Mobile-friendly REST APIs
  • MCP tools for AI agents

Real-World Use Cases​

Mobile App with BI Data​

Problem: Need sales metrics in mobile app, but Power BI Desktop only Solution: flAPI exposes Power BI model as REST API, app queries it

Automated Reporting​

Problem: Need to email daily reports from Power BI Solution: Scheduled job queries flAPI, generates PDF reports

Custom Dashboards​

Problem: Want custom UI, not Power BI's interface Solution: Build React/Vue dashboard using flAPI endpoints

AI with BI Context​

Problem: AI agent needs company metrics for analysis Solution: Expose Power BI as MCP tools for Claude/GPT

Installation​

Install PBIX Extension​

flapi.yaml
connections:
powerbi-model:
init: |
INSTALL pbix FROM community;
LOAD pbix;
properties:
pbix_file: './models/sales_model.pbix'

The pbix_file property is referenced in templates as {{{ conn.pbix_file }}}. It is a DuckDB pbix extension property — see upstream docs for the full list of options.

Prerequisites​

  1. Power BI Desktop file (.pbix)
  2. Extract to accessible location
  3. Read permissions on file

Configuration​

Basic Setup​

flapi.yaml
project-name: powerbi-api

connections:
sales-model:
init: |
INSTALL pbix FROM community;
LOAD pbix;
properties:
pbix_file: '/data/models/sales_dashboard.pbix'

Multiple Models​

connections:
# Sales analytics
sales-model:
init: |
INSTALL pbix FROM community;
LOAD pbix;
properties:
pbix_file: '/data/sales_dashboard.pbix'

# Financial reporting
finance-model:
init: |
LOAD pbix;
properties:
pbix_file: '/data/finance_dashboard.pbix'

Example 1: Query Sales Data​

Power BI Model Structure​

Typical .pbix contains:

  • Tables: Sales, Customers, Products, Dates
  • Relationships: defined between tables
  • DAX Measures: Total Revenue, YoY Growth, etc.
  • Calculated Columns: Profit Margin, Customer Segment

SQL Query​

sqls/sales_summary.sql
-- Query tables from Power BI model
SELECT
d.Year,
d.Month,
d.Quarter,
SUM(s.Amount) AS TotalSales,
COUNT(DISTINCT s.CustomerID) AS UniqueCustomers,
COUNT(DISTINCT s.OrderID) AS TotalOrders
FROM pbix_scan('{{{ conn.pbix_file }}}', 'Sales') s
JOIN pbix_scan('{{{ conn.pbix_file }}}', 'Dates') d
ON s.DateKey = d.DateKey
WHERE 1=1
{{#params.year}}
AND d.Year = {{ params.year }}
{{/params.year}}
{{#params.quarter}}
AND d.Quarter = {{ params.quarter }}
{{/params.quarter}}
GROUP BY d.Year, d.Month, d.Quarter
ORDER BY d.Year DESC, d.Month DESC
sqls/sales_summary.yaml
url-path: /sales/summary/
method: GET
template-source: sales_summary.sql
connection:
- sales-model

cache:
enabled: true
table: sales_summary_cache
schedule: 6h
template-file: sales_cache.sql

request:
- field-name: year
field-in: query
description: Filter by year
required: false
validators:
- type: int
min: 2020
max: 2030
- field-name: quarter
field-in: query
description: Filter by quarter (1-4)
required: false
validators:
- type: int
min: 1
max: 4

Example 2: Customer Segmentation​

Leverage Power BI's customer segments:

sqls/customer_segments.sql
SELECT
c.CustomerID,
c.CustomerName,
c.Segment,
c.LifetimeValue,
c.LastPurchaseDate,
COUNT(DISTINCT o.OrderID) AS TotalOrders,
SUM(o.Amount) AS TotalSpent
FROM pbix_scan('{{{ conn.pbix_file }}}', 'Customers') c
LEFT JOIN pbix_scan('{{{ conn.pbix_file }}}', 'Orders') o
ON c.CustomerID = o.CustomerID
{{#params.segment}}
WHERE c.Segment = '{{{ params.segment }}}'
{{/params.segment}}
GROUP BY
c.CustomerID,
c.CustomerName,
c.Segment,
c.LifetimeValue,
c.LastPurchaseDate
ORDER BY TotalSpent DESC

Example 3: DAX Measures (via Pre-computed Tables)​

Challenge: DuckDB can't execute DAX directly

Solution: Power BI calculates measures → export to tables → query with flAPI

In Power BI​

  1. Create a "Measures Table" with pre-computed metrics
  2. Refresh on schedule
  3. Export .pbix

Query Measures​

sqls/kpi_dashboard.sql
SELECT
Period,
TotalRevenue,
RevenueYoY,
GrossMargin,
CustomerChurnRate,
AverageOrderValue
FROM pbix_scan('{{{ conn.pbix_file }}}', 'KPI_Measures')
WHERE Period >= CURRENT_DATE - INTERVAL 12 MONTH
ORDER BY Period DESC

Performance Optimization​

Challenge: Reading .pbix Files​

Power BI files can be large (100s of MB) with complex models.

Solution 1: Cache Aggressively​

cache:
enabled: true
table: powerbi_cache
schedule: 6h
template-file: powerbi_cache.sql
sqls/powerbi_cache.sql
-- Load model into DuckLake cache
INSERT INTO {{cache.catalog}}.{{cache.schema}}.{{cache.table}}
SELECT * FROM pbix_scan('{{{ conn.pbix_file }}}', 'Sales')

Solution 2: Export to Parquet​

# One-time: export Power BI tables to Parquet
duckdb -c "
INSTALL pbix FROM community;
LOAD pbix;
COPY (SELECT * FROM pbix_scan('model.pbix', 'Sales'))
TO 'sales.parquet' (FORMAT PARQUET);
"

Then query Parquet (much faster):

SELECT * FROM 'sales.parquet'
WHERE Year = 2024

Performance Comparison​

ApproachQuery TimeSetup Effort
Direct .pbix200-500msLow (just reference file)
Cached .pbix1-10msMedium (configure caching)
Exported Parquet1-5msHigh (export pipeline)

Recommendation: Start with caching, migrate to Parquet if needed.

Limitations​

What Works​

  • Read all tables from .pbix
  • Query relationships (via JOIN)
  • Access calculated columns
  • Filter and aggregate
  • Mix with other data sources

What Doesn't Work​

  • Execute DAX directly - no DAX engine in DuckDB
  • Write back to .pbix - read-only
  • Real-time data - must refresh .pbix file
  • Power BI Service - only works with .pbix files, not cloud datasets

Workaround for DAX​

Option 1: Pre-compute measures in Power BI

Power BI DAX:
YoY Growth = CALCULATE(
[Total Sales],
DATEADD('Date'[Date], -1, YEAR)
)

→ Materialize as calculated column or separate table
→ Query with flAPI

Option 2: Reimplement in SQL

SELECT
current_year.Month,
current_year.Sales AS CurrentSales,
prior_year.Sales AS PriorYearSales,
ROUND(
(current_year.Sales - prior_year.Sales) / prior_year.Sales * 100,
2
) AS YoYGrowthPercent
FROM sales_current current_year
LEFT JOIN sales_prior prior_year
ON current_year.Month = prior_year.Month

Best Practices​

1. Version Control .pbix Files​

# Store .pbix in version control or shared storage
/data/models/
├── sales_v2024.01.pbix
├── finance_v2024.01.pbix
└── hr_dashboard.pbix

2. Automate .pbix Refresh​

#!/bin/bash
# refresh_powerbi.sh - run on schedule

# Refresh Power BI Desktop file (requires Windows + Power BI Desktop)
powershell -Command "
& 'C:\Program Files\Microsoft Power BI Desktop\bin\PBIDesktop.exe' \
-openFile 'C:\models\sales_dashboard.pbix' \
-refresh
"

# Copy updated file to flAPI location
cp C:\models\sales_dashboard.pbix /data/models/

3. Secure Model Files​

Restrict file permissions on the server, and protect the endpoint with one of flAPI's supported auth schemes (basic, jwt, bearer, oidc):

# sqls/sales_summary.yaml
auth:
enabled: true
type: bearer
jwt-secret: '${POWERBI_JWT_SECRET}'

Troubleshooting​

Issue: "Cannot open .pbix file"​

Error: Failed to read Power BI file

Solutions:

  1. Verify file path is correct
  2. Check file permissions (flAPI must have read access)
  3. Ensure .pbix is not open in Power BI Desktop
  4. Validate file is not corrupted

Issue: Slow queries​

Problem: Queries take > 500ms

Solutions:

  1. Enable caching (essential)
  2. Export to Parquet for large datasets
  3. Use incremental refresh for large models

Issue: Tables not found​

Error: Table 'Sales' not found in .pbix

# List all tables in .pbix
duckdb -c "
INSTALL pbix FROM community;
LOAD pbix;
SELECT * FROM pbix_tables('model.pbix');
"

Issue: Outdated data​

Problem: API returns stale data

Solution: Refresh .pbix file more frequently

# Automate with cron
0 */2 * * * /scripts/refresh_powerbi.sh

Migration Path​

Phase 1: Direct .pbix Access​

connections:
powerbi:
init: |
INSTALL pbix FROM community;
LOAD pbix;
properties:
pbix_file: '/data/sales_dashboard.pbix'

Phase 2: Add Caching​

cache:
enabled: true
table: powerbi_cache
schedule: 6h
template-file: powerbi_cache.sql

Phase 3: Export to Parquet (for scale)​

# Export high-traffic tables
duckdb export_to_parquet.sql
-- export_to_parquet.sql
COPY (SELECT * FROM pbix_scan('model.pbix', 'Sales'))
TO 'sales.parquet';

Next Steps​

Additional Resources​


When to Use

Power BI integration is perfect when you have existing BI models with complex logic that you want to expose via API. Don't rebuild what already works - just expose it with flAPI!

🍪 Cookie Settings