diff --git a/docfx.json b/docfx.json index 73d54ab3..3459c351 100644 --- a/docfx.json +++ b/docfx.json @@ -15,7 +15,7 @@ "build": { "content": [ { - "files": [ "te3/**/*.md", "te2/**/*.md", "onboarding/**/*.md", "common/**/*.md", "*.md", "toc.yml", "api/*" ] + "files": [ "te3/**/*.md", "te2/**/*.md", "onboarding/**/*.md", "common/**/*.md", "*.md", "toc.yml", "api/*", "kb/**/*.md" ] } ], "template": [ diff --git a/images/features/dax_query_window/dax_query_toolbar.png b/images/features/dax_query_window/dax_query_toolbar.png index a2037a64..0b1c107f 100644 Binary files a/images/features/dax_query_window/dax_query_toolbar.png and b/images/features/dax_query_window/dax_query_toolbar.png differ diff --git a/kb/DI001.md b/kb/DI001.md new file mode 100644 index 00000000..d14d4229 --- /dev/null +++ b/kb/DI001.md @@ -0,0 +1,40 @@ +--- +uid: DI001 +category: Code actions +sub-category: Improvements +title: Remove unused variable +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI001` (Improvement) **Remove unused variable** + +## Description + +Variables that are not being referenced anywhere, should be removed. + +## Example + +Change: +```dax +VAR _internetSalesTaxed = [Internet Sales] * 1.25 +VAR _cogsTaxed = [Cost of Sales] * 1.25 +VAR _internetSales = [Internet Sales] +RETURN _internetSalesTaxed - _cogsTaxed +``` +To: +```dax +VAR _internetSalesTaxed = [Internet Sales] * 1.25 +VAR _cogsTaxed = [Cost of Sales] * 1.25 +RETURN _internetSalesTaxed - _cogsTaxed +``` + +## Why is Tabular Editor suggesting this? + +Unused variables can make your code harder to read and understand. By removing them, you make your code more concise and easier to maintain. + +An unused variable is also an indication that the code may contain a mistake, or that the variable was intended to be used for something that was later removed. By removing the variable, you avoid potential confusion for other developers who may be reading your code. + +## Related to: + +- [DI002 - Remove all unused variables](xref:DI002) \ No newline at end of file diff --git a/kb/DI002.md b/kb/DI002.md new file mode 100644 index 00000000..7ef4489b --- /dev/null +++ b/kb/DI002.md @@ -0,0 +1,45 @@ +--- +uid: DI002 +category: Code actions +sub-category: Improvements +title: Remove unused variable +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI002` (Improvement) **Remove all unused variables** + +## Description + +Variables that are not being used (directly or indirectly through other variables) in the `RETURN` part of a variable block, should be removed. + +## Example + +Change: +```dax +VAR _internetSalesTaxed = [Internet Sales] * 1.25 +VAR _cogs = [Cost of Sales] +VAR _cogsTaxed = _cogs * 1.25 +RETURN _internetSalesTaxed +``` +To: +```dax +VAR _internetSalesTaxed = [Internet Sales] * 1.25 +RETURN _internetSalesTaxed +``` + +## Why is Tabular Editor suggesting this? + +Unused variables can make your code harder to read and understand. By removing them, you make your code more concise and easier to maintain. + +An unused variable is also an indication that the code may contain a mistake, or that the variable was intended to be used for something that was later removed. By removing the variable, you avoid potential confusion for other developers who may be reading your code. + +If a variable is referenced by other variables, but none of the variables are used in the `RETURN` part of the variable block, it is safe to remove all of them. + +## Remarks + +This code action has an **(All occurrences)** variant, which will appear when multiple sections of code can be improved. This variant will apply the code action to all relevant sections of the document at once. + +## Related to: + +- [DI001 - Remove unused variable](xref:DI001) \ No newline at end of file diff --git a/kb/DI003.md b/kb/DI003.md new file mode 100644 index 00000000..5e334e1a --- /dev/null +++ b/kb/DI003.md @@ -0,0 +1,41 @@ +--- +uid: DI003 +category: Code actions +sub-category: Improvements +title: Remove table name +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI003` (Improvement) **Remove table name from measure references** + +## Description + +Measure references should not include the table name, as the table name is unnecessary when referencing measures. + +## Example + +Change: +```dax +'Internet Sales'[Internet Total Sales] * 1.25 +``` +To: +```dax +[Internet Total Sales] * 1.25 +``` + +## Why is Tabular Editor suggesting this? + +In most situations, *column references* **must** be qualified by the table name. However, for *measure references*, the table name is always optional (since measure names are always unique within a model). + +Thus, a best practice is to always reference measures *without* the table name, and always reference columns *with* the table name. + +By applying this practice consistently, you make your code more concise and easier to read, and you make it easier to distinguish between measure references and column references. + +## Remarks + +This code action has an **(All occurrences)** variant, which will appear when multiple sections of code can be improved. This variant will apply the code action to all relevant sections of the document at once. + +## Related to: + +- [DI004 - Add table name to column references](xref:DI004) \ No newline at end of file diff --git a/kb/DI004.md b/kb/DI004.md new file mode 100644 index 00000000..c69a972f --- /dev/null +++ b/kb/DI004.md @@ -0,0 +1,41 @@ +--- +uid: DI004 +category: Code actions +sub-category: Improvements +title: Add table name +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI004` (Improvement) **Add table name to column references** + +## Description + +Column references should always include the table name to avoid ambiguities, even when the table name is optional. + +## Example + +Change: +```dax +SUMX('Internet Sales', [Line Amount] * [Quantity]) +``` +To: +```dax +SUMX('Internet Sales', 'Internet Sales'[Line Amount] * 'Internet Sales'[Quantity]) +``` + +## Why is Tabular Editor suggesting this? + +Since *measure* names are always unique within a model, it is always possible to reference a measure without specifying the table name. However, column names are not unique within a model, and it is therefore necessary to specify the table name when referencing a column, such as when using one of the aggregation functions, i.e.: `SUM('Sales'[Amount])`. + +However, there are situations in which the table qualifier is optional for column references. For example, this is the case when the column exists within an active row context (such as inside a [Calculated Column](https://learn.microsoft.com/en-us/analysis-services/tabular-models/ssas-calculated-columns-create-a-calculated-column?view=asallproducts-allversions)). Even so, providing the table name in this case is still valid, and helps avoid ambiguities and errors in case a measure with the same name is eventually added to the model. + +By applying this practice consistently, you make your code more concise and easier to read, and you make it easier to distinguish between measure references and column references. + +## Remarks + +This code action has an **(All occurrences)** variant, which will appear when multiple sections of code can be improved. This variant will apply the code action to all relevant sections of the document at once. + +## Related to: + +- [DI003 - Remove table name from measure references](xref:DI003) \ No newline at end of file diff --git a/kb/DI005.md b/kb/DI005.md new file mode 100644 index 00000000..d6d2ea07 --- /dev/null +++ b/kb/DI005.md @@ -0,0 +1,68 @@ +--- +uid: DI005 +category: Code actions +sub-category: Improvements +title: Rewrite table filter as scalar predicate +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI005` (Improvement) **Rewrite table filter as scalar predicate** + +## Description + +Rewrite [`CALCULATE`](https://dax.guide/CALCULATE) filter arguments as scalar predicates when possible, instead of using the [`FILTER`](https://dax.guide/FILTER) function. + +## Example 1 + +Change: +```dax +CALCULATE([Total Sales], FILTER(Products, Products[Color] = "Red")) +``` +To: +```dax +CALCULATE([Total Sales], KEEPFILTERS(Products[Color] = "Red")) +``` + +## Example 2 + +Change: +```dax +CALCULATE([Total Sales], FILTER(ALL(Products), Products[Color] = "Red")) +``` +To: +```dax +CALCULATE([Total Sales], Products[Color] = "Red") +``` + +## Example 3 + +Change: +```dax +CALCULATE( + [Total Sales], + FILTER( + ALL(Products), + Products[Color] = "Red" + && Products[Class] = "High-end" + ) +) +``` +To: +```dax +CALCULATE( + [Total Sales], + Products[Color] = "Red", + Products[Class] = "High-end" +) +``` + +## Why is Tabular Editor suggesting this? + +Filtering a table inside a `CALCULATE` filter argument is less efficient than filtering one or more columns from that table. By rewriting the filter as a scalar predicate, you make your code more efficient, consuming less memory and CPU resources. + +For example, an expression such as `FILTER(Sales, < condition >)` will iterate over all rows in the `Sales` table, evaluating the condition for each row. In contrast, an expression such as `Sales[Quantity] > 0` will only iterate over the `Quantity` column, which is much more efficient, and does not cause all the columns from the `Sales` table to be added to the filter context. + +By using scalar predicates, you also make your code more concise and easier to read. + +Behind the scenes, scalar predicates are syntax sugar for a table expression that also uses the `FILTER` function. However, the `FILTER` function is applied to a single column, which is more efficient than filtering the entire table. \ No newline at end of file diff --git a/kb/DI006.md b/kb/DI006.md new file mode 100644 index 00000000..c211ed68 --- /dev/null +++ b/kb/DI006.md @@ -0,0 +1,43 @@ +--- +uid: DI006 +category: Code actions +sub-category: Improvements +title: Split multi-column filter into multiple filters +author: Daniel Otykier +updated: 2024-12-19 +--- + +Code action `DI006` (Improvement) **Split multi-column filter into multiple filters** + +## Description + +When using a single filter expression that combines multiple columns using `AND` (or the equivalent `&&` operator), better performance can often be achieved by specifying multiple filters, one for each column. + +## Example + +Change: +```dax +CALCULATE([Total Sales], Products[Color] = "Red" && Products[Class] = "High-end") +``` +To: +```dax +CALCULATE([Total Sales], Products[Color] = "Red", Products[Class] = "High-end") +``` + +## Why is Tabular Editor suggesting this? + +Behind the scenes, a scalar predicate gets converted to a table expression that uses the `FILTER` function along with the specified condition. The table resulting from this expression has one column for each column in the filter expression. I.e.: + +```dax +Products[Color] = "Red" && Products[Class] = "High-end" +``` + +becomes: + +```dax +FILTER(ALL(Products[Color], Products[Class]), Products[Color] = "Red" && Products[Class] = "High-end") +``` + +The `ALL` function, when used with multiple column parameters, returns a table with all the unique combinations of the specified columns. This table is then filtered by the specified condition, and the resulting table then applies to the filter context of the `CALCULATE` or `CALCULATETABLE` function. + +However, when all operands in the filter condition are combined using `AND`, it is more efficient to separate these as individual filters. That way, instead of creating a table with all unique combinations of the columns, several smaller tables are created, each with a single column containing only the unique values of that column, that satisfy the filter criteria. This can result in a significant performance improvement, especially when the columns have a high cardinality and low correlation. \ No newline at end of file diff --git a/kb/DI007.md b/kb/DI007.md new file mode 100644 index 00000000..e77da953 --- /dev/null +++ b/kb/DI007.md @@ -0,0 +1,43 @@ +--- +uid: DI007 +category: Code actions +sub-category: Improvements +title: Simplify SWITCH statement +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI007` (Improvement) **Simplify SWITCH statement** + +## Description + +A [`SWITCH`](https://dax.guide/SWITCH) statement that specifies `TRUE()` for the **<Expression>** argument, and where all **<Value>** arguments are simple comparisons of the same variable/measure, can be simplified. + +## Example + +Change: +```dax +SWITCH( + TRUE(), + [Selected Currency] = "EUR", [Total Sales EUR], + [Selected Currency] = "USD", [Total Sales USD], + [Selected Currency] = "DKK", [Total Sales DKK], + [Total Sales] +) +``` +To: +```dax +SWITCH( + [Selected Currency], + "EUR", [Total Sales EUR], + "USD", [Total Sales USD], + "DKK", [Total Sales DKK], + [Total Sales] +) +``` + +## Why is Tabular Editor suggesting this? + +A common DAX pattern to specify a conditional expression with more than 2 conditions, is to use the `SWITCH` statement with `TRUE()` as the first argument. By using this technique, one can then provide condition-expression-pairs for the remaining `SWITCH` arguments. The first condition that evaluates to `TRUE` will determine the result of the `SWITCH` statement. + +However, when all conditions are simple equality comparisons against the same value (`[Selected Currency]` in the example above), the `SWITCH` statement should be simplified to its intended form, where the first argument is the expression to evaluate, and the remaining arguments are pairs of values and results. The first value to match the expression will determine the result of the `SWITCH` statement. \ No newline at end of file diff --git a/kb/DI008.md b/kb/DI008.md new file mode 100644 index 00000000..6d9ffd07 --- /dev/null +++ b/kb/DI008.md @@ -0,0 +1,62 @@ +--- +uid: DI008 +category: Code actions +sub-category: Improvements +title: Remove superfluous CALCULATE +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI008` (Improvement) **Remove superfluous CALCULATE** + +## Description + +Do not explicitly call [`CALCULATE`](https://dax.guide/CALCULATE) or [`CALCULATETABLE`](https://dax.guide/CALCULATETABLE), when it is not necessary. + +### Example 1 - Measure reference with no filter context modifiers + +In the below examples, `[Total Sales]` is a measure reference. + +Change: +```dax +CALCULATE([Total Sales]) +``` +To: +```dax +[Total Sales] +``` + +### Example 2 - Measure reference in a row context + +Change: +```dax +AVERAGEX(Product, CALCULATE([Total Sales])) +``` +To: +```dax +AVERAGEX(Product, [Total Sales]) +``` + +### Example 3 - Constant values are not affected by filter context modifications + +Change: +```dax +VAR _salesWithTax = [Total Sales] * 1.25 +RETURN + CALCULATE(_salesWithTax, Product[Color] = "Red") +``` + +To: +```dax +VAR _salesWithTax = [Total Sales] * 1.25 +RETURN + _salesWithTax +``` + +## Why is Tabular Editor suggesting this? + +The `CALCULATE` function is used to modify the filter context of a calculation and force a context transition when needed. However, when the expression is not impacted by a context transition, and the filter context is not modified, the `CALCULATE` function is superfluous and can be removed (Example 1). This can make the code easier to read and understand. + +Moreover, if the `CALCULATE` function is only used to enforce a context transition and not modify the filter context (i.e. no filter arguments), but the expression is a simple measure reference, then the `CALCULATE` function can be removed (Example 2), since measure references perform implicit context transitions, when evaluated in a row context. + +Lastly, if the expression is constant (such as when referencing a variable that was defined outside of the `CALCULATE` function, such as in Example 3 above), the modified filter context will not affect the result of the expression, and the `CALCULATE` function including all filter arguments can be removed. \ No newline at end of file diff --git a/kb/DI009.md b/kb/DI009.md new file mode 100644 index 00000000..7511b303 --- /dev/null +++ b/kb/DI009.md @@ -0,0 +1,29 @@ +--- +uid: DI009 +category: Code actions +sub-category: Improvements +title: Avoid calculate shortcut syntax +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI009` (Improvement) **Avoid calculate shortcut syntax** + +## Description + +Do not use the calculate shortcut syntax. + +### Example + +Change: +```dax +[Total Sales](Products[Color] = "Red") +``` +To: +```dax +CALCULATE([Total Sales], Products[Color] = "Red") +``` + +## Why is Tabular Editor suggesting this? + +The calculate shortcut syntax is a shorthand for the `CALCULATE` function, where the first argument is the measure to evaluate, and the second argument is the filter expression. While this syntax is valid, it is not recommended, as it can be confusing to read and understand. It is better to use the `CALCULATE` function explicitly, as it makes the code more readable and maintainable. \ No newline at end of file diff --git a/kb/DI010.md b/kb/DI010.md new file mode 100644 index 00000000..5a96bc22 --- /dev/null +++ b/kb/DI010.md @@ -0,0 +1,31 @@ +--- +uid: DI010 +category: Code actions +sub-category: Improvements +title: Use MIN/MAX instead of IF +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI010` (Improvement) **Use MIN/MAX instead of IF** + +## Description + +When a conditional expression is used to return the minimum or maximum of two values, it is more efficient and compact to use the [`MIN`](https://dax.guide/MIN) or [`MAX`](https://dax.guide/MAX) function. + +### Example + +Change: +```dax +IF([Total Sales] > 0, [Total Sales], 0)) +``` +To: +```dax +MAX([Total Sales], 0) +``` + +## Why is Tabular Editor suggesting this? + +A common anti-pattern in DAX is to use an `IF` statement to return the smaller or larger of two values, by first comparing them, and then returning the appropriate value. However, this pattern can be simplified by using the `MIN` or `MAX` functions, which are more efficient and easier to read. The `MIN` function, when called with two arguments, returns the smallest value of the two arguments, while the `MAX` function returns the largest value. By using these functions, the code becomes more concise and easier to understand. + +Moreover, if any measure references are included in the arguments, they are only evaluated once, which can improve performance. \ No newline at end of file diff --git a/kb/DI011.md b/kb/DI011.md new file mode 100644 index 00000000..34e89e0d --- /dev/null +++ b/kb/DI011.md @@ -0,0 +1,31 @@ +--- +uid: DI011 +category: Code actions +sub-category: Improvements +title: Use ISEMPTY instead of COUNTROWS +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI011` (Improvement) **Use ISEMPTY instead of COUNTROWS** + +## Description + +When checking if a table is empty, it is more efficient to use the [`ISEMPTY`](https://dax.guide/ISEMPTY) function than to count the rows of the table. + +### Example + +Change: +```dax +IF(COUNTROWS(Products) = 0, "No products", "Products exist") +``` +To: +```dax +IF(ISEMPTY(Products), "No products", "Products exist") +``` + +## Why is Tabular Editor suggesting this? + +When checking if a table is empty, a common anti-pattern in DAX is to use the `COUNTROWS` function to count the rows of the table, and then compare the result to zero. However, this pattern is inefficient, as it requires the engine to count all rows of the table, even if the only thing we are interested in is whether the table is empty or not. + +By using the `ISEMPTY` function, the engine can stop counting rows as soon as it finds the first row, which is much more efficient. The `ISEMPTY` function returns `TRUE` if the table is empty, and `FALSE` otherwise, which makes it a more efficient and readable way to check if a table is empty. \ No newline at end of file diff --git a/kb/DI012.md b/kb/DI012.md new file mode 100644 index 00000000..f90382e1 --- /dev/null +++ b/kb/DI012.md @@ -0,0 +1,31 @@ +--- +uid: DI012 +category: Code actions +sub-category: Improvements +title: Use DIVIDE instead of division +author: Daniel Otykier +updated: 2025-01-03 +--- + +Code action `DI012` (Improvement) **Use DIVIDE instead of division** + +## Description + +When using an arbitrary expression in the denominator of a division, use [`DIVIDE`](https://dax.guide/DIVIDE) instead of the division operator, to avoid division by zero errors. + +### Example + +Change: +```dax +[Total Sales] / [Total Cost] +``` +To: +```dax +DIVIDE([Total Sales], [Total Cost]) +``` + +## Why is Tabular Editor suggesting this? + +When dividing two numbers in DAX, it is common to use the division operator `/`. However, if the denominator is zero, the result of the division is an error. This can be problematic in certain scenarios, as it can cause the entire expression to fail. Downstream measures may use [`IFERROR`](https://dax.guide/IFERROR) to handle this, but a more elegant and better performing solution is to use the `DIVIDE` function, which returns a specific value or (Blank) if the denominator is zero. This makes the code more robust and easier to read. + +Tabular Editor will not suggest this action if the denominator is guaranteed to be non-zero, such as when dividing by a (non-zero) constant, in which case the division operator `/` is preferred. \ No newline at end of file diff --git a/kb/DI013.md b/kb/DI013.md new file mode 100644 index 00000000..bdd53577 --- /dev/null +++ b/kb/DI013.md @@ -0,0 +1,29 @@ +--- +uid: DI013 +category: Code actions +sub-category: Improvements +title: Use division instead of DIVIDE +author: Daniel Otykier +updated: 2025-01-06 +--- + +Code action `DI013` (Improvement) **Use division instead of DIVIDE** + +## Description + +When the 2nd argument of [`DIVIDE`](https://dax.guide/DIVIDE) is a non-zero constant, it is more efficient to use the division operator. + +### Example + +Change: +```dax +DIVIDE([Total Sales], 1.25) +``` +To: +```dax +[Total Cost] / 1.25 +``` + +## Why is Tabular Editor suggesting this? + +The `DIVIDE` function is a robust way to handle division by zero errors, as it returns a specific value or (Blank) if the denominator is zero. However, when the denominator is guaranteed to be non-zero, such as when dividing by a (non-zero) constant, the division operator `/` is more efficient and easier to read. By using the division operator, the code becomes more concise and easier to understand. \ No newline at end of file diff --git a/kb/DI014.md b/kb/DI014.md new file mode 100644 index 00000000..7a2b64d3 --- /dev/null +++ b/kb/DI014.md @@ -0,0 +1,40 @@ +--- +uid: DI014 +category: Code actions +sub-category: Improvements +title: Replace IFERROR with DIVIDE +author: Daniel Otykier +updated: 2025-01-08 +--- + +Code action `DI014` (Improvement) **Replace IFERROR with DIVIDE** + +## Description + +Use the [`DIVIDE`](https://dax.guide/DIVIDE) function instead of [`IFERROR`](https://dax.guide/IFERROR) to provide an alternate result when a division has a zero demoninator. + +### Example 1 + +Change: +```dax +IFERROR([Total Sales] / [Total Cost], BLANK()) +``` +To: +```dax +DIVIDE([Total Sales], [Total Cost]) +``` + +### Example 2 + +Change: +```dax +IFERROR(([Total Sales] - [Total Cost]) / [Total Cost], 1) +``` +To: +```dax +DIVIDE([Total Sales] - [Total Cost], [Total Cost], 1) +``` + +## Why is Tabular Editor suggesting this? + +A common anti-pattern in DAX is to check for division-by-zero errors by using the `IFERROR` function. This pattern should be avoided, as evaluation errors add overhead to the query execution. Instead, the `DIVIDE` function should be used, as it checks that the denominator is not zero before the division is carried out. Moreover, using floating point arithmetics, the `DIVIDE` function is more robust and handles edge cases better than the `IFERROR` function. By using the `DIVIDE` function, the code becomes more concise and easier to understand. \ No newline at end of file diff --git a/kb/DI015.md b/kb/DI015.md new file mode 100644 index 00000000..bb8909be --- /dev/null +++ b/kb/DI015.md @@ -0,0 +1,40 @@ +--- +uid: DI015 +category: Code actions +sub-category: Improvements +title: Replace IF with DIVIDE +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DI015` (Improvement) **Replace IF with DIVIDE** + +## Description + +Use the [`DIVIDE`](https://dax.guide/DIVIDE) function instead of [`IF`](https://dax.guide/IF), to more easily check for zero or blank in the demoninator. + +### Example 1 + +Change: +```dax +IF([Total Cost] = 0, BLANK(), [Total Sales] / [Total Cost]) +``` +To: +```dax +DIVIDE([Total Sales], [Total Cost]) +``` + +### Example 2 + +Change: +```dax +IF([Total Cost] <> 0, [Total Sales] / [Total Cost]) +``` +To: +```dax +DIVIDE([Total Sales], [Total Cost]) +``` + +## Why is Tabular Editor suggesting this? + +The `DIVIDE` function is a more concise and readable way to handle division by zero or blank values. By using `DIVIDE`, you make your code more robust and easier to understand. Moreover, the `DIVIDE` function is more efficient than using `IF` to check for zero or blank values, as it only evaluates the denominator once. \ No newline at end of file diff --git a/kb/DR001.md b/kb/DR001.md new file mode 100644 index 00000000..56786491 --- /dev/null +++ b/kb/DR001.md @@ -0,0 +1,51 @@ +--- +uid: DR001 +category: Code actions +sub-category: Readability +title: Convert to scalar predicate +author: Daniel Otykier +updated: 2025-01-06 +--- + +Code action `DR001` (Readability) **Convert to scalar predicate** + +## Description + +A column filter can be written more concisely as a scalar predicate, without explicitly using the [`FILTER`](https://dax.guide/FILTER) function. + +### Example 1 + +Change: +```dax +CALCULATE( + [Invoice Amount], + FILTER(ALL('Document'[Document Type]), 'Document'[Document Type] = "Sales Order") +) +``` +To: +```dax +CALCULATE( + [Invoice Amount], + 'Document'[Document Type] = "Sales Order" +) +``` +### Example 2 + +Change: +```dax +CALCULATE( + [Invoice Amount], + FILTER(VALUES('Document'[Document Type]), 'Document'[Document Type] = "Sales Order") +) +``` +To: +```dax +CALCULATE( + [Invoice Amount], + KEEPFILTERS('Document'[Document Type] = "Sales Order") +) +``` + +## Why is Tabular Editor suggesting this? + +A scalar predicate is a simpler and more concise way (e.g. "syntax sugar") to express a column filter, compared to using the `FILTER` function explicitly. By using a scalar predicate, the code becomes easier to read and understand, as it removes unnecessary complexity and makes the intent of the filter expression more clear. \ No newline at end of file diff --git a/kb/DR002.md b/kb/DR002.md new file mode 100644 index 00000000..edfa81ca --- /dev/null +++ b/kb/DR002.md @@ -0,0 +1,33 @@ +--- +uid: DR002 +category: Code actions +sub-category: Readability +title: Use aggregator instead of iterator +author: Daniel Otykier +updated: 2025-01-06 +--- + +Code action `DR002` (Readability) **Use aggregator instead of iterator** + +## Description + +Use an aggregator function instead of an iterator function when possible, to simplify the code. + +### Example + +Change: +```dax +SUMX(Sales, Sales[Line Amount]) +``` +To: +```dax +SUM(Sales[Line Amount]) +``` + +## Why is Tabular Editor suggesting this? + +When you need to aggregate the values of a single column only, aggregator functions ([`SUM`](https://dax.guide), [`MIN`](https://dax.guide), [`MAX`](https://dax.guide), etc.) use simpler, more concise syntax, than their equivalent iterator functions ([`SUMX`](https://dax.guide), [`MINX`](https://dax.guide), [`MAXX`](https://dax.guide), etc.) and should be preferred to make the code more readable. + +## Related to: + +- [DR003 - Use VALUES instead of SUMMARIZE](xref:DR003) \ No newline at end of file diff --git a/kb/DR003.md b/kb/DR003.md new file mode 100644 index 00000000..8e6a5cd2 --- /dev/null +++ b/kb/DR003.md @@ -0,0 +1,33 @@ +--- +uid: DR003 +category: Code actions +sub-category: Readability +title: Use VALUES instead of SUMMARIZE +author: Daniel Otykier +updated: 2025-01-06 +--- + +Code action `DR003` (Readability) **Use VALUES instead of SUMMARIZE** + +## Description + +When [`SUMMARIZE`](https://dax.guide/SUMMARIZE) only specifies a single column, and that column belongs to the table specified in the first argument, the code can be more concisely written using [`VALUES`](https://dax.guide/VALUES). + +### Example + +Change: +```dax +SUMMARIZE(Sales, Sales[Product Key]) +``` +To: +```dax +VALUES(Sales[Product Key]) +``` + +## Why is Tabular Editor suggesting this? + +The `SUMMARIZE` function is a powerful function that can be used to group data and calculate aggregates. However, when you only need to retrieve the distinct values of a single column, the `VALUES` function is more concise and easier to read. By using the `VALUES` function, the code becomes more readable and the intent of the expression is clearer. + +## Related to: + +- [DR002 - Use aggregator instead of iterator](xref:DR002) \ No newline at end of file diff --git a/kb/DR004.md b/kb/DR004.md new file mode 100644 index 00000000..46db6e83 --- /dev/null +++ b/kb/DR004.md @@ -0,0 +1,45 @@ +--- +uid: DR004 +category: Code actions +sub-category: Readability +title: Prefix variable +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR004` (Readability) **Prefix variable** + +## Description + +It is recommended to use a consistent prefix for variables, to more easily distinguish them from table references. The default prefix is `_`, but you can configure which prefix to use, to match your preferred style, under **Tools > Preferences > DAX Editor > Code Actions**. + +### Example + +Change: +```dax +VAR sales = SUM('Internet Sales'[Sales Amount]) +RETURN sales * 1.25; +``` +To: +```dax +VAR _sales = SUM('Internet Sales'[Sales Amount]) +RETURN _sales * 1.25; +``` + +## Why is Tabular Editor suggesting this? + +This code action is designed to improve the readability of your DAX code. By using a consistent naming convention for your variables, it is easier to understand the purpose of each variable, and to distinguish between variables and tables. + +Moreover, using a special character (such as an underscore) as a prefix for variables can help to avoid naming conflicts with table names, in case a table with the same name as the variable is added to the model in the future. + +## Remarks + +This code action has an **(All occurrences)** variant, which will appear when multiple sections of code can be improved. This variant will apply the code action to all relevant sections of the document at once. + +## Related to: + +- [DR005 - Prefix temporary column](xref:DR005) + +## Further reading: + +- [SQLBI: Naming variables in DAX](https://www.sqlbi.com/blog/marco/2019/01/15/naming-variables-in-dax/) \ No newline at end of file diff --git a/kb/DR005.md b/kb/DR005.md new file mode 100644 index 00000000..bec87ff6 --- /dev/null +++ b/kb/DR005.md @@ -0,0 +1,47 @@ +--- +uid: DR005 +category: Code actions +sub-category: Readability +title: Prefix temporary column +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR005` (Readability) **Prefix temporary column** + +## Description + +It is recommended to use a consistent prefix for temporary columns, to more easily distinguish them from base columns or measures. The default prefix is `@` but you can configure which prefix to use, to match your preferred style, under **Tools > Preferences > DAX Editor > Code Actions**. + +### Example + +Change: +```dax +ADDCOLUMNS( + 'Sales', + "Sales With Tax", 'Sales'[Sales Amount] * 1.25 +) +``` +To: +```dax +ADDCOLUMNS( + 'Sales', + "@Sales With Tax", 'Sales'[Sales Amount] * 1.25 +) +``` + +## Why is Tabular Editor suggesting this? + +This code action is designed to improve the readability of your DAX code. By using a consistent naming convention for your temporary columns, it is easier to understand the purpose of each column, and to distinguish between base columns, measures, and extension columns. + +## Remarks + +This code action has an **(All occurrences)** variant, which will appear when multiple sections of code can be improved. This variant will apply the code action to all relevant sections of the document at once. + +## Related to: + +- [DR004 - Prefix variable](xref:DR004) + +## Further reading: + +- [SQLBI: Naming temporary columns in DAX](https://www.sqlbi.com/articles/naming-temporary-columns-in-dax/) \ No newline at end of file diff --git a/kb/DR006.md b/kb/DR006.md new file mode 100644 index 00000000..1f2da5f8 --- /dev/null +++ b/kb/DR006.md @@ -0,0 +1,45 @@ +--- +uid: DR006 +category: Code actions +sub-category: Readability +title: Move constant aggregation to variable +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR006` (Readability) **Move constant aggregation to variable** + +## Description + +When an aggregation function is used inside an iterator or a scalar predicate, the aggregation produces the same result for every row of the iteration, and therefore the aggregation could be moved to a DAX variable outside of the iteration. + +### Example + +Change: +```dax +CALCULATE( + [Total Sales], + 'Date'[Date] = MAX('Date'[Date]) +) +``` +To: +```dax +VAR _maxDate = MAX('Date'[Date]) +RETURN + CALCULATE( + [Total Sales], + 'Date'[Date] = _maxDate + ) +``` + +## Why is Tabular Editor suggesting this? + +A common point of confusion for new DAX developers is the concept of row context and filter context. When an aggregation is used inside an iterator or a scalar predicate, the aggregation produces the same result for every row of the iteration. This is what enables syntax such as `'Date'[Date] = MAX('Date'[Date])`. While this syntax works and is efficient, it can be confusing to new developers - especially those with a SQL background, where this kind of syntax would be considered an error. + +Historically, variables were not supported in DAX, so the only way to achieve this kind syntax was to use the aggregation directly in the iterator. However, with the introduction of variables in DAX, it is now possible to move the aggregation to a variable outside of the iteration. This makes the code more readable and easier to understand for new developers. It also makes it easier to debug the code, as you can inspect the value of the variable outside of the iteration. + +## Remarks + +By default, Tabular Editor uses `_` as a prefix for variables. You can change the prefix under **Tools > Preferences > DAX Editor > Code Actions**. + +The name assigned to the variable is the concatenation of the aggregation function and the column name. If the variable name is not unique within the scope, a number is appended to make it unique. \ No newline at end of file diff --git a/kb/DR007.md b/kb/DR007.md new file mode 100644 index 00000000..5ef85825 --- /dev/null +++ b/kb/DR007.md @@ -0,0 +1,35 @@ +--- +uid: DR007 +category: Code actions +sub-category: Readability +title: Simplify 1-variable block +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR007` (Readability) **Simplify 1-variable block** + +## Description + +A variable block with only one variable can be simplified by moving the expression directly into the `RETURN` part of the block. This assumes the variable is only referenced once without any context modifiers. + +### Example + +Change: +```dax +VAR _sales = [Total Sales] +RETURN + _sales * 1.25 +``` +To: +```dax +[Total Sales] * 1.25 +``` + +## Why is Tabular Editor suggesting this? + +When variable declarations are sufficiently simple, and when the variable is used exactly once in the `RETURN` part of the code without any context modifications, there is no need to declare the variable at all. This makes the code more concise and easier to read. + +## Related to: + +- [DR008 - Simplify multi-variable block](xref:DR008) \ No newline at end of file diff --git a/kb/DR008.md b/kb/DR008.md new file mode 100644 index 00000000..3eb0c734 --- /dev/null +++ b/kb/DR008.md @@ -0,0 +1,41 @@ +--- +uid: DR008 +category: Code actions +sub-category: Readability +title: Simplify multi-variable block +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR008` (Readability) **Simplify multi-variable block** + +## Description + +A variable block with multiple variables where each is a simple measure reference, which are only used once in the `RETURN` section without any context modifiers, should be simplified. + +### Example + +Change: +```dax +VAR _sales = [Total Sales] +VAR _cost = [Total Cost] +RETURN + _sales - _cost +``` + +To: +```dax +[Total Sales] - [Total Cost] +``` + +## Why is Tabular Editor suggesting this? + +A common pattern in DAX is to declare a variable for each measure that is used in a calculation. This is a good practice when the value of a measure is needed in multiple places in the calculation (for performance reasons). However, when each such variable is used exactly once in the `RETURN` part of the code, in an evaluation context that would not change the result of the measure, there is no need to declare the variables at all. Instead, referencing the measures directly in the calculation should be preferred to make the code more concise and easier to read. + +## Remarks: + +The [DAX Debugger in Tabular Editor 3](xref:dax-debugger) will show the values of measures used in the calculation within the **Locals** view. This makes it easy to inspect the values of the measures during debugging, even when their values are not stored in variables. + +## Related to: + +- [DR007 - Simplify 1-variable block](xref:DR007) \ No newline at end of file diff --git a/kb/DR009.md b/kb/DR009.md new file mode 100644 index 00000000..d909b868 --- /dev/null +++ b/kb/DR009.md @@ -0,0 +1,31 @@ +--- +uid: DR009 +category: Code actions +sub-category: Readability +title: Rewrite using DISTINCTCOUNT +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR009` (Readability) **Rewrite using DISTINCTCOUNT** + +## Description + +Instead of using a combination of [`COUNTROWS`](https://dax.guide/COUNTROWS) and [`DISTINCT`](https://dax.guide/DISTINCT) to count the number of distinct values in a column, use the [`DISTINCTCOUNT`](https://dax.guide/DISTINCTCOUNT) function. + +### Example + +Change: +```dax +COUNTROWS(DISTINCT(Sales[CalendarDate])) +``` +``` + +To: +```dax +DISTINCTCOUNT(Sales[CalendarDate]) +``` + +## Why is Tabular Editor suggesting this? + +While both options produce the same result and the same query plan (i.e. identical performance), the `DISTINCTCOUNT` function is more concise and easier to read than the combination of `COUNTROWS` and `DISTINCT`. By using `DISTINCTCOUNT`, the code becomes more readable and the intent of the expression is clearer. \ No newline at end of file diff --git a/kb/DR010.md b/kb/DR010.md new file mode 100644 index 00000000..5de81896 --- /dev/null +++ b/kb/DR010.md @@ -0,0 +1,34 @@ +--- +uid: DR010 +category: Code actions +sub-category: Readability +title: Rewrite using COALESCE +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR010` (Readability) **Rewrite using COALESCE** + +## Description + +Instead of using [`IF`](https://dax.guide/IF) to return the first non-blank value from a list of expressions, use the [`COALESCE`](https://dax.guide/COALESCE) function. + +### Example + +Change: +```dax +IF( + ISBLANK(Product[Long Description]), + Product[Short Description], + Product[Long Description] +) +``` + +To: +```dax +COALESCE(Product[Long Description], Product[Short Description]) +``` + +## Why is Tabular Editor suggesting this? + +The `COALESCE` function is a more concise and easier to read way of returning the first non-blank value from a list of expressions. By using `COALESCE`, the code becomes more readable and the intent of the expression is clearer. \ No newline at end of file diff --git a/kb/DR011.md b/kb/DR011.md new file mode 100644 index 00000000..9906b237 --- /dev/null +++ b/kb/DR011.md @@ -0,0 +1,46 @@ +--- +uid: DR011 +category: Code actions +sub-category: Readability +title: Rewrite using ISBLANK +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR011` (Readability) **Rewrite using ISBLANK** + +## Description + +Instead of comparing an expression with the value returned by [`BLANK()`](https://dax.guide/BLANK), use the [`ISBLANK`](https://dax.guide/ISBLANK) function. + +### Example + +Change: +```dax +IF( + Document[Type] == BLANK(), + [Sales Amount], + [Sales Amount] * 1.25 +) +``` + +To: +```dax +IF( + ISBLANK(Document[Type]), + [Sales Amount], + [Sales Amount] * 1.25 +) +``` + +## Why is Tabular Editor suggesting this? + +The `ISBLANK` function is a more concise and easier to read way of checking if an expression returns a blank value. By using `ISBLANK`, the code becomes more readable and the intent of the expression is clearer. + +## Remarks + +This code action only applies to [strict equality comparison (==)](https://dax.guide/op/strictly-equal-to/) with `BLANK()`. The regular equality comparison `Document[Type] = BLANK()` does not produce the same result as `ISBLANK(Document[Type])` if `[Type]` is an empty string, or zero (in which case `ISBLANK(Document[Type])` would return `FALSE` while `Document[Type] = BLANK()` would return `TRUE`). + +## Further reading + +- [SQLBI: Handling BLANK in DAX](https://www.sqlbi.com/articles/blank-handling-in-dax/) \ No newline at end of file diff --git a/kb/DR012.md b/kb/DR012.md new file mode 100644 index 00000000..28e69bf7 --- /dev/null +++ b/kb/DR012.md @@ -0,0 +1,39 @@ +--- +uid: DR012 +category: Code actions +sub-category: Readability +title: Remove unnecessary BLANK +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR012` (Readability) **Remove unnecessary BLANK** + +## Description + +Some DAX functions, such as [`IF`](https://dax.guide/IF) and [`SWITCH`](https://dax.guide/SWITCH) already return [`BLANK()`](https://dax.guide/BLANK) when the condition is false, so there is no need to explicitly specify `BLANK()`. + +### Example + +Change: +```dax +SWITCH( + Document[Type], + "Invoice", [Invoice Amount], + "Credit Note", [Credit Note Amount], + BLANK() +) +``` + +To: +```dax +SWITCH( + Document[Type], + "Invoice", [Invoice Amount], + "Credit Note", [Credit Note Amount] +) +``` + +## Why is Tabular Editor suggesting this? + +The `BLANK()` function is redundant when used as the last argument in an `IF` or `SWITCH` function, as these functions already return `BLANK()` when the condition is false. By removing the `BLANK()` function, the code becomes more concise and easier to read. \ No newline at end of file diff --git a/kb/DR013.md b/kb/DR013.md new file mode 100644 index 00000000..7f3ef591 --- /dev/null +++ b/kb/DR013.md @@ -0,0 +1,42 @@ +--- +uid: DR013 +category: Code actions +sub-category: Readability +title: Simplify negated logic +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR013` (Readability) **Simplify negated logic** + +## Description + +When a logical expression is negated, it is often more readable to rewrite the expression using the inverted operator. + +### Example 1 + +Change: +```dax +NOT([Sales Amount] = [Budget Amount]) +``` + +To: +```dax +[Sales Amount] <> [Budget Amount] +``` + +### Example 2 + +Change: +```dax +NOT([Cost Amount] < [Sales Amount]) +``` + +To: +```dax +[Cost Amount] >= [Sales Amount] +``` + +## Why is Tabular Editor suggesting this? + +Negated logic can be harder to read and understand than non-negated logic. By removing the negation (the [`NOT`](https://dax.guide/NOT) operator) and inverting the comparison operator, the code becomes more concise and easier to read. \ No newline at end of file diff --git a/kb/DR014.md b/kb/DR014.md new file mode 100644 index 00000000..eea06f39 --- /dev/null +++ b/kb/DR014.md @@ -0,0 +1,30 @@ +--- +uid: DR014 +category: Code actions +sub-category: Readability +title: Simplify using IN +author: Daniel Otykier +updated: 2025-01-09 +--- + +Code action `DR014` (Readability) **Simplify using IN** + +## Description + +Rewrite compound predicates (equality comparisons of the same expression that are combined using [`OR`](https://dax.guide/OR) or [`||`](https://dax.guide/op/or/)) with the [`IN`](https://dax.guide/IN) operator. + +### Example + +Change: +```dax +IF(Document[Type] = "Invoice" || Document[Type] = "Credit Note", 1, 0) +``` + +To: +```dax +IF(Document[Type] IN {"Invoice", "Credit Note"}, 1, 0) +``` + +## Why is Tabular Editor suggesting this? + +The `IN` operator is more concise and easier to read than multiple `||` operators or `OR` function calls. It also makes it easier to add or remove values from the list of values to compare against. \ No newline at end of file diff --git a/kb/RW001.md b/kb/RW001.md new file mode 100644 index 00000000..c08d64e9 --- /dev/null +++ b/kb/RW001.md @@ -0,0 +1,33 @@ +--- +uid: RW001 +category: Code actions +sub-category: Rewrites +title: Rewrite TOTALxTD using CALCULATE +author: Daniel Otykier +updated: 2025-02-10 +--- + +Code action `RW001` (Rewrites) **Rewrite TOTALxTD using CALCULATE** + +## Description + +Functions such as [`TOTALMTD`](https://dax.guide/TOTALMTD), [`TOTALQTD`](https://dax.guide/TOTALQTD) and [`TOTALYTD`](https://dax.guide/TOTALYTD) can be rewritten using the [`CALCULATE`](https://dax.guide/CALCULATE) function. + +### Example + +Change: +```dax + TOTALYTD([Total Sales], 'Date'[Date]) +``` + +To: +```dax +CALCULATE([Total Sales], DATESYTD('Date'[Date])) +``` + +## Why is Tabular Editor suggesting this? + +This rewrite is useful in case you want to add additional filters or modify the calculation context. + +> [!NOTE] +> This code action is in the **Rewrites** category, which means that it does not represent a general recommendation or best practice. Instead, the code action provides a quick way to rewrite the code in a different way, for example as part of a larger refactoring. After applying the code action and until further edits are made, you may see an **Improvement** or **Readability** code action that suggests to modify the code back to its original state. \ No newline at end of file diff --git a/kb/RW002.md b/kb/RW002.md new file mode 100644 index 00000000..f2e2d6b9 --- /dev/null +++ b/kb/RW002.md @@ -0,0 +1,57 @@ +--- +uid: RW002 +category: Code actions +sub-category: Rewrites +title: Rewrite using FILTER +author: Daniel Otykier +updated: 2025-02-10 +--- + +Code action `RW002` (Rewrites) **Rewrite using FILTER** + +## Description + +A scalar predicate in a filter argument to `CALCULATE` is equivalent to a 1-column table expression that uses `FILTER`. + +### Example 1 + +Change: +```dax +CALCULATE([Total Sales], Products[Color] = "Red") +``` + +To: +```dax +CALCULATE( + [Total Sales], + FILTER( + ALL(Products[Color]), + Products[Color] = "Red") + ) +) +``` + +### Example 2 + +Change: +```dax +CALCULATE([Total Sales], KEEPFILTERS(Products[Color] = "Red")) +``` + +To: +```dax +CALCULATE( + [Total Sales], + FILTER( + VALUES(Products[Color]), + Products[Color] = "Red") + ) +) +``` + +## Why is Tabular Editor suggesting this? + +This rewrite is useful in case you want to add more complex filtering logic. + +> [!NOTE] +> This code action is in the **Rewrites** category, which means that it does not represent a general recommendation or best practice. Instead, the code action provides a quick way to rewrite the code in a different way, for example as part of a larger refactoring. After applying the code action and until further edits are made, you may see an **Improvement** or **Readability** code action that suggests to modify the code back to its original state. \ No newline at end of file diff --git a/kb/RW003.md b/kb/RW003.md new file mode 100644 index 00000000..c6cdbb64 --- /dev/null +++ b/kb/RW003.md @@ -0,0 +1,30 @@ +--- +uid: RW003 +category: Code actions +sub-category: Rewrites +title: Invert IF +author: Daniel Otykier +updated: 2025-02-10 +--- + +Code action `RW003` (Rewrites) **Invert IF** + +## Description + +To improve readability, it is sometimes useful to invert `IF` statements. + +### Example + +Change: +```dax +IF(a < b, "B is greater", "A is greater") +``` + +To: +```dax +IF(a > b, "A is greater", "B is greater") +``` + +## Why is Tabular Editor suggesting this? + +This code action provides a quick way to invert the logic of an `IF` statement, which can make the code easier to read. The readability of the code is subjective and depends on the context, which is why this code action is in the **Rewrites** category rather than the **Readability** category. \ No newline at end of file diff --git a/te3/editions.md b/te3/editions.md index b71fce49..026c3b3f 100644 --- a/te3/editions.md +++ b/te3/editions.md @@ -2,12 +2,15 @@ uid: editions title: Compare editions author: Søren Toft Joensen -updated: 2023-11-22 +updated: 2025-02-07 --- # Tabular Editor 3 Editions This document provides an overview and comparison of the different editions of Tabular Editor 3. +> [!NOTE] +> Tabular Editor 3 licenses are **per developer**. In other words, only the persons who use the Tabular Editor 3 product will need a license. + ## Supported Data Modeling Scenarios The main difference between the various editions of Tabular Editor 3, is which types of tabular data modeling scenarios they support. To understand this difference, consider that Analysis Services (Tabular) exists in a number of different "flavors": @@ -81,9 +84,9 @@ There are no other feature differences between the Tabular Editor 3 editions, th ## Personal vs. Transferable licenses -Our Desktop Edition and Business Edition uses a personal licensing model. This means, that a user receives their own personal License Key, which can not be shared or transferred to other users. When a user no longer requires the product, their subscription should be cancelled to avoid recurring payments. +Our Desktop Edition and Business Edition uses a **personal** licensing model. This means, that a user receives their own personal License Key, which can not be shared or transferred to other users. When a user no longer requires the product, their subscription should be cancelled to avoid recurring payments. -Our Enterprise Edition uses a transferable licensing model. The license administrator receives a single License Key, which is then valid for a number of named users up to the quantity purchased. Users are identified by their e-mail address, which is entered the first time a user activates an installation of Tabular Editor 3. The license administrator may contact support in case a user should be removed from the license, such as when an employee leaves the team. +Our Enterprise Edition uses a **transferable** licensing model. The license administrator receives a single License Key, which is then valid for a number of named users up to the quantity purchased. Users are identified by their e-mail address, which is entered the first time a user activates an installation of Tabular Editor 3. The first time a user activates a Tabular Editor 3 installation using the license key, they are "locked-in" to that license for 30 days. After the 30 day lock-in period, a user can be removed from the license at any time, freeing up the license slot for another user. License administrators can view and manage users through our [self-service portal](https://tabulareditor.com/my-account). You may also contact support for assistance. ## Multiple installations diff --git a/te3/features/code-actions.md b/te3/features/code-actions.md index 66501782..730f7b66 100644 --- a/te3/features/code-actions.md +++ b/te3/features/code-actions.md @@ -12,29 +12,29 @@ applies_to: # Code Actions -Tabular Editor 3.18.0 introduces a new feature called **Code Actions**. This feature is enabled by default, but can be disabled in the **Tools > Preferences** dialog, under **Text Editors > DAX Editor > Code Actions**. +Tabular Editor 3.18.0 introduces a new feature called **Code Actions**. This feature is enabled by default but can be disabled in the **Tools > Preferences** dialog under **Text Editors > DAX Editor > Code Actions**. -Code Actions is a productivity feature that discretely provides suggestions for improving your DAX code, letting you apply the suggestions with a single click. Code Actions also provide easy access to common code refactoring operations. +Code Actions is a productivity feature that discretely provides suggestions for improving your DAX code. You can apply the suggestions with a single click. Code Actions also provides easy access to common code refactoring operations. Code Actions are separated into three different categories: -1. **Improvements**: These are recommended suggestions for improving your DAX code, in terms of: +1. **Improvements**: These are recommended suggestions for improving your DAX code in terms of: - Following best practices - Avoiding common pitfalls and anti-patterns - Avoiding obsolete or deprecated DAX features - Writing better, more performant DAX code -2. **Readability**: These are suggestions for making your DAX code more readable, by: +2. **Readability**: These are suggestions for making your DAX code more readable by... - Simplifying complex expressions, when possible - Remove redundant or unnecessary code - Applying consistent formatting and naming conventions -3. **Rewrites**: These are suggestions for refactoring your DAX code. They are not necessarily improvements, but are often useful as part of larger code refactorings. Examples are: - - Convert DAX "syntax sugar" to more verbose, but more explicit code +3. **Rewrites**: These are suggestions for refactoring your DAX code. They are not necessarily improvements but are often useful for larger code refactorings. Examples are: + - Convert DAX "syntax sugar" to more verbose but more explicit code - Rename all occurrences of a variable or an extension column - Format code ## How to use Code Actions -A new command and corresponding toolbar / menu buttons have been added, **Show Code Actions**, with a default keyboard shortcut of `Ctrl+.`. This command will show the applicable Code Actions at the current cursor position: +A new command and corresponding toolbar/menu buttons have been added, **Show Code Actions**, with a default keyboard shortcut of `Ctrl+.`. This command will show the applicable Code Actions at the current cursor position: ![Code Action Invoke Menu](~/images/code-action-invoke-menu.png) @@ -42,85 +42,88 @@ You can also find the applicable Code Actions through the **Refactor** submenu o ![Code Action Refactor Submenu](~/images/code-action-refactor-submenu.png) -Lastly, a lightbulb or screwdriver icon is shown in the left margin of the editor, when the cursor is placed on a code segment that has applicable actions. Clicking on the icon will also bring up the Code Actions menu:: +Lastly, a lightbulb or screwdriver icon is shown in the editor's left margin when the cursor is placed on a code segment with applicable actions. Clicking on the icon will also bring up the Code Actions menu: ![Code Actions Margin](~/images/code-action-margin.png) ## Code Action indicators -**Improvements** and **Readability** Code Actions will also be indicated visually in the code editor. This way, you can quickly determine which parts of your code can be improved or made more readable. +**Improvements** and **Readability** Code Actions will also be indicated visually in the code editor. This lets you quickly determine which parts of your code can be improved or made more readable. -- **Improvements** are shown with orange dots under the first few characters of the code segment (unless that code segment already displays an orange warning squiggly). When the cursor is moved over the code segment, a *lightbulb* icon will appear in the left margin. +- **Improvements** are shown with orange dots under the first few characters of the code segment (unless that code segment already displays an orange warning squiggly). A *lightbulb* icon will appear in the left margin when the cursor is moved over the code segment. - **Readability** actions are shown with teal green dots under the first few characters of the code segment. When the cursor is moved over the code segment, a *screwdriver* icon will appear in the left margin. -- **Rewrites** are not visually indicated in the code itself, however, the *screwdriver* icon will appear in the left margin when the cursor is placed on a code segment that has applicable rewrites. +- **Rewrites** are not visually indicated in the code itself; however, the *screwdriver* icon will appear in the left margin when the cursor is placed on a code segment with applicable rewrites. ## Apply to all occurrences -Some Code Actions can be applied to all occurrences within the current DAX expression, DAX script or DAX query, rather than just the code segment under the cursor. When this is the case, the Code Action will be shown in the Code Actions menu with " (All occurrences)" appended to the action description. Clicking on the action will apply the change to all occurrences in the document. +Some Code Actions can be applied to all occurrences within the current DAX expression, DAX script, or DAX query rather than just the code segment under the cursor. When this is the case, the Code Action will be shown in the Code Actions menu with " (All occurrences)" appended to the action description. Clicking on the action will apply the change to all occurrences in the document. -In the screenshot below, for example, the **Prefix variable with '_'** action can be applied to all occurrences (i.e. all variables) in the document, not just the `totalSales` variable under the cursor: +In the screenshot below, for example, the **Prefix variable with '_'** action can be applied to all occurrences (i.e., all variables) in the document, not just the `totalSales` variable under the cursor: ![Code Action All Occurrences](~/images/code-action-all-occurrences.png) ## List of Code Actions -The table below lists all currently available Code Actions. You can toggle off Code Actions in the **Tools > Preferences** dialog, under **Text Editors > DAX Editor > Code Actions** (a future update will let you toggle individual actions for a more customized experience). Some Code Actions also has additional configuration options, such as which prefix to use for variable names. +The table below lists all currently available Code Actions. You can toggle off Code Actions in the **Tools > Preferences** dialog under **Text Editors > DAX Editor > Code Actions** (a future update will let you toggle individual actions for a more customized experience). Some Code Actions also have additional configuration options, such as which prefix to use for variable names. ### Improvements -The Code Actions below will appear with orange dots under the first two characters of the applicable code, and a lightbulb icon in the left margin when the cursor is placed on the code segment: - -| Name | Description | -| --- | --- | -| Remove unused variable | Variables that are not being referenced anywhere, should be removed. Example:
`VAR a = 1 VAR b = 2 RETURN a` -> `VAR a = 1 RETURN a` | -| Remove all unused variables | Variables that are not being used (directly or indirectly through other variables) in the `RETURN` part of a variable block, should be removed. Example:
`VAR a = 1 VAR b = a RETURN 123` -> `123` | -| Remove table name | Measure references should not include the table name, as the table name is unnecessary when referencing measures. Moreover, this practice makes measure references more easily distinguishable from column references. Example:
`Sales[Total Sales]` -> `[Total Sales]` -| Add table name | Column references should include the table name to avoid ambiguities, and to more easily distinguish column references from measure references. Example:
`SUM([SalesAmount])` -> `SUM(Sales[SalesAmount])` | -| Rewrite table filter as scalar predicate | A common anti-pattern in DAX is to filter a table inside a [`CALCULATE`](https://dax.guide/CALCULATE) filter argument, when it is sufficient to filter one or more columns from that table. Example:
`CALCULATE([Total Sales], FILTER(Products, Products[Color] = "Red"))` -> `CALCULATE([Total Sales], KEEPFILTERS(Products[Color] = "Red"))`
This Code Action supports various variations of the original expression. | -| Split multi-column filter into multiple filters | When filtering a table on multiple columns combined using `AND` (or the equivalent `&&` operator), better performance can often be achieved by specifying multiple filters, one for each column. Example:
`CALCULATE(..., Products[Color] = "Red" && Products[Size] = "Large")` -> `CALCULATE(..., Products[Color] = "Red", Products[Size] = "Large")` | -| Simplify SWITCH statement | A [`SWITCH`](https://dax.guide/SWITCH) statement that specifies `TRUE()` for the **<Expression>** argument, and where all **<Value>** arguments are simple comparisons of the same variable/measure, can be simplified. Example:
`SWITCH(TRUE(), a = 1, ..., a = 2, ...)` -> `SWITCH(a, 1, ..., 2, ...)` | -| Remove superfluous CALCULATE | A [`CALCULATE`](https://dax.guide/CALCULATE) function that is not necessary, because it does not modify the filter context, or because an implicit context transition would happen anyway, should be removed. Examples:
`CALCULATE([Total Sales])` -> `[Total Sales]`
`AVERAGEX(Product, CALCULATE([Total Sales]))` -> `AVERAGEX(Product, [Total Sales])`

Also applies when the first argument of `CALCULATE` / `CALCULATETABLE` is a DAX variable, e.g.:
`VAR x = [Total Sales] RETURN CALCULATE(x, Product[Color] = "Red")` ->
`VAR x = [Total Sales] RETURN x` | -| Avoid calculate shortcut syntax | Example:
`[Total Sales](Products[Color] = "Red")` -> `CALCULATE([Total Sales], Products[Color] = "Red")` | -| Use MIN/MAX instead of IF | When a conditional expression is used to return the minimum or maximum of two values, it is more efficient and compact to use the [`MIN`](https://dax.guide/MIN) or [`MAX`](https://dax.guide/MAX) function. Example:
`IF(a > b, a, b)` -> `MAX(a, b)` | -| Use ISEMPTY instead of COUNTROWS | When checking if a table is empty, it is more efficient to use the [`ISEMPTY`](https://dax.guide/ISEMPTY) function than to count the rows of the table. Examples:
`COUNTROWS(Products) = 0` -> `ISEMPTY(Products)` | -| Use DIVIDE instead of division | When using an arbitrary expression in the denominator of a division, use [`DIVIDE`](https://dax.guide/DIVIDE) instead of the division operator, to avoid division by zero errors. Example:
`x / y` -> `DIVIDE(x, y)` | -| Use division instead of DIVIDE | When the 2nd argument of [`DIVIDE`](https://dax.guide/DIVIDE) is a non-zero constant, it is more efficient to use the division operator. Example:
`DIVIDE(x, 2)` -> `x / 2` | +The Code Actions below will appear with orange dots under the first two characters of the applicable code and a lightbulb icon in the left margin when the cursor is placed on the code segment: + +| ID | Name | Description | +| -- | --- | --- | +| DI001 | [Remove unused variable](xref:DI001) | Variables not referenced anywhere should be removed. Example:
`VAR a = 1 VAR b = 2 RETURN a` -> `VAR a = 1 RETURN a` | +| DI002 | [Remove all unused variables](xref:DI002) | Variables that are not being used (directly or indirectly through other variables) in the `RETURN` part of a variable block should be removed. Example:
`VAR a = 1 VAR b = a RETURN 123` -> `123` | +| DI003 | [Remove table name](xref:DI003) | Measure references should not include the table name, as the table name is unnecessary when referencing measures. Moreover, this practice makes measure references more easily distinguishable from column references. Example:
`Sales[Total Sales]` -> `[Total Sales]` +| DI004 | [Add table name](xref:DI004) | Column references should include the table name to avoid ambiguities and to more easily distinguish column references from measure references. Example:
`SUM([SalesAmount])` -> `SUM(Sales[SalesAmount])` | +| DI005 | [Rewrite table filter as scalar predicate](xref:DI005) | A common anti-pattern in DAX is to filter a table inside a [`CALCULATE`](https://dax.guide/CALCULATE) filter argument when it is sufficient to filter one or more columns from that table. Example:
`CALCULATE([Total Sales], FILTER(Products, Products[Color] = "Red"))` -> `CALCULATE([Total Sales], KEEPFILTERS(Products[Color] = "Red"))`
This Code Action supports various variations of the original expression. | +| DI006 | [Split multi-column filter into multiple filters](xref:DI006) | When filtering a table on multiple columns combined using `AND` (or the equivalent `&&` operator), better performance can often be achieved by specifying multiple filters, one for each column. Example:
`CALCULATE(..., Products[Color] = "Red" && Products[Size] = "Large")` -> `CALCULATE(..., Products[Color] = "Red", Products[Size] = "Large")` | +| DI007 | [Simplify SWITCH statement](xref:DI007) | A [`SWITCH`](https://dax.guide/SWITCH) statement that specifies `TRUE()` for the **<Expression>** argument, and where all **<Value>** arguments are simple comparisons of the same variable/measure, can be simplified. Example:
`SWITCH(TRUE(), a = 1, ..., a = 2, ...)` -> `SWITCH(a, 1, ..., 2, ...)` | +| DI008 | [Remove superfluous CALCULATE](xref:DI008) | A [`CALCULATE`](https://dax.guide/CALCULATE) function that is not necessary because it does not modify the filter context, or because an implicit context transition would happen anyway, should be removed. Examples:
`CALCULATE([Total Sales])` -> `[Total Sales]`
`AVERAGEX(Product, CALCULATE([Total Sales]))` -> `AVERAGEX(Product, [Total Sales])`

Also applies when the first argument of `CALCULATE` / `CALCULATETABLE` is a DAX variable, e.g.:
`VAR x = [Total Sales] RETURN CALCULATE(x, Product[Color] = "Red")` ->
`VAR x = [Total Sales] RETURN x` | +| DI009 | [Avoid calculate shortcut syntax](xref:DI009) | Example:
`[Total Sales](Products[Color] = "Red")` -> `CALCULATE([Total Sales], Products[Color] = "Red")` | +| DI010 | [Use MIN/MAX instead of IF](xref:DI010) | When a conditional expression is used to return the minimum or maximum of two values, it is more efficient and compact to use the [`MIN`](https://dax.guide/MIN) or [`MAX`](https://dax.guide/MAX) function. Example:
`IF(a > b, a, b)` -> `MAX(a, b)` | +| DI011 | [Use ISEMPTY instead of COUNTROWS](xref:DI011) | When checking if a table is empty, it is more efficient to use the [`ISEMPTY`](https://dax.guide/ISEMPTY) function than to count the rows of the table. Examples:
`COUNTROWS(Products) = 0` -> `ISEMPTY(Products)` | +| DI012 | [Use DIVIDE instead of division](xref:DI012) | When using an arbitrary expression in the denominator of a division, use [`DIVIDE`](https://dax.guide/DIVIDE) instead of the division operator to avoid division by zero errors. Example:
`x / y` -> `DIVIDE(x, y)` | +| DI013 | [Use division instead of DIVIDE](xref:DI013) | When the 2nd argument of [`DIVIDE`](https://dax.guide/DIVIDE) is a non-zero constant, it is more efficient to use the division operator. Example:
`DIVIDE(x, 2)` -> `x / 2` | +| DI014 | [Replace IFERROR with DIVIDE](xref:DI014) | Use the [`DIVIDE`](https://dax.guide/DIVIDE) function instead of [`IFERROR`](https://dax.guide/IFERROR) to provide an alternate result when a division has a zero denominator. Example:
`IFERROR(x / y, 0)` -> `DIVIDE(x, y, 0)` | +| DI015 | [Replace IF with DIVIDE](xref:DI015) | Use the [`DIVIDE`](https://dax.guide/DIVIDE) function instead of [`IF`](https://dax.guide/IF) to more easily check for zero or blank in the denominator. Example:
`IF(y <> 0, x / y)` -> `DIVIDE(x, y)` | ### Readability -The Code Actions below will appear with teal green dots under the first two characters of the applicable code, and a screwdriver icon in the left margin when the cursor is placed on the code segment - -| Name | Description | -| --- | --- | -| Convert to scalar predicate | A column filter can be written more concisely as a scalar predicate, without explicitly using the [`FILTER`](https://dax.guide/FILTER) function. Examples:
`FILTER(ALL(Products[Color]), Products[Color] = "Red")` -> `Products[Color] = "Red"`
`FILTER(VALUES(Products[Color]), Products[Color] = "Red")` -> `KEEPFILTERS(Products[Color] = "Red")` | -| Use aggregator instead of iterator | Use an aggregator function instead of an iterator function when possible, to simplify the code. Example:
`SUMX(Products, Products[SalesAmount])` -> `SUM(Products[SalesAmount])` | -| Use VALUES instead of SUMMARIZE | When [`SUMMARIZE`](https://dax.guide/SUMMARIZE) only specifies a single column, and that column belongs to the table specified in the first argument, the code can be more concisely written using [`VALUES`](https://dax.guide/VALUES). Example:
`SUMMARIZE(Products, Products[Color])` -> `VALUES(Products[Color])` | -| Prefix variable | Variables should use a consistent naming convention. It is recommended to use a prefix, such as an underscore. You can configure which prefix to use, to match your preferred style. Example:
`VAR totalSales = SUM(Sales[SalesAmount])` -> `VAR _totalSales = SUM(Sales[SalesAmount])` | -| Prefix temporary column | It is recommended to use a consistent prefix for temporary columns, to more easily distinguish them from base columns or measures. You can configure which prefix to use, to match your preferred style. Example:
`ADDCOLUMNS(Product, "SalesByProd", [Sales])` -> `ADDCOLUMNS(Product, "@SalesByProd", [Sales])` | -| Move constant aggregation to variable | When an aggregation function is used inside an iterator or a scalar predicate, the aggregation produces the same result for every row of the iteration, and therefore the aggregation could be moved to a DAX variable outside of the iteration. Example:
`CALCULATE(..., 'Date'[Date] = MAX('Date'[Date]))` ->
`VAR _maxDate = MAX('Date'[Date]) RETURN CALCULATE(..., 'Date'[Date] = _maxDate)` | -| Simplify 1-variable block | A variable block with only one variable can be simplified by moving the expression directly into the `RETURN` part of the block. This assumes the variable is only referenced once without any context modifiers. Example:
`VAR _result = [Sales] * 1.25 RETURN _result` -> `[Sales] * 1.25` | -| Simplify multi-variable block | A variable block with multiple variables where each is a simple measure reference, which are only used once in the `RETURN` section without any context modifiers, should be simplified. Example:
`VAR _sales = [Sales] VAR _cost = [Cost] RETURN _sales - _cost` -> `[Sales] - [Cost]` | -| Rewrite using DISTINCTCOUNT | Instead of using `COUNTROWS(DISTINCT(T[c])` to count the number of distinct values in a column, use the [`DISTINCTCOUNT`](https://dax.guide/DISTINCTCOUNT) function. | -| Rewrite using COALESCE | Instead of using `IF` to return the first non-blank value from a list of expressions, use the [`COALESCE`](https://dax.guide/COALESCE) function. Example:
`IF(ISBLANK([Sales]), [Sales2], [Sales])` -> `COALESCE([Sales], [Sales2])` | -| Rewrite using ISBLANK | Instead of comparing an expression with `BLANK()`, use the [`ISBLANK`](https://dax.guide/ISBLANK) function. Example:
`IF([Sales] = BLANK(), [Budget], [Sales])` -> `IF(ISBLANK([Sales], [Budget], [Sales])` | -| Remove unnecessary BLANK | Some DAX functions, such as [`IF`](https://dax.guide/IF) and [`SWITCH`](https://dax.guide/SWITCH) already return `BLANK()` when the condition is false, so there is no need to explicitly specify `BLANK()`. Example:
`IF(a > b, a, BLANK())` -> `IF(a > b, a)` | -| Simplify negated logic | When a logical expression is negated, it is often more readable to rewrite the expression using the negated operator. Example:
`NOT(a = b)` -> `a <> b` | +The Code Actions below will appear with teal green dots under the first two characters of the applicable code and a screwdriver icon in the left margin when the cursor is placed on the code segment + +| ID | Name | Description | +| --- | --- | --- | +| DR001 | [Convert to scalar predicate](xref:DR001) | A column filter can be written more concisely as a scalar predicate without explicitly using the [`FILTER`](https://dax.guide/FILTER) function. Examples:
`FILTER(ALL(Products[Color]), Products[Color] = "Red")` -> `Products[Color] = "Red"`
`FILTER(VALUES(Products[Color]), Products[Color] = "Red")` -> `KEEPFILTERS(Products[Color] = "Red")` | +| DR002 | [Use aggregator instead of iterator](xref:DR002) | Use an aggregator function instead of an iterator function when possible to simplify the code. Example:
`SUMX(Products, Products[SalesAmount])` -> `SUM(Products[SalesAmount])` | +| DR003 | [Use VALUES instead of SUMMARIZE](xref:DR003) | When [`SUMMARIZE`](https://dax.guide/SUMMARIZE) only specifies a single column, and that column belongs to the table specified in the first argument, the code can be more concisely written using [`VALUES`](https://dax.guide/VALUES). Example:
`SUMMARIZE(Products, Products[Color])` -> `VALUES(Products[Color])` | +| DR004 | [Prefix variable](xref:DR004) | Variables should use a consistent naming convention. It is recommended to use a prefix, such as an underscore. You can configure which prefix to use to match your preferred style. Example:
`VAR totalSales = SUM(Sales[SalesAmount])` -> `VAR _totalSales = SUM(Sales[SalesAmount])` | +| DR005 | [Prefix temporary columns](xref:DR005) | Using a consistent prefix for temporary columns is recommended to more easily distinguish them from base columns or measures. You can configure which prefix to use to match your preferred style. Example:
`ADDCOLUMNS(Product, "SalesByProd", [Sales])` -> `ADDCOLUMNS(Product, "@SalesByProd", [Sales])` | +| DR006 | [Move constant aggregation to variable](xref:DR006) | When an aggregation function is used inside an iterator or a scalar predicate, the aggregation produces the same result for every row of the iteration. Therefore, the aggregation could be moved to a DAX variable outside of the iteration. Example:
`CALCULATE(..., 'Date'[Date] = MAX('Date'[Date]))` ->
`VAR _maxDate = MAX('Date'[Date]) RETURN CALCULATE(..., 'Date'[Date] = _maxDate)` | +| DR007 | [Simplify 1-variable block](xref:DR007) | A variable block with only one variable can be simplified by moving the expression directly into the `RETURN` part of the block. This assumes the variable is only referenced once without any context modifiers. Example:
`VAR _result = [Sales] * 1.25 RETURN _result` -> `[Sales] * 1.25` | +| DR008 | [Simplify multi-variable block](xref:DR008) | A variable block with multiple variables where each is a simple measure reference, which is only used once in the `RETURN` section without any context modifiers, should be simplified. Example:
`VAR _sales = [Sales] VAR _cost = [Cost] RETURN _sales - _cost` -> `[Sales] - [Cost]` | +| DR009 | [Rewrite using DISTINCTCOUNT](xref:DR009) | Instead of using `COUNTROWS(DISTINCT(T[c])` to count the number of distinct values in a column, use the [`DISTINCTCOUNT`](https://dax.guide/DISTINCTCOUNT) function. | +| DR010 | [Rewrite using COALESCE](xref:DR010) | Instead of using `IF` to return the first non-blank value from a list of expressions, use the [`COALESCE`](https://dax.guide/COALESCE) function. Example:
`IF(ISBLANK([Sales]), [Sales2], [Sales])` -> `COALESCE([Sales], [Sales2])` | +| DR011 | [Rewrite using ISBLANK](xref:DR011) | Instead of comparing an expression with [`BLANK()`](https://dax.guide/BLANK), use the [`ISBLANK`](https://dax.guide/ISBLANK) function. Example:
`IF([Sales] = BLANK(), [Budget], [Sales])` -> `IF(ISBLANK([Sales], [Budget], [Sales])` | +| DR012 | [Remove unnecessary BLANK](xref:DR012) | Some DAX functions, such as [`IF`](https://dax.guide/IF) and [`SWITCH`](https://dax.guide/SWITCH) already return `BLANK()` when the condition is false, so there is no need to explicitly specify `BLANK()`. Example:
`IF(a > b, a, BLANK())` -> `IF(a > b, a)` | +| DR013 | [Simplify negated logic](xref:DR013) | When a logical expression is negated, it is often more readable to rewrite the expression using the negated operator. Example:
`NOT(a = b)` -> `a <> b` | +| DR014 | [Simplify using IN](xref:DR014) | Rewrite compound predicates (equality comparisons of the same expression that are combined using [`OR`](https://dax.guide/OR) or [`||`](https://dax.guide/op/or/)) with the [`IN`](https://dax.guide/IN) operator. Example:
`a = 1 || a = 2 || a = 100` -> `a IN { 1, 2, 100 }` | ### Rewrites The Code Actions below will appear with a screwdriver icon in the left margin when the cursor is placed on the code segment. -| Name | Description | -| --- | --- | -| Rewrite TOTALxTD using CALCULATE | Functions such as [`TOTALMTD`](https://dax.guide/TOTALMTD), [`TOTALQTD`](https://dax.guide/TOTALQTD) and [`TOTALYTD`](https://dax.guide/TOTALYTD) can be rewritten using the [`CALCULATE`](https://dax.guide/CALCULATE) function, which is more expressive and provides greater flexibility. Example:
`TOTALYTD([Total Sales], 'Date'[Date])` -> `CALCULATE([Total Sales], DATESYTD('Date'[Date]))` | -| Rewrite using FILTER | A scalar predicate in a filter argument to `CALCULATE` can be rewritten using `FILTER`. This is useful, for example when you need to add more complex filtering logic. Example:
`CALCULATE(..., Products[Color] = "Red")` -> `CALCULATE(..., FILTER(ALL(Products[Color]), Products[Color] = "Red"))` | -| Invert IF | To improve readability, it is sometimes useful to invert `IF` statements. Example:
`IF(a < b, "B is greater", "A is greater")` -> `IF(a > b, "A is greater", "B is greater")` | +| ID | Name | Description | +| --- | --- | --- | +| RW001 | [Rewrite TOTALxTD using CALCULATE](xref:RW001) | Functions such as [`TOTALMTD`](https://dax.guide/TOTALMTD), [`TOTALQTD`](https://dax.guide/TOTALQTD) and [`TOTALYTD`](https://dax.guide/TOTALYTD) can be rewritten using the [`CALCULATE`](https://dax.guide/CALCULATE) function, which is more expressive and provides greater flexibility. Example:
`TOTALYTD([Total Sales], 'Date'[Date])` -> `CALCULATE([Total Sales], DATESYTD('Date'[Date]))` | +| RW002 | [Rewrite using FILTER](xref:RW002) | A scalar predicate in a filter argument to `CALCULATE` can be rewritten using `FILTER`. This is useful, for example, when you need to add more complex filtering logic. Example:
`CALCULATE(..., Products[Color] = "Red")` -> `CALCULATE(..., FILTER(ALL(Products[Color]), Products[Color] = "Red"))` | +| RW003 | [Invert IF](xref:RW003) | To improve readability, it is sometimes useful to invert `IF` statements. Example:
`IF(a < b, "B is greater", "A is greater")` -> `IF(a > b, "A is greater", "B is greater")` | ## Customizing Code Actions -You can customize the behavior of Code Actions through the **Tools > Preferences** dialog, under **Text Editors > DAX Editor > Code Actions**. Here you can toggle the feature on and off, and configure additional options for some Code Actions, such as the prefix to use for variable names and extension columns. +You can customize the behavior of Code Actions through the **Tools > Preferences** dialog under **Text Editors > DAX Editor > Code Actions**. Here, you can toggle the feature on and off and configure additional options for some Code Actions, such as the prefix to use for variable names and extension columns. -We are planning to add more configuration options to this screen in future versions, such as an option for toggling individual Code Actions on and off. Stay tuned! +We plan to add more configuration options to this screen in future versions, such as an option to toggle individual Code Actions on and off. Stay tuned! ![Code Actions Preferences](~/images/code-actions-preferences.png) diff --git a/te3/features/csharp-scripts.md b/te3/features/csharp-scripts.md index b53ef4e5..f382de79 100644 --- a/te3/features/csharp-scripts.md +++ b/te3/features/csharp-scripts.md @@ -254,3 +254,12 @@ If you just want the major version number (as an integer), use: var majorVersion = Selected.GetType().Assembly.GetName().Version.Major; majorVersion.Output(); // majorVersion is an integer (2 or 3) ``` + +## Known issues and limitations + +- Certain script operations may cause the Tabular Editor 3 application to crash or become unresponsive, due to the way scripts are executed. For example, a script with an infinite loop (`while(true) {}`) will cause the application to hang. If this happens, you will have to end the Tabular Editor process through the Windows Task Manager. + +If you intend to save the script as a [macro](xref:creating-macros), please be aware of the following limitations: + +- If the script body contains local methods with access modifiers (`public`, `static`, etc.), the script cannot be saved as a macro. Remove the access modifiers, or move the method into a class instead. +- Macros currently do not support the `await` keyword, if used in the script body. If your script body calls into asynchronous methods, you should use `MyAsyncMethod.Wait()` or `MyAsyncMethod.Result` instead of `await MyAsyncMethod()`. It is fine to use `await` in `async` methods that are defined elsewhere in the script. \ No newline at end of file diff --git a/te3/features/dax-query.md b/te3/features/dax-query.md index 6817427f..3f3e1f55 100644 --- a/te3/features/dax-query.md +++ b/te3/features/dax-query.md @@ -23,17 +23,22 @@ The built-in context-aware DAX Editor ensures that only the two valid DAX keywor ## DAX Query Options -The DAX query window has four different query options. +The DAX query window has five different query options. ![Dax Query Toolbar](~/images/features/dax_query_window/dax_query_toolbar.png) -1. Execute (F5) -2. Execute Selection (Shift+F5) -3. Stop -4. Auto Execute Query +1. **Execute (F5)**: If there is a selection, it executes the selected DAX; otherwise, it executes the full query in the DAX Query editor. +2. **Execute full query**: It executes the full query in the DAX Query editor +3. **Execute Selection (Shift+F5)**: If there is a selection, it executes it. Otherwise, it executes the EVALUATE statement where the cursor is currently located. +4. **Stop**: This button cancels the current query execution. +5. **Auto Execute Query**: It allows for keeping track of the connected semantic model and updating the query results whenever something changes in the model. This can be useful for understanding e.g. how the result of a measure changes if modified. +6. **Keep sorting and filtering**: It allows users to control how sorting and filtering are preserved in the result grid(s) when executing queries. There are three preferences available: + - **Never**: Sorting and filtering reset each time the query runs. + - **When query is modified**: Sorting and filtering reset only when the query structure changes. + - **Always**: Sorting and filtering persist as long as columns remain in the new query. -The Auto Execute Query allows for keeping track of the connected semantic model and update the query results whenever something changes in the model. This can be useful for understanding e.g. how the result of a measure changes if modified. +The default values of "Auto Execute Query" and "Keep Sorting and Filtering" preferences can be set up in the Preferences dialog: **Tools > Preferences... > Data browsing > DAX Query** > Basic. ### Adding or Updating Measures with DAX Queries