7 puntos por GN⁺ 2024-09-26 | 2 comentarios | Compartir por WhatsApp
  • Una lista de hábitos al escribir SQL y patrones de consultas que se usan con frecuencia en el trabajo de análisis de datos, con la premisa de que no todo aplica igual a todos los RDBMS
  • En términos de legibilidad, se recomienda usar comas al inicio, WHERE 1=1, sangría, CTE, comentarios y USING para que las consultas sean más fáciles de leer y modificar
  • En procesamiento de datos, se muestran ejemplos de sintaxis como anti-join, QUALIFY, GROUP BY ROLLUP y EXCEPT, usadas en la práctica para filtrar resultados, generar totales y verificar diferencias entre tablas
  • En rendimiento y exactitud, NOT IN con NULL, la conversión implícita de tipos y los conflictos de alias en campos calculados pueden afectar el resultado o la velocidad de una consulta
  • En consultas complejas, hábitos básicos como entender el orden de ejecución, revisar la documentación, indicar el origen de las columnas y usar buenos nombres para consultas guardadas son importantes para mejorar la depuración y la reutilización

Hábitos para mejorar la legibilidad al escribir SQL

  • Este repositorio es una lista organizada de consejos y trucos de SQL aprendidos a lo largo de varios años, centrada en lo útil para el trabajo diario de una analista o un analista de datos y en lo que habría sido bueno saber al comenzar a usar SQL
  • Algunos consejos pueden no aplicar a todos los RDBMS
  • Comas al inicio y AND al inicio

    • En la cláusula SELECT, se recomienda usar comas al inicio en lugar de comas al final para separar campos
    • Hace más claro si se trata de una nueva columna o de una línea continuada
    • Aunque cambie la longitud de las líneas, es más fácil detectar si falta una coma
    • Por la misma razón, también se puede poner AND al inicio de las condiciones en la cláusula WHERE
  • Facilitar pruebas de condiciones con WHERE 1=1

    • Si se agrega la condición dummy 1=1 en la cláusula WHERE, la consulta no se rompe aunque se comenten condiciones durante las pruebas
    • Incluso si se comentan todas las condiciones, 1=1 permanece y la consulta puede seguir ejecutándose
  • Sangría y formateadores

    • La sangría ayuda a que colegas y tu yo del futuro lean la consulta más fácilmente
    • Si hay una guía del equipo o de la empresa, conviene seguirla; si no, es mejor usar un estilo que te funcione
    • Se pueden usar el formateador en línea poorsql o el linter sqlfluff
  • Considerar CTE en consultas complejas

    • Si se anidan inline views en 2 o 3 niveles o más, es fácil que unas semanas después la consulta se vuelva difícil de entender
    • Los CTE se presentan como una forma de organizar mejor consultas largas y ayudar con la reutilización y la depuración
  • Que los comentarios expliquen el “por qué”

    • Después de un tiempo, puede ser difícil recordar por qué se hizo un procesamiento específico
    • En general, es mejor que los comentarios expliquen por qué se hizo algo, más que cómo funciona el código
    • El ejemplo agrega un comentario a una condición que excluye contenido archive porque un CMS nuevo no puede procesar el formato de video archive
  • Usar USING al unir columnas con el mismo nombre

    • Si se hace un join entre dos tablas usando columnas con el mismo nombre, USING permite expresarlo de forma más simple que ON
    • USING elimina duplicados de la columna común en el resultado y devuelve solo una
    • Si se usa ON y no se indica explícitamente la columna común, puede aparecer un error de ambiguous column name

