Formateo de números en SQL Server: FORMAT(), cultura y decimales

Written by

in

Un error común al migrar lógica de formato desde otros lenguajes: intentar usar una máscara personalizada tipo ###,###,###.## dentro de la función FORMAT() de SQL Server. Esa sintaxis es válida en Excel, VBA, o C# con formato personalizado — pero SQL Server no la acepta, porque su función FORMAT() solo admite formatos estándar de .NET, no patrones de máscara personalizados con símbolos #.

Formato que NO funciona

FORMAT(valor, '###,###,###.##')   -- Error / resultado incorrecto en SQL Server

Formatos válidos en SQL Server

SQL Server usa los formatos estándar numéricos de .NET, combinados opcionalmente con un parámetro de cultura (idioma/región) que determina el separador de miles y decimales:

-- Miles con coma, decimales con punto (formato en-US)
FORMAT(valor, 'N2', 'en-US')

-- Miles con punto, decimales con coma (formato es-ES)
FORMAT(valor, 'N2', 'es-ES')

El N indica formato numérico estándar, y el 2 especifica la cantidad de decimales a mostrar (se puede usar cualquier número, N0 para sin decimales, N4 para cuatro decimales, etc.). El tercer parámetro (la cultura) es el que realmente decide cómo se ven los separadores — sin especificarlo, FORMAT() usa la configuración regional por defecto del servidor, lo cual puede variar entre entornos y generar inconsistencias si no se fija explícitamente.

CulturaEjemplo con FORMAT(1234567.5, ‘N2’, cultura)
en-US1,234,567.50
es-ES1.234.567,50
es-EC1.234.567,50

Consulta de ejemplo

Aplicando el formato correcto a una agregación con SUM() y agrupación:

SELECT
    codigo_vendedor,
    canal,
    FORMAT(SUM(monto_comision), 'N2', 'en-US') AS Total
FROM comisiones_preliminar c
WHERE c.fecha_registro >= '2026-01-01'
  AND (
        (codigo_vendedor IN ('1001','1002','1003') AND canal = 'P')
     OR (codigo_vendedor IN ('2001','2002') AND canal = 'N')
     OR (codigo_vendedor IN ('3001','3002','3003') AND canal = 'I')
      )
GROUP BY codigo_vendedor, canal
ORDER BY codigo_vendedor, canal;

Cuidado: FORMAT() devuelve texto, no un número

Un detalle importante que suele pasarse por alto: FORMAT() siempre retorna un valor de tipo NVARCHAR, no numérico. Esto significa que el resultado ya no se puede usar directamente en cálculos posteriores, comparaciones numéricas, ni ordenarse correctamente como número (un ORDER BY sobre esa columna ordenaría alfabéticamente, no numéricamente). FORMAT() debe reservarse para la capa de presentación final de un reporte, nunca para columnas intermedias que aún necesiten procesamiento numérico.

Alternativa: construir un formato manual tipo ###,###.##

Si se necesita replicar exactamente el comportamiento de una máscara personalizada (por ejemplo, para igualar un formato heredado de otro sistema), se puede construir manualmente combinando CONVERT con estilo de formato de moneda y limpieza de símbolos:

SELECT REPLACE(CONVERT(VARCHAR, CAST(1234567.5 AS MONEY), 1), '.00', '.50') AS formato_manual

En la práctica, es más simple y mantenible usar FORMAT(valor, 'N2', 'en-US') y, si el separador de miles debe ser distinto al que da esa cultura, hacer el reemplazo de caracteres directamente sobre el resultado con REPLACE():

-- Ejemplo: forzar separador de miles con punto y decimales con coma, manualmente
SELECT REPLACE(REPLACE(FORMAT(1234567.50, 'N2', 'en-US'), ',', '|'), '.', ',')