«En teoría no hay diferencia entre teoría y práctica. En la práctica sí la hay.» — Yogi Berra
Hace poco, trabajando en un proyecto con un cliente, nos encontramos con un problema aparentemente simple: mostrar montos de venta formateados según la moneda de cada transacción. Dólares con $, euros con €, libras con £. Suena directo, ¿verdad?
Para simularlo y documentar el hallazgo, usamos el modelo de ejemplo Adventure Works DW 2020 disponible en el repositorio oficial de Microsoft: powerbi-desktop-samples/DAX.
El escenario
El modelo tiene una tabla Sales con una columna Extended Amount y una tabla Currency con los códigos y nombres de cada moneda. La tabla Currency incluye una columna Format String con los format strings personalizados para cada moneda:
| Moneda | Código | Format String |
|---|---|---|
| US Dollar | USD | "US"$#,0.00 |
| Euro | EUR | #,0.00 € |
| United Kingdom Pound | GBP | £#,0.00 |
| Australian Dollar | AUD | "A"$#,0.00 |
| Canadian Dollar | CAD | "C"$#,0.00 |
| Yen | JPY | ¥#,0 |
El primer intento: Dynamic Format String en la columna
Originalmente la columna Sales[Extended Amount] tenía un formato estático de tipo Currency ($#,0.00). El cliente no usaba medidas explícitas para este campo — lo arrastraba directamente a los visuales como columna con summarizeBy: sum, es decir, como una implicit measure. No es mi estilo preferido de trabajo, pero era el modelo del cliente y había que respetar el patrón existente.
Para implementar el formato dinámico por moneda sin cambiar la forma en que el cliente consumía el campo, configuré la propiedad FormatString de la columna con una expresión dinámica:
SELECTEDVALUE(
Currency[Format String],
"$#,0.00;($#,0.00);$#,0.00"
)
En TMDL se ve así:
createOrReplace
ref table Sales
column 'Extended Amount'
dataType: decimal
formatString: SELECTEDVALUE(Currency[Format String] , "\$#,0.00;(\$#,0.00);\$#,0.00")
lineageTag: b60f89ae-9208-46ee-b461-3ec3eb0ac465
summarizeBy: sum
sourceColumn: Extended Amount
annotation SummarizationSetBy = Automatic
annotation PBI_FormatHint = {"isCustom":true}
La idea es clara: según la moneda en el contexto de filtro, el formato cambia dinámicamente. Si es AUD, muestra A$10,674,857.51. Si es EUR, muestra 2,511,175.38 €. Si no hay moneda seleccionada o no tiene formato definido, aplica el default en dólares.
Resultado: El formato simplemente no se aplica. Los números aparecen pelados, sin símbolo de moneda. Peor aún: ni siquiera aplica el formato estático de Currency que tenía antes — al cambiar la propiedad FormatString a una expresión dinámica, se perdió el formato original sin ganar nada a cambio.
La solución: usar una medida
Creamos una medida con exactamente la misma lógica:
Total Extended Amount = SUM(Sales[Extended Amount])
Y en la propiedad FormatStringDefinition de la medida (el Dynamic Format String):
SELECTEDVALUE(
Currency[Format String],
"$#,0.00;($#,0.00);$#,0.00"
)
En TMDL:
createOrReplace
ref table Sales
/// Sum of Extended Amount with dynamic currency formatting based on the selected currency
measure 'Total Extended Amount' = SUM(Sales[Extended Amount])
lineageTag: 27c83828-9afb-4ca1-a2f1-e25d29a10921
formatStringDefinition = SELECTEDVALUE(Currency[Format String], "$#,0.00;($#,0.00);$#,0.00")
Resultado: Funciona perfectamente. Cada fila de la tabla muestra el monto con el símbolo de moneda correcto.
La prueba lado a lado
Pusimos ambas en la misma tabla visual, filtradas por moneda, y el resultado es contundente:
| Currency | Format String | Total Extended Amount (medida) | Extended Amount (columna) |
|---|---|---|---|
| Australian Dollar | "A"$#,0.00 | A$10,674,857.51 | 10,674,857.51 |
| Canadian Dollar | "C"$#,0.00 | C$16,269,797.60 | 16,269,797.60 |
| Deutsche Mark | 237,784.99 | 237,784.99 | |
| Euro | #,0.00 € | 2,511,175.38 € | 2,511,175.38 |
| United Kingdom Pound | £#,0.00 | £7,699,476.58 | 7,699,476.58 |
| US Dollar | "US"$#,0.00 | US$72,763,118.35 | 72,763,118.35 |
Mismo contexto de filtro. Misma expresión de formato. La medida formatea, la columna no.
Un detalle técnico importante
Para que los Dynamic Format Strings funcionen en medidas, el modelo necesita un compatibility level de 1601 o superior. Si tu modelo está en 1600 (común en modelos más antiguos), al intentar crear una medida con formatStringDefinition recibirás este error:
The database compatibility level of 1600 is below the minimal
compatibility level of 1601 needed for FormatStringDefinition.
La solución es subir el compatibility level. Se puede hacer desde Tabular Editor o programáticamente.
Qué dice la documentación
La documentación oficial de Microsoft presenta esta funcionalidad exclusivamente para medidas. El título del artículo es literal: «Create Dynamic Format Strings for Measures». SQLBI también lo confirma en su artículo sobre el tema: la propiedad FormatStringExpression se almacena en el TOM como parte de la medida.
Lo que no dice la documentación explícitamente es que no funciona en columnas. El modelo tabular permite asignar una expresión de formato a la propiedad FormatString de una columna — no lanza error, no da advertencia. Simplemente no la evalúa en tiempo de renderizado.
La reflexión
Es importante leer la documentación, pero también lo es la experimentación.
Hace mucho tiempo un desarrollador de Microsoft me dijo en una fiesta — una confidencia enorme: el problema es que el desarrollo y la documentación de los productos (SQL Server en aquel momento) se hacen en paralelo, basados en la especificación. El desarrollador interpreta algo y el documentador puede interpretar algo diferente. Al final el que gana es el documentador, porque si la documentación es diferente al producto, algún usuario reportará el bug. 🙂
En este caso no es un bug exactamente — es una funcionalidad que el TOM acepta sintácticamente pero que el motor de renderizado de Power BI no implementa para columnas. La lección es simple:
Si algo se puede configurar, no necesariamente significa que funcione. Probá siempre.
Referencias
- Create Dynamic Format Strings for Measures — Microsoft Learn
- Introducing Dynamic Format Strings for DAX Measures — SQLBI
- Deep Dive into Dynamic Format Strings — Power BI Blog
- DAX Sample Model — GitHub


Deja una respuesta