Sintaxis útil para procesamiento de datos

  • Encontrar filas ausentes en otra tabla con anti-join

    • El anti-join se usa para devolver filas que existen en una tabla pero no tienen coincidencia en otra
    • El ejemplo trata el caso de obtener solo los video_id de contenido que no fue archivado
    • Hay varias formas de implementarlo
    • Filtrar solo las filas donde la clave de la tabla emparejada sea NULL después de un LEFT JOIN
    • Usar NOT IN con una subconsulta
    • Usar NOT EXISTS con una subconsulta correlacionada
    • No se recomienda NOT IN porque puede no comportarse como se espera debido a valores NULL
  • Filtrar resultados de funciones de ventana con QUALIFY

    • QUALIFY permite filtrar resultados de consultas con base en el resultado de una función de ventana
    • Permite filtrar sin usar inline views, reduciendo la cantidad de líneas de código
    • El ejemplo selecciona los 10 principales mercados por producto con DENSE_RANK() y luego filtra con QUALIFY
    • Existe la limitación de que QUALIFY parece estar disponible solo en grandes data warehouses como Snowflake, Amazon Redshift y Google BigQuery
  • GROUP BY y ORDER BY basados en posición de columnas

    • Se puede escribir GROUP BY 1 u ORDER BY 2 usando la posición de la columna en vez del nombre
    • Puede ser útil en consultas temporales o de un solo uso
    • Para código de producción, se recomienda siempre referenciar directamente los nombres de las columnas
  • Generar totales con GROUP BY ROLLUP

    • GROUP BY ROLLUP puede usarse para generar subtotales y totales generales
    • El ejemplo calcula la suma de salarios por departamento y además genera una fila con la suma total de salarios
    • La documentación de Transact-SQL explica que ROLLUP crea grupos por combinaciones de expresiones de columnas y reduce la cantidad de grupos de derecha a izquierda para generar subtotales y totales
    • Si se aplica COALESCE, la fila de total puede mostrarse como Total
    • Hay que cuidar la columna de ordenamiento para que la fila de total quede al final del resultado
  • Encontrar diferencias entre dos conjuntos de resultados con EXCEPT

    • EXCEPT devuelve filas que están en el resultado de la primera consulta pero no en el de la segunda
    • Si se usan juntos EXCEPT y UNION ALL, se puede validar si dos tablas tienen los mismos datos
    • Si no se devuelve ninguna fila, ambas tablas son iguales
    • Si se devuelven filas, esas filas son la causa de la diferencia

Patrones que dañan el rendimiento y la exactitud

  • En columnas que pueden ser NULL, NOT EXISTS es mejor que NOT IN

    • Si la columna comparada permite NULL, NOT IN normalmente puede ser más lento que NOT EXISTS
    • Se menciona haber vivido este problema en Snowflake, y Don’t Do This del PostgreSQL Wiki indica que NOT IN (SELECT ...) no se optimiza bien
    • NOT IN no funciona como se espera si hay NULL en los valores comparados
    • Que una columna permita NULL no significa que realmente tenga valores NULL, pero al trabajar con tablas que no se pueden modificar, NOT EXISTS puede ayudar a mejorar la velocidad
  • La conversión implícita de tipos puede ralentizar o fallar

    • Si en una condición se pone un valor de un tipo de dato distinto al de la columna, la base de datos puede intentar una conversión implícita de tipos
    • El ejemplo trata el caso de comparar el entero 200050 contra una columna video_id de tipo cadena
    • Depender de una conversión implícita puede causar problemas
    • Si hay valores que no se pueden convertir, puede producirse un error
    • La consulta puede volverse más lenta por el trabajo extra de convertir cada valor al tipo indicado
    • Se puede usar el mismo tipo de dato que la columna o, para evitar errores, funciones como TRY_TO_NUMBER de Snowflake
    • El impacto en velocidad depende del tamaño del dataset procesado

Errores frecuentes

  • NOT IN y NULL

    • NOT IN no funciona si entre los valores comparados hay NULL
    • Como NULL representa Unknown, el motor SQL no puede verificar que el valor evaluado no esté en la lista
    • En ese caso, una alternativa es usar NOT EXISTS
  • Conflictos de alias en campos calculados

    • Si el nombre de un campo calculado queda igual al de una columna existente, puede haber comportamientos inesperados
    • La documentación de GROUP BY de Snowflake indica que si un nombre en la cláusula GROUP BY coincide tanto con un nombre de columna como con un alias, se usa el nombre de la columna
    • En el ejemplo, si se crea el alias LEFT(product, 1) AS product y luego se usa GROUP BY product, la agrupación se hace por la columna original product y no por la primera letra, por lo que se devuelven 3 filas
    • Hay dos soluciones
    • Usar un alias único como product_letter
    • Indicar explícitamente la expresión, como GROUP BY LEFT(product, 1)
    • Los alias también pueden causar problemas en funciones de ventana
    • En el ejemplo, un CASE cambia a 0 el revenue de Robot, pero como se aplica después de ejecutar la función de ventana, el ranking sale distinto a lo esperado
    • Cuando sea posible, se necesita usar un alias único o poner directamente la expresión calculada dentro del ORDER BY de la función de ventana
  • Indicar a qué tabla pertenece cada columna

    • En consultas complejas con varios joins, debe ser posible rastrear un problema de valores hasta la tabla de origen
    • Si dos tablas comparten el mismo nombre de columna y no se indica a cuál pertenece, el RDBMS puede lanzar un error
    • El ejemplo deja claro el origen de las columnas usando alias de tabla como vc.video_id y metadata.season

