Select Star SQL
Português (Brasil)

Possível inocência

Os opositores da pena de morte argumentam que o risco de executar por engano uma pessoa inocente é um preço alto demais a pagar. Neste capítulo, tentaremos estimar quantas pessoas inocentes podem ter sido executadas.

A principal ressalva é que uma alegação de inocência, mesmo feita no leito de morte, não constitui prova de inocência. Além disso, mesmo que o detento esteja dizendo a verdade, há muitas interpretações possíveis para o que significa ser inocente: o detento pode ter sido acusado de assassinar duas pessoas, mas ser inocente da morte de apenas uma; ou pode ter matado o transeunte, mas não o policial. Essas questões não são meras minúcias: no Texas, o assassinato por si só não é suficiente para justificar a pena de morte. O detento precisa ter cometido um crime passível de pena de morte, como matar um agente de segurança pública ou várias pessoas. Portanto, o detento pode ser inocente em um sentido estritamente jurídico, embora talvez não pelos padrões comuns de moralidade.

Ainda assim, há algo perturbador em alegações de inocência que persistem até a proximidade da execução, quando há pouco a ganhar. Nossa tarefa aqui é descobrir com que frequência isso acontece, calculando a proporção das últimas declarações que contêm uma alegação de inocência.


Funções de agregação

Precisamos calcular dois números para obter essa proporção:

  Numerador: Número de execuções com alegações de inocência.

  Denominador: Número total de execuções.

Até agora, cada linha no resultado vinha de uma única linha de entrada. Aqui, porém, tanto o numerador quanto o denominador exigem informações de várias linhas de entrada. Isso nos mostra que precisamos usar uma função de agregação. Agregar significa combinar vários elementos em um todo. Da mesma forma, as funções de agregação recebem várias linhas de dados e as combinam em um único número.


A função COUNT

COUNT é provavelmente a função de agregação mais usada. Como o nome sugere, ela conta coisas! Por exemplo, COUNT(<column>) retorna o número de linhas não nulas da coluna.

Como você pode perceber, a função COUNT está intrinsecamente ligada ao conceito de NULL. Vamos fazer uma pequena digressão para aprender sobre NULL.

NULLs

Em SQL, NULL é o valor de uma entrada vazia. Isso é diferente da string vazia '' e do inteiro 0, que não são considerados NULL. Para verificar se uma entrada é NULL, use IS e IS NOT em vez de = e !=.

Com isso, podemos encontrar o denominador da nossa proporção:


Variações de COUNT

Até aqui, tudo bem. Mas e se não soubermos quais colunas não contêm NULL? Pior ainda, e se nenhuma das colunas estiver livre de NULL? Certamente ainda deve haver uma maneira de descobrir o número de linhas da tabela!

A solução é COUNT(*). Isso lembra o SELECT *, em que o * representa todas as colunas. Na prática, COUNT(*) conta as linhas desde que qualquer uma de suas colunas não seja nula. Isso nos ajuda a descobrir o número de linhas das tabelas, pois uma tabela não deveria ter linhas cujas colunas fossem todas nulas.

Outra variação comum é contar um subconjunto da tabela. Por exemplo, contar as execuções no condado de Harris. Poderíamos executar SELECT COUNT(*) FROM executions WHERE county='Harris', que filtra os dados para um conjunto menor contendo as execuções de Harris e depois conta todas as linhas. Mas e se quisermos descobrir simultaneamente o número de execuções no condado de Bexar?

A solução é usar um bloco CASE WHEN, que funciona como uma grande estrutura if-else. Ele possui dois formatos, e o que eu prefiro é:

CASE
  WHEN <clause> THEN <result>
  WHEN <clause> THEN <result>
  ...
  ELSE <result>
END

Esta é, reconhecidamente, uma das partes mais complicadas do SQL. Um erro comum é esquecer o comando END e a condição ELSE, que serve como alternativa caso todas as condições anteriores sejam falsas. Lembre-se também do capítulo anterior que as condições são expressões que podem ser avaliadas como verdadeiras ou falsas. Por isso, é importante pensar no valor booleano do que quer que você coloque ali.


Prática

Vale a pena parar por um momento e pensar nas diferentes maneiras como o computador lidou com essas três consultas. Na versão com WHERE, ele primeiro filtrou os dados para uma tabela menor e depois fez a agregação, enquanto nas outras duas precisou percorrer a tabela inteira. Na versão com COUNT + CASE WHEN, ele precisou percorrê-la apenas uma vez, enquanto a versão com dois COUNT fez isso duas vezes. Portanto, embora o resultado fosse idêntico, o desempenho provavelmente foi melhor na primeira versão e pior na terceira.

Consultando a documentação

