7 pontos por GN⁺ 2024-09-26 | 2 comentários | Compartilhar no WhatsApp
  • Uma lista de hábitos de escrita em SQL e padrões de consulta usados com frequência em trabalho de análise de dados, com a ressalva de que nem tudo pode se aplicar igualmente a todos os RDBMS
  • Em termos de legibilidade, recomenda-se usar vírgulas no início da linha, WHERE 1=1, indentação, CTE, comentários e USING para tornar as consultas mais fáceis de ler e modificar
  • No processamento de dados, são mostrados exemplos práticos de sintaxes como anti-join, QUALIFY, GROUP BY ROLLUP e EXCEPT, usadas para filtrar resultados, gerar totais e verificar diferenças entre tabelas
  • Em desempenho e precisão, NOT IN com NULL, conversão implícita de tipo e conflitos de alias em campos calculados podem afetar o resultado ou a velocidade da consulta
  • Em consultas complexas, hábitos básicos como entender a ordem de execução, consultar a documentação, indicar a origem das colunas e usar nomes descritivos para consultas salvas são importantes para melhorar depuração e reutilização

Hábitos para melhorar a legibilidade ao escrever SQL

  • Este repositório é uma coletânea de dicas e truques de SQL aprendidos ao longo de vários anos, focada no que é útil no dia a dia de analistas de dados e no que teria sido bom saber ao começar a usar SQL
  • Algumas dicas podem não servir para todos os RDBMS
  • Vírgulas no início da linha e AND no início da linha

    • Recomenda-se usar vírgulas no início da linha em vez de vírgulas no fim para separar campos na cláusula SELECT
    • Fica mais claro distinguir se é uma nova coluna ou apenas uma quebra de linha no código
    • Mesmo com linhas de tamanhos diferentes, fica mais fácil encontrar vírgulas ausentes
    • Pelo mesmo motivo, também é possível colocar AND no início das condições da cláusula WHERE
  • Facilitar testes de condições com WHERE 1=1

    • Colocar a condição fictícia 1=1 na cláusula WHERE evita que a consulta quebre durante testes ao comentar condições
    • Mesmo se todas as condições forem comentadas, o 1=1 continua ali e a consulta ainda pode ser executada
  • Indentação e formatadores

    • A indentação ajuda colegas e você no futuro a lerem a consulta com mais facilidade
    • Se houver diretrizes da equipe ou da empresa, vale segui-las; caso contrário, é melhor usar um estilo que funcione para você
    • Você pode usar o formatador online poorsql ou o linter sqlfluff
  • Considere CTE em consultas complexas

    • Quando uma inline view é aninhada em 2 ou 3 níveis ou mais, a consulta tende a ficar difícil de entender quando você a revisita semanas depois
    • CTE é apresentada como uma forma de deixar consultas longas mais organizadas e de ajudar na reutilização e depuração
  • Comentários devem explicar o “porquê”

    • Depois de algum tempo, pode ser difícil lembrar por que determinado processamento foi feito
    • Em geral, comentários funcionam melhor explicando por que algo foi feito, e não apenas como o código funciona
    • O exemplo mostra um comentário em uma condição que exclui conteúdo arquivado porque o novo CMS não consegue processar o formato de vídeos arquivados
  • Use USING para joins com colunas de mesmo nome

    • Ao fazer join entre duas tabelas por colunas com o mesmo nome, usar USING pode deixar a expressão mais simples do que ON
    • USING também remove duplicidade da coluna em comum no resultado, retornando apenas uma
    • Ao usar ON, se você não indicar explicitamente a coluna em comum, pode ocorrer erro de ambiguous column name