Orden de ejecución, documentación y nombres guardados

  • Entender el orden de ejecución de SQL

    • Se señala que uno de los consejos más importantes para quienes aprenden SQL es entender el orden de ejecución de las cláusulas
    • Conocer ese orden puede cambiar mucho la forma de escribir consultas
    • Como material de referencia se propone A beginner’s guide to the true order of SQL operations
  • Leer la documentación hasta el final

    • Se menciona un caso en Snowflake donde se usó GREATEST() para devolver la fecha más reciente entre varias columnas de fecha
    • GREATEST() devuelve NULL si uno de sus argumentos es NULL
    • Si se hubiera leído más la documentación, se habría podido usar GREATEST_IGNORE_NULLS() en lugar de COALESCE(GREATEST(...), ...)
    • En muchos casos, revisar la documentación toma menos de un minuto y puede ahorrar el esfuerzo de encontrar por qué algo se comporta distinto de lo esperado
  • Usar nombres descriptivos para consultas guardadas

    • Para evitar no encontrar una consulta que luego haya que volver a ejecutar o consultar, conviene guardarla con un nombre descriptivo
    • El nombre guardado suele incluir el tema de la consulta, el mes de ejecución y el nombre de quien la pidió
    • El ejemplo tiene el formato Lapsed users analysis - 2023-09-01 - Olivia Roberts

2 comentarios

 
hiyama 2024-09-26

Las comas iniciales en esta publicación están escritas todas como comas finales. En el original están ingresadas como iniciales.

-- Good:  
SELECT   
timeslot_date  
, timeslot_channel   
, overnight_fta_share  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) > 7, -- First argument of IFF.  
	LAG(overnight_fta_share, 1) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity), -- Second argument of IFF.  
		NULL) AS C7_fta_share -- Third argument of IFF.  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) >= 29,   
		LAG(overnight_fta_share, 2) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),   
			NULL) AS C28_fta_share  
FROM timeslot_data  
;  
 
GN⁺ 2024-09-26
Opiniones de Hacker News
  • El consejo que agregaría es este: aprende bien tu servidor de DB y revisa con frecuencia los planes de ejecución. Pueden salir resultados inesperados, así que conviene ajustar y volver a revisar.
    Por lo general, EXISTS es más rápido que IN, y NOT EXISTS se comporta distinto que EXCEPT al manejar NULL. A veces es mucho más rápido usar columnas de subconsulta en la lista de SELECT que unir tablas y luego filtrar filas con algo como DISTINCT. Esto puede ser cierto incluso si traes más de 10 valores de la misma tabla, y también aunque el servidor de DB soporte lateral join. Eso sí, la subconsulta debe devolver como máximo una fila.
    Las consultas que no sean de una sola vez no deberían hacer un escaneo completo de tabla. El escaneo de tabla de hoy puede ser la caída de mañana, así que hay que agregar índices. Recuerda también que la cláusula GROUP BY suele determinar el uso de índices.
    Si necesitas filtrar por una expresión, por ejemplo comprobar si una subcadena es igual a cierto valor, puedes agregar una columna calculada e indexarla. Algunas DB soportan directamente índices sobre expresiones. Usar UNION ALL en lugar de OR a menudo puede hacer que consultas complejas o con varias condiciones OR sean mucho más rápidas.
    Cuando la DB no elige de forma inteligente el orden de filtrado, también es útil forzar el orden haciendo JOIN con una subconsulta.

    • Lo más útil es aprender el DBMS en sí. Cada DB tiene sus particularidades de rendimiento y niveles de aislamiento, y también distintas funciones extra, así que no hay forma de evitarlo.
      Algo que me pareció interesante en Postgres —y es posible que también ocurra en otras DB— es que una operación INSERT (SELECT ...) puede acelerarse de forma casi lineal si la particionas manualmente según la cantidad de núcleos de CPU. Incluso funcionó con unas 10 uniones. Primero miras EXPLAIN y encuentras el join más interno o más externo; luego ejecutas consultas paralelas separadas para cada rango de filas (id >= start AND id < end). En un trabajo de hace 6 años usamos mucho este método por razones raras. Postgres 10+ agregó paralelismo, pero por lo que sé todavía no es tan avanzado como esto.
    • No entiendo exactamente qué significa usar “columnas” de subconsulta en la lista de SELECT.
      Por ejemplo, si ejecuto SELECT column1, (SELECT column2, column3, ... FROM table_b WHERE table_a.id = table_b.a_id) FROM table_a, como era de esperarse aparece “subquery must return only one column”. ¿La idea es devolver varias columnas como un record/tipo compuesto?
      No me resultó obvio por qué la cláusula GROUP BY suele determinar el uso de índices, pero para quien tenga curiosidad, este artículo lo explica paso a paso: https://www.brentozar.com/archive/2015/06/indexing-for-group...
    • De acuerdo. Hay que usar EXPLAIN y aprender a interpretarlo con la herramienta que prefieras. También hay que monitorear las consultas.
      En una startup anterior instalamos PgHero, y nos ayudó muchísimo con la optimización de rendimiento y la priorización.
    • Incluso las consultas bien diseñadas a menudo se comportan distinto de lo esperado. Casos típicos son cuando las estadísticas de columnas no están actualizadas o cuando los datos están fragmentados en una tabla grande. Por ejemplo, con inserciones de claves primarias aleatorias.
    • No estoy de acuerdo con “las consultas que no sean de una sola vez no deberían hacer un escaneo completo de tabla. El escaneo de tabla de hoy puede ser la caída de mañana”.
      Hay consultas en las que un escaneo completo de tabla es la estrategia de acceso más eficiente. Normalmente son consultas analíticas/de agregación que leen toda la tabla, y a veces incluso cuando solo se recupera el 50% de las filas, un escaneo de tabla es mejor.
      Tampoco veo bien cómo un escaneo de tabla de solo lectura terminaría causando una caída, porque no bloquea el acceso concurrente. La única desventaja es que genera mucha carga de I/O, pero si el servidor no puede soportar eso, diría que desde el principio está gravemente subdimensionado.
  • Los 3 ejemplos de la sección “legibilidad” son raros. Los primeros 2 literalmente sacrifican legibilidad para que sea más fácil escribirlos, y el último es un monstruo difícil de leer que casi ni la indentación puede salvar.

    • El formato con coma al inicio tiene ventajas además de la legibilidad. Por ejemplo, en sistemas de control de versiones, el formato de una línea por argumento + coma al inicio hace que un cambio de argumento aparezca como una diferencia de una sola línea.
      Creo que los desarrolladores miran el historial de commits casi tanto como el código fuente real.
    • No es que me encante la apariencia de las dos primeras convenciones, pero son convenciones que la gente que escribe SQL de verdad usa en la práctica. También puedo entender por qué existen.
      Las he visto con suficiente frecuencia como para que ya no me molesten mucho.
    • Como alternativa, puedes escribir SQL hecho un desastre, como si un niño de tres años acabara de descubrir MSPaint, luego apretar el botón de “beautifier” e irte a almorzar temprano.
    • No entiendo por qué crees que es peor.
      No veo el problema
      y no parece haber nada incorrecto.
    • ¿Quién demonios separa las columnas del bloque SELECT una por línea y aun así deja líneas de 150 caracteres? Esa es una definición rota de legibilidad. Y ni siquiera voy a empezar con lo de las comas.
      En las revisiones de código, nadie mira bien las líneas largas. Ese fue el mayor problema de AngularJS. Los merges se resolvían mal y todo se rompía porque, alrededor de la columna 90, la vista se te empieza a nublar. He pasado por más de media docena de equipos con code review y siempre fue igual. Incluso siendo muy consciente de este problema e intentando evitarlo, yo también sigo cometiendo errores, aunque más o menos con la mitad de frecuencia que otros.
      Divídanlo un poco. Sobre todo si van a mostrar ejemplos a otras personas.
  • Estos son algunos consejos para lidiar con procedimientos almacenados complejos

    1. Al inicio del procedimiento, copia inmediatamente las tablas permanentes a tablas temporales, y especifica/limita/filtra solo las filas necesarias
    2. En la parte intermedia, manipula las tablas temporales según sea necesario
    3. Al final, actualiza las tablas permanentes dentro de una transacción. Si se detecta un error, revierte de inmediato la transacción y termina el procedimiento. Si sigues estos tres pasos, mejoras la concurrencia y puedes reiniciar el procedimiento sin tener que limpiar manualmente residuos de datos
    4. Hay que tener extremo cuidado al trabajar con tablas remotas. Como las tablas remotas no están dentro del RDBMS actual, es muy probable que no puedas aprovechar mucho las estadísticas ni los índices de ese RDBMS. En muchos casos, es más rápido volcar/copiar toda la tabla remota a una tabla temporal y luego trabajar con ella. Lo máximo que puedes esperar de una tabla remota es, más o menos, la ejecución de la cláusula WHERE. Si intentas hacer JOIN u operaciones complejas, es muy probable que haya timeouts
    5. Los planes de ejecución confunden fácilmente. En algunos casos, el plan de ejecución puede caer en procesamiento fila por fila y el rendimiento se queda paralizado. Muchas veces conviene dividir un procedimiento almacenado complejo en pasos pequeños que usen tablas temporales
    6. Para ver qué está haciendo realmente el RDBMS, siempre hay que revisar el plan de ejecución
    • Una vez mejoré mucho el rendimiento de una consulta revirtiendo código que aplicaba el punto 5 en una situación donde no era realmente necesario. A veces, dividir una consulta en varias consultas pequeñas es mucho más ineficiente que darle la consulta completa al optimizador de consultas y dejar que encuentre la ruta óptima
      Si haces el punto 5 sin el 6, es muy probable que no veas que estás haciendo algo no óptimo. Mi consejo es evitar la optimización prematura, escribir primero de la forma más intuitiva y optimizar solo cuando haga falta. Lo más importante es no escribir SQL de forma procedural. Se trata de describir los datos que quieres, no de ordenarle al motor cómo traerlos
    • No me gusta tener que usar montones de tablas temporales, pero a menudo me encuentro con consultas que, si se las dejas al planificador de consultas, nunca terminan. Al igual que con los compiladores, la capacidad del planificador de consultas está muy sobreestimada
      En cambio, Microsoft sigue poniendo advertencias de no intentar ajustarlo, como si el planificador de consultas siempre supiera mejor
    • Estas reglas pueden ser totalmente correctas en la DB de un proveedor, pero en otra DB las prioridades, características y trade-offs pueden ser muy distintos
      La versión de la DB también puede influir
    • Los puntos 1 a 3 están bien si puedes garantizar que el tamaño de los datos es razonable. Pero si los datos crecen demasiado para que el hardware los soporte, copiar un dataset grande y luego volver a actualizar un dataset grande puede agregar bastante overhead
  • No me gusta el desarrollo hecho “por si acaso”. Lo mismo aplica a las interfaces y a placeholders como where 1=1
    Hazlo cuando lo necesites. No lo hagas solo porque quizá algún día en el futuro lo necesites. El código de producción no es un lugar para dejar helpers de desarrollo. Durante el desarrollo puedes hacer lo que quieras, pero en código de producción la legibilidad y una intención clara son mucho más importantes

    • ¿Sueles escribir todas las referencias a nombres de tablas y columnas totalmente calificadas? Muchas veces eso mejora la legibilidad por más de un orden de magnitud, pero rápidamente se vuelve muy verboso e increíblemente tedioso de escribir
  • Una cosa más sobre los “anti joins”. Si solo quieres comprobar si existe una fila que cumpla una condición en otra tabla grande o en una subconsulta, conviene usar EXISTS en vez de IN o LEFT JOIN
    EXISTS devuelve verdadero en cuanto encuentra una coincidencia. En el caso de LEFT JOIN e IN, el motor reúne todos los resultados antes de evaluarlos

    • Esa parte me confundió un poco. En todos los casos que probé, (NOT) EXISTS generó un plan de ejecución mejor que (LEFT) JOIN o (NOT) IN, o bien el mismo plan
      Además, la intención queda más clara
  • En relación con “comenta tu código”, al menos en MSSQL se suele recomendar usar /**/ en los comentarios en lugar de --. Es porque funciones como Query Store muchas veces guardan las consultas sin saltos de línea, así que cuando traes una consulta desde ahí tienes que arreglarla toda manualmente en vez de poder usar directamente el formateador del IDE

    • Eso suena como un bug de Query Store
    • ¿Se puede castear a XML? Yo uso eso con OBJECT_DEFINITION
      select name,cast((select OBJECT_DEFINITION(object_id) for xml path('')) as xml) from sys.procedures
      Como se preservan los saltos de línea, puede ser más fácil ordenarlo. Eso sí, otros caracteres XML se rompen, como cuando > se convierte en >. Otra opción es usar VARBINARY y algo que lo vuelva a decodificar
  • Todos se alteran con la sugerencia de las comas, pero ¿les parece buena idea lo de 1=1 en la cláusula WHERE? Si veo eso en una revisión de código, no sé qué pensar del autor

    • Se puede justificar por la misma razón que las comas finales: que cambiar una sentencia WHERE no afecte otras líneas, haciendo que la revisión de código sea más fácil
      Pero si la razón es agregar condiciones dinámicas, como en este caso, donde trabajo definitivamente te despedirían
  • ¿Alguien podría compartir una guía general sobre dónde trazar la línea entre aumentar la velocidad mediante la configuración de la DB, algo casi cercano a “comprar”, y una “construcción” que en la práctica se implementa manualmente? En mi experiencia limitada, como los DBA competentes ganan mucho más y trabajan en otros lugares, este trabajo a menudo termina cayéndole al desarrollador de la app. Como se dijo arriba, es importante conocer la DB
    Un ejemplo típico son los datos que se acumulan masivamente con el tiempo y donde los datos más recientes son los que se consultan con más frecuencia. Un DBA puede mantener rápido el acceso con particionamiento o índices parciales, pero un desarrollador de apps también podría mover registros en segundo plano a tablas de archivo separadas, sin dejar de soportar funciones como la búsqueda final sobre todo el conjunto de datos. También me da la sensación de que, en casos como cuando uno está limitado por la falta de funciones de una DB en la nube, una herramienta podría automatizar bastante el trabajo inicial de dividir una tabla en varias en el momento adecuado
    Otra opción de gestión es guardar todos los blobs/archivos grandes en una base de datos separada, o en el sistema de archivos, para usar otra configuración de almacenamiento. Esto también puede ser soportado por la DB o manejarse manualmente
    En el extremo, creo que se podría llegar incluso a implementar índices por cuenta propia. Sería tener una tabla enorme con una clave primaria autoincremental y muchísimas columnas, y crear aparte una tabla con ese ID y algunas columnas buscables. Incluso se podría llegar a búsqueda de texto completo o vectores
    Un tip útil al implementar manualmente el patrón de vistas materializadas en MSSQL 2016+ es usar también el cambio de particiones. Está bien explicado e implementado en https://github.com/cajuncoding/SqlBulkHelpers?tab=readme-ov-.... Fue una pequeña biblioteca que encontré por casualidad, comercialmente la más útil para mí, pero con bajo ranking en búsquedas y pocas estrellas; está enfocada en inserciones masivas en MSSQL con .NET. Creo que es un buen ejemplo de trazar correctamente la línea entre comprar y construir mediante la automatización del cambio de particiones

  • Algo que falta: hay que dejar de usar SELECT *. Casi seguro no necesitas todo el ancho de la tabla; al hacerlo aumentas los datos que hay que filtrar y transferir, y además bloqueas una función muy buena: los semi joins

    • Hay dos grandes grupos de personas que usan SQL: analistas y desarrolladores
      Si eres desarrollador, sí. SELECT * tiene trampas, y casi siempre deberías especificar las columnas o usar un query builder que lo haga por ti
      Pero si eres analista, la vida es corta y a veces no quieres tipear todas las columnas. SELECT * está bien
  • Puede que me salga un poco del tema, pero ¿es aceptable que el administrador simplemente cierre un pull request sin ningún comentario ni discusión?
    Lo pregunto como alguien que ha contribuido, o intentado contribuir, ocasionalmente al repositorio
    Ejemplo: https://github.com/ben-n93/SQL-tips-and-tricks/pulls?q=is%3A...