|
| 1 | +--- |
| 2 | +layout: page |
| 3 | +title: Parameter types |
| 4 | +published: true |
| 5 | +order: /02 |
| 6 | +--- |
| 7 | + |
| 8 | +Each parameter has two independent properties: a **data type** (what expressions it accepts) and a **passing mode** (how the argument is transferred from call site to function body). |
| 9 | + |
| 10 | +## Passing modes |
| 11 | + |
| 12 | +### VAL |
| 13 | + |
| 14 | +The argument is evaluated once, in the evaluation context of the caller, before the function body runs. The parameter behaves like a `VAR` bound to that value. Changes in filter or row context inside the function body have no effect on the parameter's value. |
| 15 | + |
| 16 | +Semantic equivalent for a call `F( SUM ( Sales[Quantity] ) )` with parameter `qty : VAL`: |
| 17 | + |
| 18 | +```dax |
| 19 | +VAR qty = SUM ( Sales[Quantity] ) |
| 20 | +RETURN <body> |
| 21 | +``` |
| 22 | + |
| 23 | +### EXPR |
| 24 | + |
| 25 | +The argument expression is captured unevaluated and substituted inline at every reference to the parameter inside the function body. The expression is re-evaluated each time it is referenced, in the evaluation context present at that point in the body. |
| 26 | + |
| 27 | +Semantic equivalent for a call `F( SUM ( Sales[Quantity] ) )` with parameter `qty : EXPR`: |
| 28 | + |
| 29 | +```dax |
| 30 | +-- every reference to qty in the body becomes SUM ( Sales[Quantity] ) |
| 31 | +``` |
| 32 | + |
| 33 | +**Context transition:** EXPR parameters do **not** receive automatic context transition in row contexts, unlike a measure reference. To ensure correct behavior inside iterators, wrap the parameter reference in CALCULATE: |
| 34 | + |
| 35 | +```dax |
| 36 | +CALCULATE ( paramExpr ) |
| 37 | +``` |
| 38 | + |
| 39 | +`MEASUREREF` (see below) is the only reference type that guarantees context transition automatically. |
| 40 | + |
| 41 | +## Data types |
| 42 | + |
| 43 | +| Type | Accepts | Passing mode | Notes | |
| 44 | +|---|---|---|---| |
| 45 | +| `ANYVAL` | Any scalar or table | VAL (default) | Default when no type hint is specified | |
| 46 | +| `SCALAR` | Scalar expressions only | VAL or EXPR | Accepts a subtype to restrict the data type | |
| 47 | +| `TABLE` | Table expressions only | VAL or EXPR | | |
| 48 | +| `ANYREF` | Any expression | EXPR (forced) | No semantic guarantee on the expression kind | |
| 49 | +| `MEASUREREF` | Measure references only | EXPR (forced) | Guarantees context transition in row contexts | |
| 50 | +| `COLUMNREF` | Column references only | EXPR (forced) | Enables model-independent column access | |
| 51 | +| `TABLEREF` | Model table references only | EXPR (forced) | Provides full column and relationship access | |
| 52 | +| `CALENDARREF` | Calendar references only | EXPR (forced) | Intended for time intelligence functions | |
| 53 | + |
| 54 | +`ANYREF`, `MEASUREREF`, `COLUMNREF`, `TABLEREF`, and `CALENDARREF` force EXPR passing mode and cannot be declared as VAL. |
| 55 | + |
| 56 | +### Scalar subtypes |
| 57 | + |
| 58 | +`SCALAR` can be qualified with a subtype that restricts the accepted data type and enables automatic coercion: |
| 59 | + |
| 60 | +| Subtype | Accepts | |
| 61 | +|---|---| |
| 62 | +| `VARIANT` (default) | Any scalar data type | |
| 63 | +| `INT64` | Integer | |
| 64 | +| `DECIMAL` | Fixed-decimal number | |
| 65 | +| `DOUBLE` | Floating-point number | |
| 66 | +| `NUMERIC` | Any numeric type (INT64, DECIMAL, DOUBLE) | |
| 67 | +| `STRING` | Text | |
| 68 | +| `DATETIME` | Date or timestamp | |
| 69 | +| `BOOLEAN` | True/False | |
| 70 | + |
| 71 | +Coercion applies independently to each parameter; it does not propagate across parameters. |
| 72 | + |
| 73 | +## Type declaration syntax |
| 74 | + |
| 75 | +``` |
| 76 | +<ParameterName> : <Type> [<Subtype>] [<PassingMode>] |
| 77 | +``` |
| 78 | + |
| 79 | +When only a passing mode is written without a type, the type defaults to `ANYVAL`: |
| 80 | + |
| 81 | +```dax |
| 82 | +FUNCTION F = ( a : VAL, b : EXPR ) => ... |
| 83 | +``` |
| 84 | + |
| 85 | +## Type checking |
| 86 | + |
| 87 | +Type checking and coercion apply differently depending on the parameter category: |
| 88 | + |
| 89 | +**Scalar subtypes** (`INT64`, `DECIMAL`, `DOUBLE`, etc.) do not reject incompatible arguments — they coerce them. Each argument is independently converted to the declared type before the function body runs. No error is raised; see the coercion note under *Scalar subtypes* above. |
| 90 | + |
| 91 | +**Reference types** (`MEASUREREF`, `COLUMNREF`, `TABLEREF`, `CALENDARREF`) perform genuine type checking at call time. Passing an incompatible expression produces an error that identifies the expected and received types, for example: *"An invalid argument type was passed into parameter 'amountMeasure' of the user-defined function. Expected 'MEASUREREF' but got 'SCALAR'."* There is a known limitation for `COLUMNREF`: when a column reference is invalid, the internal syntax error from the function body surfaces before any custom validation error the function author may have written. |
| 92 | + |
| 93 | +**`ANYREF`** performs no type checking. An incompatible argument may produce confusing errors deep inside the function body or, in the worst case, incorrect results with no error at all. Functions using `ANYREF` must handle the general case defensively — for example, wrapping every reference to the parameter in `CALCULATE` to guarantee context transition regardless of what was passed. |
| 94 | + |
| 95 | +## Introspection functions |
| 96 | + |
| 97 | +Two functions are available for use inside a function body with `COLUMNREF` or `TABLEREF` parameters: |
| 98 | + |
| 99 | +- **`TABLEOF ( columnRef )`** — returns the table in which the referenced column is defined. |
| 100 | +- **`NAMEOF ( columnRef )`** — returns the column's fully qualified name as a string. |
| 101 | + |
| 102 | +These functions are intended to support runtime validation — for example, checking that two `COLUMNREF` parameters belong to the same table: |
| 103 | + |
| 104 | +```dax |
| 105 | +IF ( |
| 106 | + NAMEOF ( TABLEOF ( col1 ) ) <> NAMEOF ( TABLEOF ( col2 ) ), |
| 107 | + ERROR ( "col1 and col2 must belong to the same table" ) |
| 108 | +) |
| 109 | +``` |
| 110 | + |
| 111 | +This pattern does not work reliably today: when the column parameters are used incorrectly in the function body, DAX generates its own internal error from that usage before the `IF`/`ERROR` validation code executes, hiding the custom error message. The intent is correct and the pattern is expected to work once the evaluation order is enforced. |
| 112 | + |
| 113 | +See [Understanding parameter types in DAX user-defined functions](https://www.sqlbi.com/articles/understanding-parameter-types-in-dax-user-defined-functions-udf/). |
0 commit comments