Skip to main content

sql-to-dax

Guide for translating SQL aggregation expressions into DAX measures.

Informations de source

Dépôt
microsoft/semantic-link-labs
Dernière activité de la source
12 mai 2026 à 10:40
Langue détectée de SKILL.md
anglais
Étoiles
586
Forks
197

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
sql_to_dax
description
Guide for translating SQL aggregation expressions into DAX measures.
# SKILL.md — SQL to DAX Metric Translation ## Purpose Translate analytical SQL aggregation expressions into equivalent DAX measures. The goal is semantic equivalence, not syntactic similarity. The generated DAX should: - Follow Power BI / Tabular best practices - Prefer iterator functions when row context is required - Use DIVIDE instead of `/ NULLIF(...,0)` - Fully qualify columns using `'table'[column]` - Use CALCULATE where filter context translation is required - Preserve aggregation semantics exactly - Avoid SQL constructs unsupported in DAX by rewriting logically --- # SQL Identifier Parsing Rules The SQL source may reference columns using any of the following formats: ```sql column_name ``` ```sql table.column_name ``` ```sql table.`column name` ``` The translator must normalize all forms into valid DAX column references. --- ## Identifier Normalization Rules | SQL Format | DAX Format | |---|---| | column_name | 'table'[column_name] | | table.column_name | 'table'[column_name] | | table.`column name` | 'table'[column name] | --- ## Backtick Handling SQL backticks must be removed during translation. ### SQL ```sql dim_product.`standard cost` ``` ### DAX ```dax 'dim_product'[standard cost] ``` --- ## Unqualified Column Resolution If a column is referenced without a table qualifier: ```sql SUM(SALES_AMOUNT) ``` The translator should: 1. Infer the table from model metadata if available 2. Prefer the primary fact table in the expression 3. Fully qualify the final DAX output ### DAX ```dax SUM('fact_sales'[SALES_AMOUNT]) ``` > **Important — bare identifiers inside scalar aggregates must NOT become iterators.** > > When a SQL aggregate wraps a single bare identifier (e.g. `SUM(ORIGINAL_SALES_AMOUNT)`, > `SUM(COST_OF_GOODS_SOLD)`) and that identifier is not declared as a > column in the model metadata, the translator must still emit the > scalar aggregation form against the default/owning table: > > ```dax > SUM('fact_returns'[ORIGINAL_SALES_AMOUNT]) > ``` > > It must **not** fall through to the iterator form: > > ```dax > SUMX('fact_returns', ORIGINAL_SALES_AMOUNT) -- INVALID > ``` > > Iterators (`SUMX`, `AVERAGEX`, etc.) are reserved for cases where the > aggregate argument contains arithmetic or references multiple > columns. A single bare token is always a scalar aggregation. --- ## Mixed Identifier Formats Expressions may mix styles. ### SQL ```sql SUM(fact_sales.`sales amount` - DISCOUNT_AMOUNT) ``` ### DAX ```dax SUMX( 'fact_sales', 'fact_sales'[sales amount] - 'fact_sales'[DISCOUNT_AMOUNT] ) ``` --- # Core Translation Rules --- ## 1. Aggregate Functions ### SQL ```sql SUM(column) ``` ### DAX ```dax SUM('table'[column]) ``` --- ### SQL ```sql AVG(column) ``` ### DAX ```dax AVERAGE('table'[column]) ``` --- ### SQL ```sql COUNT(DISTINCT column) ``` ### DAX ```dax DISTINCTCOUNT('table'[column]) ``` --- ## 2. Arithmetic Inside Aggregations If arithmetic occurs inside SUM/AVG/etc., use iterator functions. ### SQL ```sql SUM(price * quantity) ``` ### DAX ```dax SUMX( 'table', 'table'[price] * 'table'[quantity] ) ``` --- ### SQL ```sql SUM(revenue - discount) ``` ### DAX ```dax SUMX( 'table', 'table'[revenue] - 'table'[discount] ) ``` --- ### SQL ```sql AVG(quantity * cost) ``` ### DAX ```dax AVERAGEX( 'table', 'table'[quantity] * 'table'[cost] ) ``` --- # Iterator Function Rules Use iterator functions when: - Multiple columns participate in row-level arithmetic - Expressions exist inside aggregate functions - Mixed table references occur inside aggregation | SQL Aggregate | DAX Iterator | |---|---| | SUM(expr) | SUMX(table, expr) | | AVG(expr) | AVERAGEX(table, expr) | | MIN(expr) | MINX(table, expr) | | MAX(expr) | MAXX(table, expr) | --- # Safe Division --- ## SQL NULLIF Pattern ### SQL ```sql SUM(sales) / NULLIF(SUM(cost), 0) ``` ### DAX ```dax DIVIDE( SUM('table'[sales]), SUM('table'[cost]) ) ``` --- ## Nested NULLIF ### SQL ```sql 365 / NULLIF(metric, 0) ``` ### DAX ```dax DIVIDE( 365, [metric] ) ``` --- # Percentage Calculations --- ### SQL ```sql (metric / total) * 100 ``` ### DAX ```dax DIVIDE( [metric], [total] ) * 100 ``` --- # ROUND Translation --- ### SQL ```sql ROUND(expression, 2) ``` ### DAX ```dax ROUND(expression, 2) ``` --- # DIV0 Translation DIV0 means divide-by-zero-safe division. ### SQL ```sql DIV0(a, b) ``` ### DAX ```dax DIVIDE(a, b) ``` --- # Window Function Translation --- ## Rolling Window SUM Translate `<agg>(<inner>) OVER (ORDER BY <col> ROWS BETWEEN N PRECEDING AND CURRENT ROW)` into a `CALCULATE` wrapping the inner aggregation with a `DATESINPERIOD` filter over the ORDER BY column. Rules: - The window's ORDER BY column becomes the date column passed to `DATESINPERIOD`. It is resolved through the column map and fully qualified as `'table'[column]`. - `MAX(<order_col>)` is used as the anchor date. - `ROWS BETWEEN N PRECEDING AND CURRENT ROW` becomes `-N, DAY`. The skill preserves the literal `N` from the SQL (e.g. `89 PRECEDING` → `-89, DAY`) rather than rounding up to the inclusive day count. - When the inner expression is itself an aggregate (a non-standard but common Snowflake/BigQuery pattern such as `SUM(SUM(...)) OVER (...)`), the redundant outer aggregate is stripped and only the inner aggregation body is preserved. - The inner aggregation body is translated using the normal aggregation rules (including the additive distribution rule that turns `SUM(a - b)` into `(SUM(a) - SUM(b))`). ### SQL ```sql SUM(metric) OVER ( ORDER BY DATE_KEY ROWS BETWEEN 89 PRECEDING AND CURRENT ROW ) ``` ### DAX ```dax CALCULATE( [metric], DATESINPERIOD( 'dim_date'[date_key], MAX('dim_date'[date_key]), -89, DAY ) ) ``` --- ## Rolling Window over a Nested Aggregate ### SQL ```sql SUM( SUM(EXTENDED_AMOUNT - DISCOUNT_AMOUNT) ) OVER ( ORDER BY DATE_KEY ROWS BETWEEN 89 PRECEDING AND CURRENT ROW ) ``` ### DAX ```dax CALCULATE( ( SUM('fact_sales'[sales_amount]) - SUM('fact_sales'[discount_amount]) ), DATESINPERIOD( 'dim_date'[date_key], MAX('dim_date'[date_key]), -89, DAY ) ) ``` Equivalently (when distribution is not applied), the inner body may be emitted in iterator form: ```dax CALCULATE( SUMX( 'fact_sales', 'fact_sales'[sales_amount] - 'fact_sales'[discount_amount] ), DATESINPERIOD( 'dim_date'[date_key], MAX('dim_date'[date_key]), -89, DAY ) ) ``` --- ## Unbounded Window An unbounded window (`OVER ()` with no PARTITION BY, ORDER BY, or frame) ignores the current filter context entirely. Translate it to the **iterator form of the aggregate over `ALL(<table>)`** — not to `CALCULATE(<agg>, ALL(<table>))`. | SQL aggregate | DAX | |---|---| | `SUM(col) OVER ()` | `SUMX(ALL('table'), 'table'[col])` | | `AVG(col) OVER ()` | `AVERAGEX(ALL('table'), 'table'[col])` | | `MIN(col) OVER ()` | `MINX(ALL('table'), 'table'[col])` | | `MAX(col) OVER ()` | `MAXX(ALL('table'), 'table'[col])` | | `COUNT(col) OVER ()` | `COUNTX(ALL('table'), 'table'[col])` | | `COUNT(*) OVER ()` | `COUNTROWS(ALL('table'))` | | `COUNT(DISTINCT col) OVER ()` | `CALCULATE(DISTINCTCOUNT('table'[col]), ALL('table'))` | ### SQL ```sql MAX(cutoff) OVER () ``` ### DAX ```dax MAXX(ALL('table'), 'table'[cutoff]) ``` ### SQL ```sql SUM(amount) OVER () ``` ### DAX ```dax SUMX(ALL('table'), 'table'[amount]) ``` --- # CASE WHEN Translation --- ## Conditional DISTINCTCOUNT ### SQL ```sql COUNT(DISTINCT CASE WHEN order_count > 1 THEN customer_key END) ``` ### DAX ```dax CALCULATE( DISTINCTCOUNT('table'[customer_key]), 'table'[order_count] > 1 ) ``` --- # Multi-Table Arithmetic When expressions reference multiple tables: - Preserve table qualification - Use iterator functions - Choose the fact table as the iterator table when possible --- ## Iterator Table Selection from Relationships When an X-function (`SUMX`, `AVERAGEX`, `COUNTX`, `MINX`, `MAXX`) is needed and the inner expression references two tables that participate in a relationship, choose the iterator table as the **"from" side** of that relationship (typically the many / fact side). Wrap any column reference to the **"to" side** (typically the one / dimension side) in `RELATED(...)`. This rule is independent of the table that the measure is *defined on* — what matters is which table is on the many side of the relationship linking the two referenced tables. ### Example Given a relationship: `fact_sales (Many) → dim_product (One)` ### SQL ```sql SUM(dim_product.standard_cost * fact_sales.quantity_sold) ``` ### DAX ```dax SUMX( 'fact_sales',
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub