Rules / DAX Expressions

Filter column values with proper syntax

warning FILTER_COLUMN_VALUES · ported · model layer · scope: Measure, CalculatedColumn, CalculationItem

What it checks

CALCULATE or CALCULATETABLE whose first filter argument is FILTER('Table', 'Table'[Column] ...).

Each finding names the object that holds the expression: a measure as [Bike Sales], a calculated column as 'Sales'[Bike Amount], a calculation item by its name with its calculation group in the detail.

Example

Fires the rule
table Sales
	column Amount
		dataType: decimal
		sourceColumn: Amount
	column 'Product ID'
		dataType: int64
		sourceColumn: ProductID
	measure 'Total Sales' = SUM(Sales[Amount])
		formatString: #,0
	measure 'Bike Sales' = CALCULATE([Total Sales], FILTER('Product', 'Product'[Category] = "Bikes"))
		formatString: #,0

table Product
	column 'Product ID'
		dataType: int64
		isKey
		sourceColumn: ProductID
	column Category
		dataType: string
		sourceColumn: Category

relationship Sales_Product
	fromColumn: Sales.'Product ID'
	toColumn: Product.'Product ID'
After the fix
table Sales
	column Amount
		dataType: decimal
		sourceColumn: Amount
	column 'Product ID'
		dataType: int64
		sourceColumn: ProductID
	measure 'Total Sales' = SUM(Sales[Amount])
		formatString: #,0
	measure 'Bike Sales' = CALCULATE([Total Sales], KEEPFILTERS('Product'[Category] = "Bikes"))
		formatString: #,0

table Product
	column 'Product ID'
		dataType: int64
		isKey
		sourceColumn: ProductID
	column Category
		dataType: string
		sourceColumn: Category

relationship Sales_Product
	fromColumn: Sales.'Product ID'
	toColumn: Product.'Product ID'

Why it matters

FILTER over a whole table walks every row, keeps the ones that pass, and hands that row set to CALCULATE. A plain column predicate, 'Table'[Column] = "Value", is applied to the column's distinct values before any row is touched, which is far cheaper on a fact table and lets the storage engine do the work. The two forms also differ in meaning: FILTER over the table respects a filter already on the column, which the plain predicate replaces, so KEEPFILTERS is the exact equivalent.

How to fix it

Drop the FILTER and pass the predicate itself. Wrap it in KEEPFILTERS to keep whatever filter the visual already puts on the column, which is what the FILTER form was doing:

Bike Sales = CALCULATE ( [Total Sales], KEEPFILTERS ( 'Product'[Category] = "Bikes" ) )

Leave KEEPFILTERS off to replace that filter instead, which is what a bare predicate inside CALCULATE means. The SQLBI article under Links covers which one you want. In Power BI Desktop, select the measure in the Data pane and edit it in the formula bar; a calculated column and a calculation item are edited the same way. In the TMDL file, edit the expression after measure 'Bike Sales' =, after column Name = for a calculated column, or after calculationItem Name = in the calculation group.

When to ignore it

A predicate that reads two columns of the same table row by row has no column equivalent: FILTER('Sales', 'Sales'[Amount] > 'Sales'[Budget]) needs the row context that FILTER supplies, and there is nothing to rewrite. The finding is also worth less on a small dimension table, where the table has a few hundred rows and the column a few dozen distinct values, than on a fact table with millions. What to check before deciding is which table the FILTER is over, not which table the measure lives on.

To ignore this rule on one object, add annotation pbiplint.ignore = FILTER_COLUMN_VALUES under the object in its TMDL file. Power BI Desktop keeps the annotation. To turn the rule off for a whole project, set "FILTER_COLUMN_VALUES": "off" under rules in pbiplint.config.json.

Quirks

Ported from Microsoft's Best Practice Analyzer ruleset (opens in a new tab).

Check a model for this Improve this page (opens in a new tab)