Sintaxes úteis para processamento de dados

  • Encontrar linhas ausentes em outra tabela com anti-join

    • Anti-join é usado para retornar linhas que existem em uma tabela, mas não têm correspondência em outra
    • O exemplo trata do caso de buscar apenas video_id de conteúdos que não foram arquivados
    • Há várias formas de implementar isso
    • Fazer LEFT JOIN e depois filtrar apenas as linhas cuja chave da tabela correspondente seja NULL
    • Usar NOT IN com subconsulta
    • Usar NOT EXISTS com subconsulta correlacionada
    • Não se recomenda NOT IN, pois ele pode não funcionar como esperado por causa de valores NULL
  • Filtrar resultados de funções de janela com QUALIFY

    • QUALIFY permite filtrar o resultado da consulta com base no resultado de funções de janela
    • Isso reduz a quantidade de linhas de código, já que o filtro pode ser feito sem inline view
    • O exemplo seleciona os 10 principais mercados por produto com DENSE_RANK() e depois aplica o filtro com QUALIFY
    • Há a limitação de que QUALIFY aparentemente só está disponível em grandes data warehouses como Snowflake, Amazon Redshift e Google BigQuery
  • GROUP BY e ORDER BY por posição de coluna

    • Em vez do nome da coluna, dá para usar a posição da coluna como em GROUP BY 1 e ORDER BY 2
    • Isso pode ser útil em consultas temporárias ou pontuais
    • Para código de produção, recomenda-se sempre referenciar diretamente os nomes das colunas
  • Gerar totais com GROUP BY ROLLUP

    • GROUP BY ROLLUP pode ser usado para gerar subtotais e totais gerais
    • O exemplo calcula a soma dos salários por departamento e cria também uma linha com o total geral
    • A documentação do Transact-SQL explica que ROLLUP cria grupos para combinações de expressões de coluna e reduz o número de grupos da direita para a esquerda para gerar subtotais e total geral
    • Aplicando COALESCE, a linha de total pode ser exibida como Total
    • É preciso prestar atenção na coluna de ordenação para que a linha de total fique no fim do resultado
  • Encontrar diferenças entre dois conjuntos de resultados com EXCEPT

    • EXCEPT retorna linhas que estão no resultado da primeira consulta, mas não no da segunda
    • Usar EXCEPT junto com UNION ALL permite validar se duas tabelas contêm os mesmos dados
    • Se nenhuma linha for retornada, as duas tabelas são idênticas
    • Se houver linhas retornadas, elas são a causa da diferença

Padrões que prejudicam desempenho e precisão

  • Em colunas que podem ter NULL, NOT EXISTS é melhor que NOT IN

    • Se a coluna comparada permite NULL, NOT IN em geral pode ser mais lento que NOT EXISTS
    • Isso ocorreu em Snowflake, e o Don’t Do This da PostgreSQL Wiki diz que NOT IN (SELECT ...) não costuma ser bem otimizado
    • NOT IN também não funciona como esperado quando há NULL nos valores comparados
    • O fato de a coluna permitir NULL não significa que existam valores NULL de fato, mas ao lidar com tabelas que você não pode alterar, NOT EXISTS pode ajudar a melhorar a performance
  • Conversão implícita de tipo pode deixar a consulta lenta ou causar falhas

    • Se você colocar na condição um valor com tipo diferente do da coluna, o banco pode tentar fazer uma conversão implícita de tipo
    • O exemplo trata do caso de comparar o inteiro 200050 com a coluna video_id, que é do tipo string
    • Confiar em conversão implícita de tipo pode causar problemas
    • Se houver valores que não possam ser convertidos, pode ocorrer erro
    • A consulta pode ficar mais lenta por causa do trabalho extra de converter cada valor para o tipo especificado
    • Você pode usar o mesmo tipo de dado da coluna ou, para evitar erros, funções como TRY_TO_NUMBER do Snowflake
    • O impacto no desempenho varia conforme o tamanho do conjunto de dados processado

Erros comuns

  • NOT IN e NULL

    • NOT IN não funciona quando há NULL nos valores comparados
    • Como NULL representa Unknown, o mecanismo SQL não consegue verificar que o valor inspecionado não está na lista
    • Nesse caso, usar NOT EXISTS é uma alternativa
  • Conflito de alias em campo calculado

    • Se você der a um campo calculado o mesmo nome de uma coluna existente, pode surgir um comportamento inesperado
    • A documentação de GROUP BY do Snowflake diz que, se o nome na cláusula GROUP BY corresponder tanto ao nome da coluna quanto ao alias, o nome da coluna será usado
    • No exemplo, ao criar o alias LEFT(product, 1) AS product e usar GROUP BY product, o agrupamento acontece pela coluna product original, e não pela primeira letra, retornando 3 linhas
    • Há duas soluções
    • Usar um alias único, como product_letter
    • Especificar a expressão, como em GROUP BY LEFT(product, 1)
    • Problemas com alias também podem ocorrer em funções de janela
    • No exemplo, um CASE muda a revenue de Robot para 0, mas a alteração é aplicada depois da execução da função de janela, então o ranking sai diferente do esperado
    • Sempre que possível, use alias únicos ou coloque diretamente a expressão calculada no ORDER BY da função de janela
  • Indique a qual tabela a coluna pertence

    • Em consultas complexas com vários joins, você precisa conseguir rastrear problemas de valor até a tabela de origem
    • Quando duas tabelas compartilham o mesmo nome de coluna, não indicar a origem pode fazer o RDBMS gerar erro
    • O exemplo usa alias de tabela como vc.video_id e metadata.season para deixar clara a origem das colunas

Ordem de execução, documentação e nomes salvos

  • Entenda a ordem de execução do SQL

    • Uma das orientações mais importantes para quem está aprendendo SQL é entender a ordem de execução das cláusulas
    • Saber essa ordem pode mudar bastante a forma como você escreve consultas
    • Como material de referência, é sugerido A beginner’s guide to the true order of SQL operations
  • Leia a documentação até o fim

    • Houve um caso em Snowflake em que se usou GREATEST() para retornar a data mais recente entre várias colunas de data
    • GREATEST() retorna NULL se qualquer um dos argumentos for NULL
    • Se a documentação tivesse sido lida com mais atenção, seria possível usar GREATEST_IGNORE_NULLS() em vez de COALESCE(GREATEST(...), ...)
    • Em muitos casos, levar menos de um minuto para revisar a documentação evita o trabalho de descobrir por que algo está se comportando de forma diferente do esperado
  • Use nomes descritivos para consultas salvas

    • Para evitar a situação de não conseguir encontrar uma consulta que precisa ser executada novamente ou usada como referência, vale a pena salvá-la com um nome descritivo
    • O nome salvo normalmente inclui o tema da consulta, o mês de execução e o nome de quem fez o pedido
    • O exemplo segue o formato Lapsed users analysis - 2023-09-01 - Olivia Roberts

2 comentários

 
hiyama 2024-09-26

As vírgulas iniciais foram todas escritas como vírgulas finais neste post. No original, elas aparecem no início.

