Transformar filas en columnas

Transformar filas en columnas

En ingeniería de datos, tarde o temprano aparece la necesidad de transformar filas en columnas, especialmente cuando trabajamos con datos normalizados que el negocio quiere ver “aplanados”. En este artículo muestro cómo resolver este patrón en SQL Server usando dos enfoques distintos, ambos basados en un CTE para que el lector pueda reproducir el ejemplo sin usar tablas físicas.


CTE de ejemplo: correos electrónicos por tipo

Usaremos correos electrónicos porque es un caso más común hoy en día: un cliente puede tener un correo personal, uno laboral y uno alternativo. El objetivo es obtener una sola fila por cliente.

-- [ES] CTE con datos de prueba
-- [EN] CTE with sample data
with Emails as (
    select 101 as CustomerID, 'Personal' as EmailType, 'a.personal@sqlenvivo.com' as Email union all
    select 101, 'Work',      'a.work@sqlenvivo.com'                        union all
    select 101, 'Extra',     'a.extra@sqlenvivo.com'                       union all
    select 202, 'Personal',  'b.personal@sqlenvivo.com'                    union all
    select 202, 'Work',      'b.work@sqlenvivo.com'                        union all
    select 303, 'Personal',  'c.personal@sqlenvivo.com'
)

Resultado esperado:

CustomerIDPersonalWorkExtra
101a.personal@sqlenvivo.coma.work@sqlenvivo.coma.extra@sqlenvivo.com
202b.personal@sqlenvivo.comb.work@sqlenvivo.comNULL
303c.personal@sqlenvivo.comNULLNULL

Transformar filas en columnas con PIVOT (SQL Server)

Este es el enfoque nativo de SQL Server para transformar filas en columnas cuando se sabe de antemano los valores que se convertirán en columnas.

-- [ES] CTE con datos de prueba
-- [EN] CTE with sample data
with Emails as (
    select 101 as CustomerID, 'Personal' as EmailType, 'a.personal@sqlenvivo.com' as Email union all
    select 101, 'Work',      'a.work@sqlenvivo.com'                        union all
    select 101, 'Extra',     'a.extra@sqlenvivo.com'                       union all
    select 202, 'Personal',  'b.personal@sqlenvivo.com'                    union all
    select 202, 'Work',      'b.work@sqlenvivo.com'                        union all
    select 303, 'Personal',  'c.personal@sqlenvivo.com'
)
select 
    CustomerID,
    [Personal],
    [Work],
    [Extra]
from Emails
pivot (
    max(Email)
    for EmailType in ([Personal], [Work], [Extra])
) as p;

¿Por qué funciona?

  • PIVOT toma los valores de EmailType y los convierte en columnas.
  • El PIVOT necesita una función de agregación para resolver qué valor colocar en cada celda. En este caso solo hay un correo por tipo, así que MAX() no está “calculando” nada, sino que toma ese único valor y lo coloca en la columna correspondiente (también podría haberse usado MIN(), porque solo existe un valor por combinación).
  • Si un cliente no tiene un tipo de correo, la columna queda en NULL, que es lo correcto.
  • Es limpio, declarativo y legible.


Transformar filas en columnas con CASE + MAX() (solución universal)

Este patrón funciona en cualquier motor SQL, no solo en SQL Server.

-- [ES] CTE con los mismos datos de prueba
-- [EN] CTE with the same sample data
with Emails as (
    select 101 as CustomerID, 'Personal' as EmailType, 'a.personal@sqlenvivo.com' as Email union all
    select 101, 'Work',      'a.work@sqlenvivo.com'                        union all
    select 101, 'Extra',     'a.extra@sqlenvivo.com'                       union all
    select 202, 'Personal',  'b.personal@sqlenvivo.com'                    union all
    select 202, 'Work',      'b.work@sqlenvivo.com'                        union all
    select 303, 'Personal',  'c.personal@sqlenvivo.com'
)

-- [ES] Transformación universal usando CASE + MAX()
-- [EN] Universal transformation using CASE + MAX()
select 
    CustomerID,
    max(case when EmailType = 'Personal' then Email end) as Personal,
    max(case when EmailType = 'Work'     then Email end) as Work,
    max(case when EmailType = 'Extra'    then Email end) as Extra
from Emails
group by CustomerID;

¿Por qué funciona esto para transformar filas en columnas?

  • Cada CASE filtra el correo del tipo correspondiente.
  • MAX() toma ese único valor y lo convierte en una columna (también podría haberse usado MIN(), porque solo existe un valor por combinación).
  • Si no existe ese tipo, el CASE devuelve NULL, y MAX(NULL) es NULL.
  • Es explícito, robusto y universal.


¿Cuál usar?

  • SQL Server puro: PIVOT es elegante y directo.
  • Entornos mixtos: CASE + MAX() es la opción segura.
  • Tipos dinámicos: Hace falta SQL dinámico (tema para otro artículo).

El script que soluciona el problema puede descargarse en este enlace.


Imagen de Barbara JacksonPixabay

    error: Content is protected !!