La selección dinámica de columnas es una funcionalidad potente, aunque poco utilizada, de ClickHouse que permite seleccionar columnas mediante expresiones regulares en lugar de nombrar cada columna individualmente. También se pueden aplicar funciones a las columnas coincidentes con el modificador APPLY, lo que la hace especialmente útil para tareas de análisis y transformación de datos.
Aprenderemos a usar esta funcionalidad con ayuda del conjunto de datos de taxis de Nueva York, que también puede encontrarse en el Playground de ClickHouse SQL.
Selección de columnas que coinciden con un patrón
Empecemos con un caso habitual: seleccionar solo las columnas que contienen _amount del conjunto de datos de taxis de NYC. En lugar de escribir manualmente el nombre de cada columna, podemos usar la expresión COLUMNS con una expresión regular:
FROM nyc_taxi.trips
SELECT COLUMNS('.*_amount')
LIMIT 10;Esta consulta devuelve las 10 primeras filas, pero solo de las columnas cuyos nombres coinciden con el patrón .*_amount (cualquier carácter seguido de "_amount").
┌─fare_amount─┬─tip_amount─┬─tolls_amount─┬─total_amount─┐
1. │ 9 │ 0 │ 0 │ 9.8 │
2. │ 9 │ 0 │ 0 │ 9.8 │
3. │ 3.5 │ 0 │ 0 │ 4.8 │
4. │ 3.5 │ 0 │ 0 │ 4.8 │
5. │ 3.5 │ 0 │ 0 │ 4.3 │
6. │ 3.5 │ 0 │ 0 │ 4.3 │
7. │ 2.5 │ 0 │ 0 │ 3.8 │
8. │ 2.5 │ 0 │ 0 │ 3.8 │
9. │ 5 │ 0 │ 0 │ 5.8 │
10. │ 5 │ 0 │ 0 │ 5.8 │
└─────────────┴────────────┴──────────────┴──────────────┘Supongamos que también queremos devolver columnas que contengan los términos fee o tax.
Podemos actualizar la expresión regular para incluirlos:
SELECT COLUMNS('.*_amount|fee|tax')
FROM nyc_taxi.trips
ORDER BY rand()
LIMIT 3; ┌─fare_amount─┬─mta_tax─┬─tip_amount─┬─tolls_amount─┬─ehail_fee─┬─total_amount─┐
1. │ 5 │ 0.5 │ 1 │ 0 │ 0 │ 7.8 │
2. │ 12.5 │ 0.5 │ 0 │ 0 │ 0 │ 13.8 │
3. │ 4.5 │ 0.5 │ 1.66 │ 0 │ 0 │ 9.96 │
└─────────────┴─────────┴────────────┴──────────────┴───────────┴──────────────┘Seleccionar varios patrones
Podemos combinar varios patrones de columnas en una sola consulta:
SELECT
COLUMNS('.*_amount'),
COLUMNS('.*_date.*')
FROM nyc_taxi.trips
LIMIT 5; ┌─fare_amount─┬─tip_amount─┬─tolls_amount─┬─total_amount─┬─pickup_date─┬─────pickup_datetime─┬─dropoff_date─┬────dropoff_datetime─┐
1. │ 9 │ 0 │ 0 │ 9.8 │ 2001-01-01 │ 2001-01-01 00:01:48 │ 2001-01-01 │ 2001-01-01 00:15:47 │
2. │ 9 │ 0 │ 0 │ 9.8 │ 2001-01-01 │ 2001-01-01 00:01:48 │ 2001-01-01 │ 2001-01-01 00:15:47 │
3. │ 3.5 │ 0 │ 0 │ 4.8 │ 2001-01-01 │ 2001-01-01 00:02:08 │ 2001-01-01 │ 2001-01-01 01:00:02 │
4. │ 3.5 │ 0 │ 0 │ 4.8 │ 2001-01-01 │ 2001-01-01 00:02:08 │ 2001-01-01 │ 2001-01-01 01:00:02 │
5. │ 3.5 │ 0 │ 0 │ 4.3 │ 2001-01-01 │ 2001-01-01 00:02:26 │ 2001-01-01 │ 2001-01-01 00:04:49 │
└─────────────┴────────────┴──────────────┴──────────────┴─────────────┴─────────────────────┴──────────────┴─────────────────────┘Aplicar funciones a todas las columnas
También podemos usar el modificador APPLY para aplicar funciones a todas las columnas.
Por ejemplo, si quisiéramos encontrar el valor máximo de cada una de esas columnas, podríamos ejecutar la siguiente consulta:
SELECT COLUMNS('.*_amount|fee|tax') APPLY(max)
FROM nyc_taxi.trips; ┌─max(fare_amount)─┬─max(mta_tax)─┬─max(tip_amount)─┬─max(tolls_amount)─┬─max(ehail_fee)─┬─max(total_amount)─┐
1. │ 998310 │ 500000.5 │ 3950588.8 │ 7999.92 │ 1.95 │ 3950611.5 │
└──────────────────┴──────────────┴─────────────────┴───────────────────┴────────────────┴───────────────────┘O quizá prefiramos ver el promedio:
SELECT COLUMNS('.*_amount|fee|tax') APPLY(avg)
FROM nyc_taxi.trips ┌─avg(fare_amount)─┬───────avg(mta_tax)─┬────avg(tip_amount)─┬──avg(tolls_amount)─┬──────avg(ehail_fee)─┬──avg(total_amount)─┐
1. │ 11.8044154834777 │ 0.4555942672733423 │ 1.3469850969211845 │ 0.2256511991414463 │ 3.37600560437412e-9 │ 14.423323722271563 │
└──────────────────┴────────────────────┴────────────────────┴────────────────────┴─────────────────────┴────────────────────┘Esos valores tienen demasiadas cifras decimales, pero por suerte podemos solucionarlo encadenando funciones. En este caso, aplicaremos la función avg, seguida de la función round:
SELECT COLUMNS('.*_amount|fee|tax') APPLY(avg) APPLY(round)
FROM nyc_taxi.trips; ┌─round(avg(fare_amount))─┬─round(avg(mta_tax))─┬─round(avg(tip_amount))─┬─round(avg(tolls_amount))─┬─round(avg(ehail_fee))─┬─round(avg(total_amount))─┐
1. │ 12 │ 0 │ 1 │ 0 │ 0 │ 14 │
└─────────────────────────┴─────────────────────┴────────────────────────┴──────────────────────────┴───────────────────────┴──────────────────────────┘Pero eso redondea los promedios a números enteros. Si queremos redondearlos a, por ejemplo, 2 decimales, también podemos hacerlo. Además de aceptar funciones, el modificador APPLY acepta una lambda, lo que nos da la flexibilidad de hacer que la función round redondee nuestros valores medios a 2 decimales:
SELECT COLUMNS('.*_amount|fee|tax') APPLY(avg) APPLY(x -> round(x, 2))
FROM nyc_taxi.trips; ┌─round(avg(fare_amount), 2)─┬─round(avg(mta_tax), 2)─┬─round(avg(tip_amount), 2)─┬─round(avg(tolls_amount), 2)─┬─round(avg(ehail_fee), 2)─┬─round(avg(total_amount), 2)─┐
1. │ 11.8 │ 0.46 │ 1.35 │ 0.23 │ 0 │ 14.42 │
└────────────────────────────┴────────────────────────┴───────────────────────────┴─────────────────────────────┴──────────────────────────┴─────────────────────────────┘Reemplazar columnas
Hasta aquí, todo bien. Pero supongamos que queremos ajustar uno de los valores y dejar los demás tal como están. Por ejemplo, quizá queramos duplicar el importe total y dividir el impuesto MTA entre 1.1. Podemos hacerlo con el modificador REPLACE, que sustituye una columna sin modificar las demás.
FROM nyc_taxi.trips
SELECT
COLUMNS('.*_amount|fee|tax')
REPLACE(
total_amount*2 AS total_amount,
mta_tax/1.1 AS mta_tax
)
APPLY(avg)
APPLY(col -> round(col, 2)); ┌─round(avg(fare_amount), 2)─┬─round(avg(di⋯, 1.1)), 2)─┬─round(avg(tip_amount), 2)─┬─round(avg(tolls_amount), 2)─┬─round(avg(ehail_fee), 2)─┬─round(avg(mu⋯nt, 2)), 2)─┐
1. │ 11.8 │ 0.41 │ 1.35 │ 0.23 │ 0 │ 28.85 │
└────────────────────────────┴──────────────────────────┴───────────────────────────┴─────────────────────────────┴──────────────────────────┴──────────────────────────┘Excluir columnas
También podemos excluir un campo con el modificador EXCEPT. Por ejemplo, para quitar la columna tolls_amount, escribiríamos la siguiente consulta:
FROM nyc_taxi.trips
SELECT
COLUMNS('.*_amount|fee|tax') EXCEPT(tolls_amount)
REPLACE(
total_amount*2 AS total_amount,
mta_tax/1.1 AS mta_tax
)
APPLY(avg)
APPLY(col -> round(col, 2)); ┌─round(avg(fare_amount), 2)─┬─round(avg(di⋯, 1.1)), 2)─┬─round(avg(tip_amount), 2)─┬─round(avg(ehail_fee), 2)─┬─round(avg(mu⋯nt, 2)), 2)─┐
1. │ 11.8 │ 0.41 │ 1.35 │ 0 │ 28.85 │
└────────────────────────────┴──────────────────────────┴───────────────────────────┴──────────────────────────┴──────────────────────────┘