-- 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
Comentários no Hacker News
  • Minha dica adicional é esta: aprenda bem o servidor de banco de dados e verifique os planos de execução com frequência. Podem aparecer resultados inesperados, então é bom ajustar e verificar de novo
    Em geral, EXISTS é mais rápido que IN, e NOT EXISTS se comporta de forma diferente de EXCEPT no tratamento de NULL. Às vezes é muito mais rápido usar colunas de subconsulta na lista do SELECT do que fazer join de tabelas e depois filtrar linhas com algo como DISTINCT. Isso vale mesmo ao buscar mais de 10 valores da mesma tabela, e pode continuar valendo mesmo que o servidor de banco de dados suporte lateral join. Só que a subconsulta deve retornar no máximo uma linha
    Consultas que não são pontuais não devem fazer varredura completa da tabela. A varredura de tabela de hoje pode virar a indisponibilidade de amanhã, então é preciso adicionar índices. Lembre também que a cláusula GROUP BY normalmente determina o uso de índices
    Se você precisa filtrar por uma expressão, por exemplo verificar se uma substring é igual a determinado valor, pode adicionar uma coluna calculada e criar um índice nela. Alguns bancos de dados dão suporte direto a índices de expressão. Usar UNION ALL em vez de OR muitas vezes pode deixar consultas complexas ou com várias condições OR muito mais rápidas
    Quando o banco de dados não consegue escolher uma boa ordem de filtragem, também é útil fazer JOIN com uma subconsulta para forçar a ordem

    • O mais útil é aprender o próprio DBMS. Não há como fugir disso, porque cada banco de dados tem suas peculiaridades de desempenho e níveis de isolamento, além de recursos extras diferentes
      Uma coisa interessante no Postgres — e talvez outros bancos também sejam assim — é que uma operação INSERT (SELECT ...) pode ficar quase linearmente mais rápida se você fizer sharding manual de acordo com o número de núcleos de CPU. Isso funcionava mesmo com cerca de 10 joins. Primeiro, veja o EXPLAIN e encontre o join mais interno ou mais externo; depois, execute consultas paralelas separadas para cada intervalo de linhas (id >= start AND id < end). Usei muito essa abordagem num trabalho de 6 anos atrás por motivos estranhos. O Postgres 10+ adicionou paralelismo, mas, até onde sei, ainda não é tão avançado assim
    • Não sei exatamente o que significa usar “colunas” de subconsulta na lista do SELECT
      Por exemplo, se eu executar SELECT column1, (SELECT column2, column3, ... FROM table_b WHERE table_a.id = table_b.a_id) FROM table_a, recebo, como esperado, “subquery must return only one column”. A ideia é retornar várias colunas como um registro/tipo composto?
      Não ficou imediatamente claro para mim por que a cláusula GROUP BY normalmente determina o uso de índices, mas, para quem tiver curiosidade, este artigo explica passo a passo: https://www.brentozar.com/archive/2015/06/indexing-for-group...
    • Concordo. É preciso usar EXPLAIN e aprender a interpretá-lo com a ferramenta de sua preferência. Também é preciso monitorar as consultas
      Na minha startup anterior, instalamos o PgHero, e ele ajudou muito na otimização de desempenho e na definição de prioridades
    • Mesmo consultas bem projetadas muitas vezes se comportam de forma diferente do esperado. Casos típicos são estatísticas de colunas desatualizadas ou dados fragmentados em tabelas grandes. Por exemplo, inserções de chave primária aleatória
    • Não concordo com “consultas que não são pontuais não devem fazer varredura completa da tabela. A varredura de tabela de hoje pode virar a indisponibilidade de amanhã”
      Há consultas em que a varredura completa da tabela é a estratégia de acesso mais eficiente. Normalmente são consultas analíticas/de agregação que leem a tabela inteira; às vezes, até quando se busca apenas 50% de todas as linhas, a varredura de tabela é melhor.
      Também não vejo bem como uma varredura de tabela somente leitura levaria a uma indisponibilidade, já que ela não bloqueia acessos simultâneos. A única desvantagem é a carga maior de I/O; se o servidor não aguenta isso, eu diria que ele já está seriamente subdimensionado
  • Os 3 exemplos da seção “legibilidade” são estranhos. Os 2 primeiros literalmente sacrificam a legibilidade para facilitar a escrita, e o último é um monstro difícil de ler que quase nem a indentação consegue salvar

    • O formato com vírgulas no início tem vantagens além da legibilidade. Por exemplo, em sistemas de controle de versão, o formato uma linha por argumento + vírgula no início faz com que a mudança de um argumento apareça como um diff de apenas uma linha
      Acho que desenvolvedores olham o histórico de commits tanto quanto olham o código-fonte em si
    • Não é que eu adore a aparência das duas primeiras convenções, mas são convenções que pessoas que escrevem SQL de verdade realmente usam. Também dá para entender por que elas existem
      Já vi isso com frequência suficiente para não me incomodar muito mais
    • Como alternativa, dá para escrever SQL todo bagunçado como se uma criança de três anos tivesse acabado de descobrir o MSPaint, apertar o botão de “beautifier” e sair para almoçar mais cedo
    • Não sei por que você acha pior
      Não vejo problema
      E não parece haver nada errado
    • Quem é que divide as colunas do bloco SELECT em uma por linha, mas deixa linhas de 150 caracteres intactas? Isso é uma definição quebrada de legibilidade. E eu nem vou começar a falar das vírgulas
      Em revisão de código, ninguém enxerga direito linhas longas. Esse era o maior problema do AngularJS. Merges eram resolvidos errado e tudo quebrava, porque por volta da coluna 90 a visão fica turva. Já passei por mais de meia dúzia de equipes com revisão de código, e era sempre igual. Mesmo estando muito consciente desse problema e tentando evitá-lo, eu ainda erro mais ou menos metade das vezes que os outros
      Vamos quebrar um pouco essas linhas. Especialmente se for para mostrar exemplos a outras pessoas
  • Dicas para lidar com stored procedures complexas são estas

    1. No início da procedure, copie imediatamente as tabelas permanentes para tabelas temporárias e especifique/limite/filtre apenas as linhas necessárias
    2. No meio, manipule as tabelas temporárias conforme necessário
    3. No final, atualize as tabelas permanentes dentro de uma transação. Se um erro for detectado, faça rollback da transação imediatamente e encerre a procedure. Seguir esses três passos melhora a concorrência e permite reiniciar a procedure sem ter que limpar manualmente resíduos de dados
    4. Ao lidar com tabelas remotas, é preciso ter extremo cuidado. Como tabelas remotas não estão dentro do RDBMS atual, é bem provável que você quase não consiga aproveitar as estatísticas ou índices desse RDBMS. Em muitos casos, é mais rápido despejar/copiar a tabela remota inteira para uma tabela temporária e trabalhar a partir dela. O máximo que se pode esperar de uma tabela remota é algo como a execução da cláusula WHERE. Se tentar fazer JOIN ou operações complexas, há grande chance de ocorrer timeout
    5. Planos de execução confundem facilmente. Em alguns casos, o plano de execução pode cair em processamento linha a linha e a performance fica travada. Muitas vezes é melhor dividir uma stored procedure complexa em etapas menores usando tabelas temporárias
    6. Para ver o que o RDBMS realmente faz, sempre confira o plano de execução
    • Já tive um caso em que reverti um código que aplicava o item 5 sem que fosse realmente necessário, e isso melhorou bastante a performance da query. Às vezes, quebrar uma query em várias queries menores é muito mais ineficiente do que entregar a query inteira ao otimizador de consultas e deixá-lo encontrar o melhor caminho
      Se você fizer o item 5 sem o item 6, é bem provável que não perceba que está fazendo algo que não é ótimo. Meu conselho é evitar otimização prematura, escrever primeiro da forma mais intuitiva e só otimizar quando necessário. O mais importante é não escrever SQL de forma procedural. Você descreve os dados que quer, não ordena ao engine como buscá-los
    • Não gosto de ter que usar um monte de tabelas temporárias, mas encontro com frequência queries que nunca terminariam se fossem deixadas para o query planner. Assim como compiladores, a capacidade dos query planners é muito superestimada
      Por outro lado, a Microsoft continua colocando avisos para não tentar ajustá-lo, como se o query planner soubesse de tudo melhor do que ninguém
    • Essas regras podem estar completamente corretas no DB de um determinado fornecedor, mas em outros DBs as prioridades, características e trade-offs podem ser muito diferentes
      A versão do DB também pode influenciar
    • Os itens 1 a 3 são bons se você puder garantir que o tamanho dos dados é razoável. Mas, se os dados ficarem grandes demais para o hardware dar conta, copiar um dataset grande e depois atualizar novamente um dataset grande pode adicionar um overhead considerável
  • Não gosto de desenvolvimento feito “por via das dúvidas”. Isso vale para interfaces e também para placeholders como where 1=1
    Faça quando for necessário. Não faça só porque talvez um dia no futuro venha a ser necessário. Código de produção não é lugar para deixar auxiliares de desenvolvimento. Durante o desenvolvimento, faça como quiser, mas em código de produção legibilidade e intenção clara são muito mais importantes

    • Você costuma escrever todas as referências a nomes de tabelas e colunas de forma totalmente qualificada? Muitas vezes isso melhorou a legibilidade em uma ordem de grandeza ou mais, mas rapidamente fica muito verboso e incrivelmente tedioso de escrever
  • Mais uma coisa sobre “anti join”. Se você só quer verificar se existe uma linha que satisfaça a condição em outra tabela grande ou em uma subquery, é melhor usar EXISTS em vez de IN ou LEFT JOIN
    EXISTS retorna verdadeiro assim que encontra uma correspondência. No caso de LEFT JOIN e IN, o engine reúne todos os resultados antes de avaliar

    • Essa parte me deixou um pouco confuso. Em todos os casos que testei, (NOT) EXISTS produziu um plano de execução melhor que (LEFT) JOIN ou (NOT) IN, ou o mesmo plano
      Além disso, a intenção fica mais clara
  • Sobre “comente seu código”, pelo menos no MSSQL é comum recomendarem usar /**/ em vez de -- nos comentários. Isso porque recursos como o Query Store muitas vezes armazenam queries sem quebras de linha; quando você pega a query de lá, precisa corrigir tudo manualmente em vez de simplesmente usar o formatador da IDE

    • Isso soa como um bug do Query Store
    • Dá para fazer cast para XML? Eu uso isso em OBJECT_DEFINITION
      select name,cast((select OBJECT_DEFINITION(object_id) for xml path('')) as xml) from sys.procedures
      Como as quebras de linha são preservadas, pode ser mais fácil organizar. Porém outros caracteres XML se quebram, como > virando >. Outra opção é usar VARBINARY e algo que depois desfaça isso
  • Todo mundo está surtando com a sugestão das vírgulas, mas acham que 1=1 na cláusula WHERE é uma boa ideia? Quando vejo isso em code review, não sei o que pensar do autor

    • Dá para justificar pelo mesmo motivo da vírgula no final: mudanças na instrução WHERE não afetam outras linhas, então o code review fica mais fácil
      Mas, se o motivo for adicionar condições dinâmicas como neste caso, no lugar onde trabalho a pessoa certamente seria demitida
  • Alguém poderia compartilhar uma orientação geral sobre onde traçar a linha entre acelerar as coisas via configuração do DB — uma abordagem quase de “comprar” — e “construir”, que na prática é implementar manualmente? Pela minha experiência limitada, DBAs competentes recebem salários muito mais altos e trabalham em outros lugares, então esse trabalho muitas vezes acaba caindo para o desenvolvedor da aplicação. Como foi dito acima, é importante conhecer o DB
    Um exemplo típico são dados que se acumulam em grande volume com o tempo e em que os dados mais recentes são acessados com mais frequência. Um DBA pode manter o acesso rápido com particionamento ou índices parciais, mas um desenvolvedor de aplicação também pode mover registros para uma tabela de arquivo separada em segundo plano, mantendo suporte a recursos como busca final no conjunto completo de dados. Também fica a impressão de que ferramentas poderiam automatizar razoavelmente o trabalho inicial de dividir uma tabela em várias no momento adequado, como quando se está limitado pela falta de recursos de um DB em nuvem
    Outra opção de gerenciamento é armazenar todos os blobs/arquivos grandes em um banco de dados separado, ou no sistema de arquivos, para usar uma configuração de armazenamento diferente. Isso também pode ser absorvido pelo DB ou tratado manualmente
    No extremo, parece que dá até para chegar a implementar índices por conta própria. Seria ter uma tabela enorme com uma chave primária autoincremental e muitas colunas, e criar uma tabela separada com esse ID e algumas colunas pesquisáveis. Poderia até chegar a busca full-text ou vetores
    Uma dica útil ao implementar manualmente o padrão de view materializada no MSSQL 2016+ é usá-lo junto com troca de partições. Isso é bem explicado e implementado em https://github.com/cajuncoding/SqlBulkHelpers?tab=readme-ov-.... Foi uma pequena biblioteca que encontrei por acaso, a mais útil comercialmente para mim, mas com ranking de busca baixo e poucas estrelas, focada em inserções em massa no MSSQL com .NET. Acho que é um bom exemplo de traçar bem a linha entre comprar/construir por meio da automação da troca de partições

  • O que faltou: é preciso parar de usar SELECT *. Quase certamente você não precisa da largura inteira da tabela e, ao fazer isso, aumenta os dados a filtrar e transmitir, além de impedir um recurso bacana: semi-joins

    • Há basicamente dois tipos de pessoas que usam SQL: analistas e desenvolvedores
      Se você é desenvolvedor, sim. SELECT * tem armadilhas, e quase sempre você deve explicitar as colunas ou usar um query builder que faça isso por você
      Mas, se você é analista, a vida é curta, e às vezes você pode não querer digitar todas as colunas. SELECT * também é aceitável
  • Pode ser um pouco fora do tema, mas é aceitável um mantenedor simplesmente fechar pull requests sem qualquer comentário ou discussão?
    Pergunto como alguém que às vezes contribuiu, ou tentou contribuir, com o repositório
    Exemplo: https://github.com/ben-n93/SQL-tips-and-tricks/pulls?q=is%3A...