Este livro nunca teve a intenção de ser uma referência abrangente da linguagem SQL. Para isso, você terá que consultar outros recursos online. Isso também é uma habilidade por si só, e vale a pena dominá-la porque você continuará consultando a documentação anos depois de já estar familiarizado com a linguagem.

A boa notícia é que, com os modelos mentais que você aprenderá neste livro, as consultas à documentação deverão ser rápidas e fáceis, pois você estará apenas verificando detalhes, como se a função se chama AVERAGE ou AVG, em vez de tentar descobrir qual abordagem usar.

Para consultas à documentação, costumo usar o W3 Schools, o Stack Overflow e a documentação oficial do SQLite.

SELECT DISTINCT não é realmente uma função de agregação porque não retorna um único número e porque opera sobre o resultado da consulta, e não sobre a tabela subjacente. Ainda assim, incluímos aqui porque ele compartilha a característica de operar sobre várias linhas.


Uma consulta estranha

Antes de encerrarmos, vamos dar uma olhada nesta consulta:
SELECT first_name, COUNT(*) FROM executions.

Não parece estranho? Se você tem um bom modelo mental de agregações, deveria! COUNT(*) está tentando retornar uma única entrada correspondente ao número de linhas da tabela de execuções. first_name está tentando retornar uma entrada para cada linha. O computador deveria retornar uma ou várias linhas? Se retornar uma, qual first_name deveria escolher? Se retornar várias, deveria repetir o resultado de COUNT(*) em todas as linhas? Os formatos dos resultados simplesmente não são compatíveis!

Na prática, os bancos de dados tentam retornar algo razoável mesmo quando você fornece algo sem sentido. Neste caso, nosso banco de dados escolhe o primeiro nome da última entrada da tabela. Como nossa tabela está em ordem cronológica inversa, a última entrada é Charlie Brooks Jr., a primeira pessoa executada desde que a Suprema Corte suspendeu a proibição da pena de morte. Bancos de dados diferentes tratarão esse caso de maneiras diferentes, então é melhor não depender desse comportamento padrão. Se você sabe que quer a última entrada, deve encontrá-la explicitamente. Muitos dialetos SQL possuem uma função de agregação LAST, o que torna isso trivial. Infelizmente, o SQLite não possui essa função, então é necessário usar uma solução alternativa.

Dialetos SQL e bancos de dados

Embora tenhamos chamado isto de um livro sobre SQL, se quisermos ser rigorosos, ele é na verdade um livro sobre SQLite. Isso acontece porque SQL é um conceito imaginário, um ideal platônico. Na realidade, existem apenas dialetos que tentam estar em conformidade com as especificações SQL.

SQL também é pouco especificado, o que significa que algumas funcionalidades não são definidas pelos padrões. Por exemplo, os padrões não dizem se a função que encontra o comprimento de uma string deve se chamar LEN (SQL Server) ou LENGTH (SQLite), nem como identificadores como nomes de tabelas ou colunas devem ser colocados entre aspas (` no MySQL, " no SQLite).

Para piorar as coisas, até mesmo uma única consulta em um único dialeto pode ser processada de maneiras diferentes, porque os bancos de dados subjacentes podem ter arquiteturas diferentes. Por exemplo, o dialeto PostgreSQL pode ser usado em bancos de dados distribuídos por várias máquinas físicas e em bancos de dados constituídos por um único arquivo. Isso significa que os modelos mentais que desenvolvemos aqui são apenas um apoio. Eles podem não refletir exatamente o que o banco de dados está fazendo.

Escolhemos o SQLite, que é tanto um dialeto quanto uma implementação, porque ele é um dos bancos de dados mais comuns. Também tentamos nos concentrar nas funcionalidades essenciais e no modelo mental do SQL, em vez das particularidades do SQLite. Com um modelo mental sólido, é fácil alternar entre dialetos SQL e bancos de dados.


Conclusão e revisão

Vamos usar o que aprendemos até agora para concluir nossa tarefa:

Este método para encontrar alegações de inocência é reconhecidamente bastante impreciso, pois a inocência pode ser expressa por outros termos, como “não culpado”. Ainda assim, suspeito que ele subestime o número real e provavelmente esteja na ordem de grandeza correta. A questão que nos resta, então, é se estamos dispostos a aceitar a possibilidade de que até 5% das pessoas que executamos sejam, na verdade, inocentes. (Paul Graham não está.)

Para revisar, passamos de operações linha a linha, na seção anterior, para o uso de funções de agregação em várias linhas do conjunto de dados. Isso abriu caminho para estudarmos o comportamento do sistema como um todo. Na próxima seção, aprenderemos a aplicar funções de agregação a vários subgrupos do conjunto de dados usando o bloco GROUP BY.