Optimizar el código para mejorar la lectura, manutención y robustez en vez de la cantidad de lineas de código
Las lineas de código son baratas, el tiempo de las personas es caro.
Lineas de código largas son difíciles de leer.
Es mas fácil de leer y de ser consistente, evitar capitalizaciones, espacios o otros caracteres especiales
Es preferible que todas las palabras reservadas y nombres de objetos (columnas, tablas, funciones, etc) esten en ingles.
Todos los motores de base de datos fueron contruidos en ingles, las sentencias son nativas en ingles y la cantidad de informacion disponible en internet en ingles es mucho mayor que en español. Ademas evitamos errores con caracteres desconocidos.
Minuscula es más fail de leer que mayuscula y no tenes que estar constantemente apretando la tecla shift.
/* Recomendado */
select *
from customers
/* Recomendado */
select count(*) as customers_count
from customers
/* No recomendado */
SELECT *
FROM customers
/* No recomendado */
Select *
From customers
/* No recomendado */
select COUNT(*) as customers_count
from customers!= se lee mas facil “no igual" que es mas entendible que usar <>.
|| es un operador standard de SQL, y en algunos operadores (ejemplo Redshift) concat solo acepta dos argumentos.
coalescees universalmente aceptado, por ejemplo Redshift no tieneifnully BigQuery no tienenvl.coalescees mas flexible y acepta mayor cantidad de argumentos
isnull y notnull son específicos de Redshift.
case statements son aceptados universalmente, mientras que Redshift no tiene iff, y BigQuery se llama if en vez deiff.
/* Recomendado */
select count(*) as customers_count
from customers
/* No recomendado */
select count(*) customers_count
from customers/* Recomendado */
select max(id) as max_customer_id
from customers
/* No recomendado */
select max(id)
from customersLas queries filtran primero por “where” en el orden de ejecución por eso son más eficientes.
union all es más eficiente por qué no tiene que ordenar y de-duplicar las filas.
Hace que sea más clara la intención de la query.
/* Recomendado */
select distinct
customer_id
, date_trunc('day', created_at) as purchase_date
from orders
/* No recomendado */
select
customer_id
, date_trunc('day', created_at) as purchase_date
from orders
group by 1, 2No hay necesidad de afectar la performance de la query. Si el usuario final necesita los datos ordenados generalmente lo pueden hacer por si solos.
Es mas fail de expandir los comentarios de una sola linea en multiples lineas sin cambiar la sintaxis.
Cuando expandas a varias lineas
- Mantener la primera linea
/*en la primera linea del comentario y el ultimo*/en la misma linea del ultimo comentario. - Identar las subsecuentes lineas con una tabulación.
/* Recomendado */
-- No recomendado
/* Recomendado: Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Dolor sed viverra ipsum nunc aliquet bibendum enim. */
/* No recomendado: Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Dolor sed viverra ipsum nunc aliquet bibendum enim. */
-- No recomendado: Lorem ipsum dolor sit amet, consectetur adipiscing elit,
-- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-- Dolor sed viverra ipsum nunc aliquet bibendum enim.Algunos dialectos puede procesar doble comillas o hasta triple pero en la mayoria:
- Doble comillas representan identificadores.
- Triple comillas es para aquellos valores que tienen comillas per se.
/* Recomendado */
select *
from customers
where email like '%@domain.com'
/* No recomendado */
select *
from customers
where email like "%@domain.com"
/* Will probably result in an error like `column "%@domain.com" does not exist`. */
/* No recomendado */
select *
from customers
where email like '''%@domain.com'''
/* Will probably be interpreted like '\'%domain.com\''. */- Usar siempre
onen todos los casos. - Si hay que agregar mas condiciones,
ones mas fácil de adaptar. usingpuede producir resultados inconsistentes con outer joins en algunas base de datos.
Es mejor que sea explicito que tipo de join estamos utilizando.
/* Recomendado */
select *
from customers
inner join orders on customers.id = orders.customer_id
/* No recomendado */
select *
from customers
join orders on customers.id = orders.customer_idEn las condiciones del join, poner la tabla que fue referenciada primero inmediatamente después delon.
Es mas fácil entender la relación de los campos entre tablas y si va a haber una explosión de datos.
/* Recomendado */
select *
from customers
left join orders on customers.id = orders.customer_id
/* primary key = foreign key --> uno-a-muchos --> explosión */
/* Recomendado */
select *
from orders
left join customers on orders.customer_id = customers.id
/* foreign key = primary key --> muchos-a-uno --> no hay explosion */
/* No recomendado */
select *
from customers
left join orders on orders.customer_id = customers.idLa idea es que seas capaz de ver a simple vista de dónde viene la columna y evitar problemas donde el nombre de la columna es el mismo para diferentes tablas.
/* Recomendable */
select
customers.email
, orders.invoice_number
, orders.total_amount
from customers
inner join orders on customers.id = orders.customer_id
/* No recomendado */
select
email
, invoice_number
, total_amount
from customers
inner join orders on customers.id = orders.customer_idSolo las condiciones del join deben ponerse en la clausal del join, todas las condiciones de filtrado deben ir en el where.
/* Recomendado */
select
...
from orders
inner join customers on orders.customer_id = customers.id
where
orders.total_amount >= 100
and customers.email like '%@domain.com'
/* No recomendado */
select
...
from orders
inner join customers on
orders.customer_id = customers.id
and customers.email like '%@domain.com'
where orders.total_amount >= 100Si estas usando right join probablemente debas cambiar el orden de la tabla.
- Si no hay problemas de performances, cada CTE debe realizar una única unidad de trabajo.
- Los nombres de los CTEs deben ser los sufientemente explícitos para entender de qué se trata.
- Los nombres de los CTEs no deben incluir la palabra “cte”
- CTEs que son complejos debe estar comentados
CTEs van a hacer las queries más fáciles de leer, entender la razón del por que esta ahi, más fail de debugger y pueden ser referenciadas multiples veces. Ademas son más faciales de adaptar o refactorizar más adelante.
/* Recomendado */
with
paying_customers as (
select *
from customers
where plan_name != 'free'
)
select ...
from paying_customers
/* No recomendado */
select ...
from (
select *
from customers
where plan_name != 'free'
) as paying_customersEsto permite de ver fácilmente si la columna es una primary key y entender la relation con otras tablas (uno a muchos, muchos a uno, etc).
/* Recomendado */
select ...
from orders
left join customers on orders.customer_id = customers.id
/* Facil de ver que es una relacion muchos a uno y que no va haber explosion de datos. */
/* No recomendado */
select ...
from orders
left join customers on orders.customer_id = customers.customer_id
/* Recomendado */
select ...
from orders
left join some_exceedingly_long_name on orders.order_id = some_exceedingly_long_name.id
/* No recomendado */
select ...
from orders
left join some_exceedingly_long_name on orders.some_exceedingly_long_name_id = some_exceedingly_long_name.some_exceedingly_long_name_idLa idea es ser consistente y tener FK tales como "order_id", "customer_id", etc.
- Las columnas de fechas deben ser basadas en UTC y nombradas de la siguiente manera
<event>_dateo<event>_dt. - Aquellas que tengan una zona horaria especifica:
<event>_date_<timezone indicator>(e.g.order_date_et). - Columnas con fecha y hora deben estar basadas en UTC y nombradas de la siguiente manera:
<event>_at. - Si tienen alguna zona horaria especifica
<event>_at_<timezone indicator>(ejemplo:created_at_pt). - Ejemplo de zona horaria de EEUU:
et= Eastern Time.ct= Central Time.mt= Mountain Time.pt= Pacific Time.
- Las columnas booleanas deben tener un prefijo en presente o pasada indicando a que hace referencia los valores True/False de la siguiente manera:
is_orwas_.has_orhad_.does_ordid_.
Columnas con valores numéricos con una unidad de medida que se conoce deben tener un sufijo que indique la unidad de medida.
Ejemplos:
price_usdweight_ozweight_kgweight_grams
Sugerencias.
- Si el nombre de la tabla tiene 3 palabras o menos no le pongas alias.
- Si tiene mas, usa un subconjunto de las palabras como alias. (ejemplo:
partner_shipments_order_line_itemspuede ser reverenciada comoline_items).
/* Recomendado */
select
customers.email
, orders.invoice_number
from customers
inner join orders on customers.id = orders.customer_id
/* No recomendado */
select
c.email
, o.invoice_number
from customers as c
inner join orders as o on c.id = o.customer_idEl patron general a seguir es:
- Si hay solo una cosa, ponerlo en la misma linea que la primera keyword.
- Si hay multiples cosas, poner cada una en su propia linea incluyendo la primera, indentado un nivel con respecto a la primer keyword.
Por default poner todo del lado izquierdo salvo casos mencionados en las distintas partes de este documento.
/* Recomendado */
select email
from customers
where email like '%@domain.com'
/* No recomendado */
select email
from customers
where email like '%@domain.com'/* Recomendado */
select
id
, email
from customers
where
email like '%@domain.com'
and plan_name != 'free'
/* No recomendado */
select
id
, email
from customers
where email like '%@domain.com'
and plan_name != 'free'Si hay alguno de estos operadores cada uno debe tener su linea y debemos ponerlo al inicio.
- La idea es leer por arriba el lado izquierdo de la query y darse cuenta rápidamente la logica de la query, sin necesidad de leer toda lea linea.
- Poner la condición en la misma linea que el operador lo hace mas claro de entender por que esta siendo usado.
/* No recomendado */
select *
from customers
where
email like '%@domain.com' and
plan_name != 'free'
/* Recomendado */
select *
from customers
where
email like '%@domain.com'
and plan_name != 'free'Si el coding tiene comas poner cada una en un nivel distinto, poner las comas al inicio seguidas de un espacio.
- Es la forma mas fácil de ver si falta alguna coma.
- La coma solo esta ahi por lo que sigue, si nada sigue la coma no tiene sentido por eso es mas claro y menos facil que haya errores si ponemos las comas adelante.
/* Recomendado */
with customers as (
...
)
, paying_customers as (
...
)
select
id
, email
, date_trunc('month', created_at) as signup_month
from paying_customers
where email in (
'user-1@example.com'
, 'user-2@example.com'
, 'user-3@example.com'
)
/* No recomendado */
with
customers as (
...
),
paying_customers as (
...
)
select
id,
email,
date_trunc('month', created_at) as signup_month
from paying_customers
where email in (
'user-1@example.com',
'user-2@example.com',
'user-3@example.com'
)- Sí hay solo una columna ponerla al mismo nivel que el
select. - Sí hay multiples columnas, poner cada una en una linea, incluida la primera e indentar un nivel más que el
select. - Si hay un
distinctqualifier, ponerlo en el mismo nivel que elselect.
/* Recomendado */
select id
/* Recomendado */
select
id
, email
/* No recomendado */
select id, email
/* No recomendado */
select id
, email
/* Recomendado */
select distinct country
/* Recomendado */
select distinct
state
, country
/* No recomendado */
select distinct state, country- Poner la tabla inicial en la misma linea que el
from. - Si hay otras tablas en el join:
- Poner cada
joinen su propia linea, en el mismo nivel de indentation que elfrom. - Sí hay una sola condición de join, ponerlo en la misma linea que el
join. - Si hay multiples condiciones, terminar el
joinconony poner cada condition una linea mas, incluido la primera condición, inventada un nivel más.
- Poner cada
/* Recomendado */
from customers
/* Recomendado */
from customers
left join orders on customers.id = orders.customer_id
/* No recomendado */
from customers
left join orders on customers.id = orders.customer_id
/* No recomendado */
from customers
left join orders
on customers.id = orders.customer_id
/* Recomendado */
from customers
left join orders on
customers.id = orders.customer_id
and customers.region_id = orders.region_id
/* No recomendado */
from customers
left join orders on customers.id = orders.customer_id
and customers.region_id = orders.region_id
/* No recomendado */
from customers
left join orders
on customers.id = orders.customer_id
and customers.region_id = orders.region_id- Sí hay una sola condición ponerla en la misma linea que el
where. - Sí hay más de una columna ponerlo cada uno en su respectiva linea e indentedo con respecto al
where.
/* Recomendado */
where email like '%@domain.com'
/* Recomendado */
where
email like '%@domain.com'
and plan_name != 'free'
/* No recomendado */
where email like '%@domain.com' and plan_name != 'free'
/* No recomendado */
where email like '%@domain.com'
and plan_name != 'free'- Evitar agrupa/ordena con numeros.
- Puede haber problemas si cambiamos las columnas pero no editamos las columnas referenciadas en el
group by/order byhaciendo que ordenemos/agrupemos por columnas indeseadas.
- Puede haber problemas si cambiamos las columnas pero no editamos las columnas referenciadas en el
- Si se agrupa/ordena por el nombre de la columna:
- Si hay una sola columna hacerlo al mismo nivel que el
group by/order by. - Si hay mas de una columna ponerlo cada uno en su respectiva linea e indentedo con respecto al
group by/order by.
- Si hay una sola columna hacerlo al mismo nivel que el
/* No recomendado */
group by 1, 2, 3
/* No recomendado */
group by
1
, 2
, 3
/* Recomendado */
order by plan_name
/* Recomendado */
order by
plan_name
, signup_month
/* No recomendado */
order by plan_name, signup_month
/* Bad */
No recomendado by plan_name
, signup_month- Agregar un blanco en cada alinea del CTE para marcar la diferencia.
- Poner comentarios sobre el CTE dentro del paréntesis del CTE al mismo nivel que el
select.
/* Recomendado */
with paying_customers as (
select ...
from customers
)
select ...
from paying_customers
/* No recomendado */
with
paying_customers as (
select ...
from customers
)
select ...
from paying_customers
/* Recomendado */
with paying_customers as (
select ...
from customers
)
, paying_customers_per_month as (
/* CTE comments... */
select ...
from paying_customers
)
select ...
from paying_customers_per_month
/* No recomendado */
with paying_customers as (
select ...
from customers
)
/* CTE comments... */
, paying_customers_per_month as (
select ...
from paying_customers
)
select ...
from paying_customers_per_month- Se puede poner un
casestatement todo en una tunica linea si tiene un unicowheny la linea no es muy larga. - Para
casestatements con multiples lineas:when:- Cada
whendebe empezar en su propia linea e indentarlo un nivel mas que elcasestatement. - Si un
whentiene multiples condiciones, mantener la primera condicion en la primer linea y poner el resto en sus propias lineas e intentarlas todas al mismo nivel.
- Cada
then:- Los
thenpueden ir en la misma linea delwhenmientras que la linea sea muy larga. - Sino, utilizar el
thenen su propia linea, indentada un nivel mas que elwhenque le corresponde. - Si un
thentiene multiples lineas, cada uno debe tener su propia linea y estar intentada un nivel mas que elthen.
- Los
else:- Un
elsedebe ir en su propia linea, al mismo nivel que elwhen. - Si un
elsetiene multiples lineas cada linea tienen que estar inventadas un nivel mas que la condiciónelse.
- Un
end:- El
enddebe tener su propia linea al mismo nivel que la sentenciacase.
- El
- Si el
caseesta dentro de una función de agregación se deben cumplir los mismo requisaos mencionados indentado un nivel mas que la función de agregación.
/* Recomendado */
select
case when customers.status_code = 1 then 'Active' else 'Inactive' end as customer_status
/* No recomendado */
select
case when customers.status_code = 1 and customers.deleted_at is null then 'Active' else 'Inactive' end as customer_status
/* Recomendado */
select
case
when customers.status_code = 1 then 'Active'
else 'Inactive'
end as customer_status
, ...
/* No recomendado */
select
case when customers.status_code = 1 then 'Active'
else 'Inactive' end as customer_status
, ...
/* Recomendado */
select
...
, case
when customers.status_code = 1
and customers.deleted_at is null
then 'Active'
else 'Inactive'
end as customer_status
/* No recomendado */
select
...
, case
when customers.status_code = 1 and customers.deleted_at is null
then 'Active'
else 'Inactive'
end as customer_status
/* Recomendado */
select
...
, sum(
case
when customers.status_code = 1
and customers.deleted_at is null
then customers.lifetime_value
else 0
end
) as active_customers_lifetime_value
/* No recomendado */
select
...
, sum(case
when customers.status_code = 1 and customers.deleted_at is null then customers.lifetime_value
else 0
end) as active_customers_lifetime_value- Podes poner toda la Window function en una linea sino es muy larga.
- Si la tenes que dividir en multiples lineas:
- Poner cada clausula dentro del
over ()en su propia linea, indentarla un nivel mas de la window function. Ejemplo:partition byorder byrows between/range between
- Poner el parentesis final del
over ()en su propia linea con una indentacion similar a la primera linea de la window function.
- Poner cada clausula dentro del
/* Recomendado */
select
customer_id
, invoice_number
, row_number() over (partition by customer_id order by created_at) as order_rank
from orders
/* Recomendado */
select
customer_id
, invoice_number
, row_number() over (
partition by customer_id
order by created_at
) as order_rank
from orders
/* No recomendado */
select
customer_id
, invoice_number
, row_number() over (partition by customer_id
order by created_at) as order_rank
from orders- Mostrar los valores de una lista en multiples lineas cuando es muy larga.
/* Recomendado */
where email in (
'user-1@example.com'
, 'user-2@example.com'
, 'user-3@example.com'
)
/* No recomendado */
where email in ('user-1@example.com', 'user-2@example.com', 'user-3@example.com')/* No recomendado */
select *
from customers
where plan_name in ( 'monthly', 'yearly' )
/* Recomendado */
select *
from customers
where plan_name in ('monthly', 'yearly')Esta guía estuvo inspirada por las siguientes guías: