Você está na página 1de 20

Introdução à Linguagem SQL

(no dialeto do ClickHouse)


Parte 1/2
Marco Carnut <marco.carnut@coinwise.io>, 2023-03-21

Introdução
Sobre Este Tutorial
Este é o primeiro de uma série de dois documentos provendo uma introdução/revisão dos princípios
básicos e conceitos da linguagem SQL, objetivando capacitar o leitor a escrever suas próprias
consultas.
Apesar de concebido como material de suporte para o curso BlockSQL, a ser lido pelos alunos,
instrutores, monitores e convidados do curso, este documento adota uma abordagem genérica, com
exemplos próprios, distintos do resto do curso. Isso foi feito para tornar esse texto tão sucinto,
objetivo, autocontido e reaproveitável quanto possível. As particularidades do banco de dados
principal do curso serão tratadas em outro documento.
Contudo, esse texto não pretende ser um curso completo de SQL; ele vai se ater apenas ao mínimo
necessário para acompanhar o curso BlockSQL. Existem vários livros, vídeos e cursos disponíveis
que cobrem o assunto em muito mais amplitude e profundidade.
Recomendamos que o leitor execute as consultas usadas como exemplo no banco de dados e confira
pessoalmente os resultados. Entre em contato com seu instrutor ou os monitores para obter as
informações de acesso.
À época em que este documento foi escrito, estava na moda usar o ChatGPT para explicar o que as
consultas fazem (“traduzi-las para Português”) ou para escrever as consultas para nós. Como todas
as consultas feitas aqui são muito básicas, o ChatGPT não tem dificuldade em entendê-las, explicá-
las ou fazê-las. Por essa mesma razão, as explicações dele não nos pareceram muito melhores do
que as que já estão contidas nesse documento.

Sobre a Linguagem SQL


SQL é a sigla de “Structured Query Language”, ou Linguagem Estruturada de Consultas. Para
entender o significado da palavra “estruturada” nessa siga, é preciso saber que o nome original era
“SEQUEL”, cujo significado, “Structured English Query Language”, ou “Linguagem de Consultas
em Inglês Estruturado”, denuncia o objetivo do projeto: fazer com que as consultas na linguagem
parecesse com frases comuns na língua inglesa.

1/20
Todavia, como a linguagem evoluiu em direções muito além desse objetivo, a tentativa de fazê-la
resultar em “frases legíveis em inglês” ficou em segundo plano. Mesmo assim, veremos que muitos
comandos e cláusulas da linguagem são, de fato, trechos de frases em inglês, quase sempre
começando com um verbo no imperativo e pensando no resto da frase como um monte de adjetivos
e conectivos.
Mas é por causa dessa história que, até hoje, muitos falantes nativos da língua inglesa pronunciam
“SQL” como “síquel”, ao invés das letras individuais da sigla: “éss-kíu-él”. Os brasileiros
pronunciam “ésse-quê-éli” mesmo.
A linguagem SQL é uma das mais antigas, tradicionais e bem estabelecidas da informática – a
primeira versão apareceu em 1974 e foi padronizada em 1986 pela ANSI (a equivalente americana
à nossa ABNT) e pela ISO (Organização Internacional de Padrões) no ano seguinte.
A linguagem SQL não é uma “linguagem de programação completa” ao estilo das linguagens mais
populares, como JavaScript, Python, C, C++, Flutter, Rust, etc. Isso tem a vantagem de torná-la
muitíssimo mais fácil de aprender e dominar. Em particular, a linguagem SQL descreve o que você
quer que seja feito, sem explicitar como será feito – descobrir “como fazer” é exatamente o trabalho
do sistema de banco de dados. Mesmo assim, a desenvoltura com a linguagem SQL não só é muito
importante para diversos trabalhos no mundo da informática, mas também é um excelente passo
intermediário para quem quer aprender a programar.
Dominar a linguagem, porém, é apenas um dos conhecimentos que se precisa. É também essencial
conhecer a estrutura dos dados, seus significados, suas origens, quem os fornece e quem os usa.
Fazendo uma analogia, dominar SQL é como aprender a dirigir; conhecer os dados é como
conhecer as ruas, caminhos e lugares de uma cidade. É perfeitamente possível saber dirigir e não
conseguir chegar a lugar nenhum porque não se conhece os caminhos; e é perfeitamente possível
conhecer os caminhos sem saber dirigir. Os melhores resultados são obtidos quando se combina os
dois tipos de conhecimento.

Sobre o Dialeto e o SGBD


A linguagem SQL tem diversas variações ou dialetos, semelhantes no geral mas diversas sutis
diferenças entre si.
Este tutorial foi feito para o dialeto específico do ClickHouse, o Sistema Gerenciador de Banco de
Dados (SGBD) que será utilizado no curso BlockSQL. Esse dialeto é bem parecido com o “SQL
padrão”, e muito do que você aprender aqui funcionará em outros bancos de dados também, como o
PostgreSQL, MariaDB (anteriormente conhecido como MySQL), sqlite, Microsoft SQL Server,
Oracle, entre outros. Em vários pontos ao longo deste documento você encontrará alguns apartes
discutindo algumas diferenças entre o dialeto do ClickHouse e de outros sistemas.

2/20
Conceitos Básicos
A unidade básica de armazenamento e organização das informações em um banco de dados
relacional chama-se tabela. O termo “relação” é um termo antigo para “tabela” – é por isso que esse
tipo de banco de dados se chama “relacional”: nesse contexto, “relacional” quer dizer “baseado em
tabelas”. Existem outros tipos de bancos de dados baseados em outros princípios.
As tabelas são arranjos regulares de informações em linhas e colunas. O termo “registro” é um
sinônimo antigo, mas ainda muito em uso, para “linha”; e o termo “campo” é um termo antigo, mas
ainda muito em uso, para “coluna”. Cada campo tem um nome para que possamos nos referir a cada
uma delas individualmente. Cada “retângulozinho” nos pontos de encontro entre as linhas e colunas
chama-se célula, e cada célula tem dentro de si um valor ou conteúdo. A figura abaixo provê um
exemplo de como se parece visualmente uma tabela contendo dados sobre países:

Tabela “Countries”
cabeçalho
code name capital population area com nomes
BR Brasil Brasília 217240060 8515767 das colunas
AR Argentina Buenos Aires 46044703 2780400
CL Chile Santiago 18430408 756096 linhas

colunas

As tabelas podem ser de vários tipos, como as tabelas materializadas, que têm nome (como a
tabela “Countries” do exemplo acima), armazenam dados, ocupam espaço no HD ou SSD do
computador onde o banco de dados está hospedado, são feitas para durar indefinidamente e cujos
dados servem de origem para consultas posteriores; ou as tabelas efêmeras, que só existem por um
curto período de tempo e depois são descartadas.

Consultas Básicas
Digite a seguinte consulta e a execute, clicando no botão “Play”:
SELECT 1

(colocamos as palavras estruturais da linguagem SQL em negrito só para facilitar a leitura; digite-as
normalmente.)
O resultado será uma tabela como a abaixo:
1
1

A primeira linha na tabela acima é o cabeçalho com os nomes dos campos. Esse “1” que aparece
nessa primeira linha é o nome do campo. Os nomes dos campos normalmente são textos bem curtos

3/20
descrevendo seus significados, mas, na realidade, podem ser qualquer coisa, inclusive números,
como no exemplo acima.
Quando você não explicita o nome do campo, o ClickHouse inventa um a partir do contexto. Nesse
caso, como o único contexto que ela tinha era o número “1” que você digitou na consulta, ele usou
isso como nome de campo.
O resultado das consultas aparece nas linhas após a primeira. No caso dessa consulta, o resultado
tem apenas uma linha e uma coluna, e o seu conteúdo é “1”.
As tabelas que resultam das consultas são efêmeras (seu conteúdo não fica guardado e é descartado
logo depois) e anônimas (elas não têm nome).

Dando Nomes às Colunas


Experimente a seguinte consulta abaixo:
SELECT 1 AS valor

O resultado será, novamente, uma tabela de uma única linha e uma única coluna:
valor
1

Mas, como podemos ver, a coluna recebeu o nome que especificamos após a cláusula “AS” (a
palavra “como”, em inglês). Esse nome da coluna é chamado de alias (a pronúncia correta em
inglês é “êiliéss”, mas os brasileiros frequentemente dizem “álias”, “aliás”, ou “alías”.)
Tecnicamente, a cláusula “AS” é opcional – ela pode ser omitida:
SELECT 10 valor

valor
10

Se o nome do campo for uma única palava (isto é, não contiver espaços), começar com uma letra e
não tiver acentos ou sinais de pontuação, pode-se escrevê-lo diretamente, como fizemos acima.
Contudo, podemos ter nomes de campos quaisquer, se colocarmos seus nomes dentro de aspas
duplas, como no exemplo abaixo:
SELECT 1 AS "Valor Resultante"

O resultado será:
Valor Resultante
1

Podemos até mesmo ter nomes de campos contendo o próprio caractere “aspas duplas”: para isso,
devemos grafar duas aspas duplas, para que o sistema possa diferenciar o início e o fim do nome
pelas aspas sozinhas, e onde queremos aspas através das aspas duplas em dobro:

4/20
SELECT 1 AS "Valor ""especial"" resultante"

O resultado será:
Valor "especial" resultante
1

Dar bons nomes aos campos é uma arte: o ideal é ser sucinto, preferindo nomes curtos que
economizem digitação; mas é igualmente importante dar nomes que comuniquem o significado da
informação da forma mais clara possível. Neste curso, além desses princípio gerais, vamos adotar
os seguintes convenções:
• expressões em inglês (preferivelmente uma palavra só) descrevendo sucintamente que
informação o campo guarda – isso ajuda quando se trabalha com equipes internacionais;
• sempre em letras minúsculas;
• se forem mais de uma palavra, colocaremos o caractere de sublinhado (“_”, chamado de
“underline”, em inglês) onde se colocaria o espaço – isso nos permite evitar usar aspas duplas;

Expressões Numéricas, Operadores e Funções


A linguagem SQL é capaz de fazer cálculos. Por exemplo, a consulta:
SELECT 1+1

Resultará na seguinte tabela:


plus(1, 1)
2

As operações aritméticas que aprendemos na escola são representadas pelos seguintes símbolos:

Símbolo Operação Função correspondente


+ Soma plus
- Subtração minus
* Multiplicação multiply
/ Divisão divide
% Módulo (Resto da Divisão) mod

Os sinais de pontuação tradicionais são uma conveniência que o ClickHouse nos oferece para
escrevermos as expressões da forma que estamos acostumados. Mas, internamente, ele converte
todas as expressões para a chamada forma funcional, em que todas as operações são transformadas
em chamadas para funções, que são operações pré-determinadas e que têm um nome. Para “chamar
uma função” (ou seja, pedir que ela calcule algo), você deve colocar cada um dos dados que ela
precisa separados por vírgulas e englobados entre parênteses. Então, a na forma funcional, a
operação 1+1 acima fica:

5/20
plus(1,1)

De fato, quando você escreve uma expressão e não especifica um nome para seu respectivo campo,
o ClickHouse usa a forma funcional como nome do campo.
➢ Cada banco de dados tem seu jeito próprio de dar nomes automáticos aos campos: o
PostgreSQL, por exemplo, usa ?column? quando a consulta não especifica um nome, e o
MariaDB usa a própria expressão que deu origem ao campo.
Pode-se perfeitamente digitar plus(1,1) diretamente na consulta:
SELECT plus(1,1)

Como você pode imaginar, o resultado dá o mesmo:


plus(1, 1)
2

Operações com vários termos são perfeitamente aceitas e se tornam funções aninhadas:
SELECT 1+2+3

Resulta em:
plus(plus(1, 2), 3)
6

Repare não só no resultado correto, o número seis, mas também na forma funcional da expressão
que apareceu como nome do campo: a função plus foi usada duas vezes e os parênteses estão
aninhados (parêntese dentro de parêntese).
Sob essa interpretação, o primeiro argumento da primeira função plus é outra função plus com
argumentos um e dois; e o segundo argumento da primeira função plus é o número três. Note ainda
o posicionamento dos parênteses: há um abre-parêntese sempre imediatamente após o nome da
função e um fecha parêntese após o último argumento.
Tal como aprendemos no colégio, as expressões entre parênteses mais internos são executadas
primeiro. No exemplo acima, a primeira coisa a ser calculada é o plus(1,2), que resulta em 3. O
resultado ocupa o lugar de onde o plus(1,2) estava, então a expressão inteira se reduz a
plus(3,3), cujo resultado é seis.

Eis outro exemplo onde os parênteses se comportam como aprendemos na escola: calcular a média
entre 10 e 8:
SELECT (10+8)/2

O resultado é:
divide(plus(10, 8), 2)
9

6/20
Precisamos colocar o parêntese para que a soma fosse executada antes da divisão, pois, tal como
aprendemos na matemática, normalmente as divisões e multiplicações são executadas primeiro. Isso
fica bem claro se experimentarmos não colocar os parênteses:
SELECT 10+8/2

Isso resulta na divisão ser executada antes da soma:


plus(10, divide(8, 2))
14

Se nossa intenção era calcular a média, o resultado acima estaria errado. Mas, uma das vantagens da
forma funcional é que ela deixa muito clara a ordem exata em que as funções são aplicadas.
No ClickHouse, quase tudo é feito através de funções. Muitas funções têm operadores equivalentes,
tal como o operador + (sinal de mais) é equivalente à função plus, o * (asterisco) é a operação de
multiplicação e equivalente à função multiply, etc. Existem vários outros operadores que vamos
conhecer mais adiante, e todo operador tem uma função correspondente.
O contrário, porém, não é verdade: nem toda função tem um operador correspondente; há muitos
recursos que só são disponibilizados na forma de funções.
No ClickHouse, a operação de divisão pode dar resultados fracionários mesmo se os dois operandos
forem inteiros.
SELECT 5/2

divide(5, 2)
2.5

➢ No PostgreSQL, a divisão dá um resultado inteiro se os dois operandos forem inteiros, com o


resultado arredondado para baixo. Mas, se pelo menos um dos operandos tiver dígitos
decimais, a conta é feita com números fracionários.
Uma coisa em que as expressões numéricas são diferentes do que aprendemos no colégio é que não
usamos colchetes ou chaves, só parênteses (aninhados, se necessário). Então, a expressão
matemática abaixo:
5+7
(2+1 ) ×2
Em SQL, se torna:
SELECT (5+7)/((2+1)*2)

divide(plus(5, 7), multiply(plus(2, 1), 2))


14

Note o uso dos parênteses aninhados. Em SQL, os símbolos de colchetes e chaves tem outros
significados que veremos mais adiante.

7/20
Note que, para uma expressão ser válida, os parênteses devem estar balanceados: deve haver a
mesma quantidade de “fecha parênteses” que de “abre parênteses”. Se a houver mais parênteses
abrindo do que fechando ou o contrário, você receberá uma mensagem de erro tipo
“SYNTAX_ERROR”.

Expressões Textuais
Além de números, os valores podem ser textos. Dentro de uma consulta SQL, os textos são trechos
entre aspas simples:
SELECT 'Alô Mundo!'

'Alô Mundo!'
Alô Mundo

Novamente, a primeira linha acima é a linha de cabeçalhos, indicando que o nome do campo é . A
segunda linha é o resultado em si, que é o texto, sem as aspas simples.
Tal como antes, podemos dar os nomes que quisermos aos campos:
SELECT 'Alô Mundo!' AS "Saudação"

Saudação
Alô Mundo

O exemplo acima ainda serve para ilustrar a diferença entre as aspas simples e aspas duplas:
• Aspas Simples: delimitam valores textuais. Em Ciência da Computação, valores textuais são
chamados de “strings” (abreviatura de “character string” ou “cadeia de caracteres”). Atente que
o caractere “aspas simples” não é o mesmo que o acento agudo; nos teclados padrão ABNT2
usados aqui no Brasil, ele fica à esquerda do numeral 1.
Há que use o termo “apóstrofo” como sinônimo para “aspas simples”, mas, na realidade, são
dois caracteres diferentes: os apóstrofos são curvados, e as aspas simples são retas.
• Aspas Duplas: delimitam nomes de campos ou nomes de tabelas. Nos teclados ABNT2 usados
aqui no Brasil, a aspas dupla também fica à esquerda do numeral 1, mas requer o uso simultâneo
da tecla SHIFT.
Atente que alguns editores de texto, como o Google Docs, LibreOffice Writer e o Microsoft
Word trocam as aspas duplas " (note que os tracinhos são retos) por aspas tipográficas “ ” (abre
e fecha aspas, respectivamente; note como elas são curvadas) ao digitar. É preciso dar o
comando de desfazer (teclas CTRL-Z) imediatamente após digitá-la para o editor de texto
reverter para as aspas retas (isso também funciona com as aspas simples).
Um sinônimo popular para as aspas duplas são as aspas invertidas: ` (no teclado ABNT2, as
aspas invertidas é feita pressionando SHIFT + acento agudo, para fazer uma acento grave, e
imediatamente em seguida pressionando a barra de espaço).

8/20
Confundir o uso das aspas simples com aspas duplas é considerado um erro amador embaraçoso!
(embora até os profissionais se confundem às vezes, pois diferentes linguagens de programação têm
diferentes significados para os vários tipos de aspas).
Caso o próprio texto que queiramos utilizar contenha aspas simples, podemos especificá-la usando
duas aspas simples (que, dependendo da fonte e do zoom da tela, podem acabar ficando parecidas
com uma aspas dupla!):
SELECT 'Pingo d''água' AS texto

texto
Pingo d'água

Perceba que a primeira e a última aspas delimitam o início e o fim do texto, e as aspas simples
repetidas (que não é a mesma coisa que aspas duplas!) fazem as vezes de uma única aspas simples
dentro do texto.
Os textos são considerados valores como outros quaisquer, e é possível fazer cálculos e expressões
com eles. Por exemplo, o operador || (duas barras verticais; também chamado de “pipe pipe” –
pronuncia-se “paipe paipe”) realiza uma operação chamada concatenação, que consiste em
emendar dois textos um seguido do outro:
SELECT 'Recife,' || 'Brasil' AS texto

texto
Recife,Brasil

No exemplo acima, note que não há espaço entre a vírgula e o “Brasil”. O operador de concatenação
não acrescenta nenhum caractere que não tenha sido explicitamente colocado; por isso, se
quisermos que não fique “grurado”, temos de colocar explicitamente o espaço:
SELECT 'Recife,' || ' Brasil' AS texto

texto
Recife,Brasil

Vale aqui uma advertência importante: os navegadores (Chrome, Firefox, etc.) colapsam múltiplos
espaços seguidos em um só ao exibir textos. Por exemplo, se você executar a consulta abaixo:
SELECT 'Curso ' || ' BlockSQL' AS texto

O resultado correto terá dois espaços (um após o “o” final da palavra “Curso” e outro antes do “B”
inicial da palavra “BlockSQL”). Mas, se você selecionar o texto na tela do navegador e copiá-lo,
digamos, para o Bloco de Notas, vai aparecer apenas um espaço entre as duas palavras. Esse é um
“erro” causado pelo navegador, não pelo banco de dados.
Experimente refazer as consultas acima sem definir o nome do campo, de forma que o ClickHouse
lhe mostre a forma funcional – você verá que o nome da função que corresponde ao operador || é
concat.

9/20
Múltiplas Colunas
Pode-se criar uma tabela com vários colunas separando-as por vírgulas:
SELECT 10 AS "Número", 'Tempus Fugit' AS "Citação", 3+3 AS soma

Número Citação soma


10 Tempus Fugit 6

Observe que a ordem das colunas na tabela resultante é a mesma em que elas são listadas na
consulta. Então, por exemplo, se quiséssemos que a citação aparecesse primeiro, bsataria colocá-la
antes dos demais campos:
SELECT 'Tempus Fugit' AS "Citação", 10 AS "Número", 3+3 AS soma

Citação Número soma


Tempus Fugit 10 6

Note que o último campo é o único que não é seguido de vírgula e é isso que indica que a lista de
campos acabou. Sempre que houver uma vírgula após a descrição de um campo, o ClickHouse
entenderá que há outro campo a seguir.
Um erro muito comum oriundo disso é, ao copiar-e-colar para rearranjar a ordem dos campos,
deixar uma vírgula sobrando no final:
SELECT 1 AS campo1, 2 AS campo2,

Isso causará uma mensagem de erro do tipo “SYNTAX_ERROR”:


Code: 62. DB::Exception: Syntax error: failed at position 33 (end of
query): . Expected one of: expression with optional alias, element of
expression with optional alias, lambda expression, end of query.
(SYNTAX_ERROR)

Outro erro comum é colocar duas vírgulas seguidas:


SELECT 1 teste,, 2 algo

Isso também resulta em um erro parecido com o anterior.

Obtendo dados de uma tabela


Nos exemplos que fizemos até então, os resultados sempre vinham diretamente dos valores que
digitávamos nas próprias consultas. Para obtermos valores que venham das tabelas, precisamos
mencionar os nomes dos campos que desejamos após a cláusula SELECT e acrescentar a cláusula
FROM para dizer o nome da tabela de onde queremos buscar esses dados. Por exemplo:

SELECT name, population FROM "Countries"

10/20
name population
Brasil 217240060
Chile 18430408
Argentina 46044703

Quando você especifica o nome de uma tabela na cláusula FROM, o banco de dados vai de linha em
linha daquela tabela e gera uma nova tabela com os campos que você pediu na seção entre o SELECT
e o FROM. No exemplo, colocamos o nome entre aspas, mas, como ele não tem espaços nem acentos,
omitiremos as aspas daqui em diante.
É possível pedir todos os campos da tabela sem explicitar seus nomes usando o caractere
“asterisco”, que vale como um “curinga”, como no exemplo abaixo:
SELECT * FROM Countries

Isso resulta na exibição da tabela completa, incluindo todos as linhas e todos as colunas:
code name capital population area
AR Argentina Buenos Aires 46044703 2780400
BR Brasil Brasília 217240060 8515767
CL Chile Santiago 18430408 756096

Experimente repetir essa consulta algumas vezes. Você notará que os dados podem não vir sempre
na mesma ordem, mas todas as linhas da tabela sempre virão.
Os campos de uma consulta que são obtidos da mera citação direta do nome de uma tabela, ou
através do asterisco, como fizemos nos exemplos acima, são chamados de campos literais.
Podemos ter também os chamados campos calculados, que usam as expressões numéricas e de
textos para calcular resultados oriundos de um ou mais campos literais da tabela. Por exemplo, se
quisermos ter uma lista com as densidades populacionais dos países, poderíamos fazer uma consulta
assim:
SELECT
name || ' (' || code || ')' AS country,
population/area AS population_density
FROM Countries

Para tornar a consulta mais fácil de ler, nós a partirmos em várias linhas, e acrescentamos espaços
extras para alinhar as cláusulas “AS” de modo a ficarem umas embaixo das outras. Isso facilita ler
os nomes dos campos e suas definições. Você pode digitar (ou copiar-e-colar) a consulta assim
mesmo: o banco de dados não se incomoda com espaços ou quebras de linha extras.
Recomendamos que você adote essa prática, pois é muito comum fazer consultas extensas que
ficam muito ruins de ler se colocadas em uma linha só.
O resultado dessa consulta é:

11/20
country population_density
Brasil (BR) 25.51033394877995
Chile (CL) 24.375751227357373
Argentina (AR) 16.560460005754567

Note como usamos uma expressão textual para colocar o código do país entre parênteses no
resultado final, separado do nome por um espaço. E note também como usamos uma expressão
numérica para dividir a população do país por sua área para obter a densidade populacional.
Também é perfeitamente possível misturar campos literais com calculados.
O banco de dados não tem noção das unidades de medida que utilizamos. Em lugar nenhum está
dito, por exemplo, que a área está em quilômetros quadrados e que a população está em habitantes.
Tem gente que gosta de acrescentar essa informação no nome dos campos:
SELECT
name || ' (' || code || ')' AS country_and_code,
population/area AS population_density_in_square_km
FROM Countries

country_and_code population_density_in_square_km
Brasil (BR) 25.51033394877995
Chile (CL) 24.375751227357373
Argentina (AR) 16.560460005754567

O banco de dados também não tem noção do significado dos dados, nem do contexto do que o
usuário quer saber, ou do problema que quer resolver; e não vai entender, muito menos questionar,
se o que o usuário pediu faz sentido. Por exemplo, se, ao invés de dividir a população pela área,
fosse feito uma soma, o banco de dados executaria a consulta sem problema nenhum, mas o
resultado seria totalmente espúrio. A função do analista de dados é justamente entender o contexto,
os dados, fazer as perguntas e contas certas, bem como conferir e criticar os próprios resultados para
ver se fazem sentido.

Restringindo por Critérios


As consultas que fizemos até agora sempre mostram todas as linhas de uma tabela. Podemos,
porém, exibir apenas as linhas que satisfaçam um determinado critério. Por exemplo, a consulta
abaixo usa-se a cláusula WHERE (“onde”, em inglês) para exibir apenas os países que tiverem uma
área maior que um milhão de quilômetros quadrados.
SELECT * FROM Countries WHERE area > 1000000

code name capital population area


BR Brasil Brasília 217240060 8515767
AR Argentina Buenos Aires 46044703 2780400

O que aconteceu aqui é que, para cada linha, a condição especificada no WHERE é testada com os
valores das colunas daquela linha. Ao passar pela linha que continha os dados do Brasil, o banco

12/20
de dados viu que o valor do campo area satisfazia a condição de ser maior que um milhão. O
mesmo aconteceu para a linha seguinte, com os dados da Argentina. Contudo, ao chegar na linha
com os dados do Chile, a condição não era verdadeira, então essa linha foi omitida do resultado
(embora continue intacta na tabela Countries original).
É perfeitamente possível definir critérios baseados em campos calculados, como o exemplo abaixo
mostra:
SELECT
name || ' (' || code || ')' AS country_and_code,
population/area AS population_density_in_square_km
FROM Countries
WHERE population_density_in_square_km < 20

country_and_code population_density_in_square_km
Argentina (AR) 16.560460005754567

É também perfeitamente possível especificar uma condição que nenhuma linha satisfaça, resultando
em uma tabela vazia (sem nenhuma linha de conteúdo, a não ser pelo cabeçalho):
SELECT * FROM Countries WHERE code = 'XX'

code name capital population area

Um resultado vazio pode ser exatamente o que se deseja – por exemplo, quando estamos
interessados em saber que um certo caso não ocorre. Às vezes um resultado vazio pode ser
inesperado e nos assustar momentaneamente, mas cabe a nós avaliar se a consulta está certa e o
resultado vazio faz sentido, ou se o resultado veio vazio porque erramos algo na consulta.
Para especificar condições, usamos os chamados operadores relacionais:

Operador Significado Função correspondente


= Igual a equals
<> Diferente de notEquals
!= Diferente de notEquals
> Maior que greater
>= Maior ou igual a greaterOrEquals
< Menor que less
<= Menor ou igual a lessOrEquals

Observe que a operação “diferente de” tem duas grafias. A primeira é mais tradicional, e a segunda
foi criada porque quase todas as linguagens de programação usam dessa forma.
Os operadores relacionais retornam o número zero se a condição for falsa ou o número um se a
condição for verdadeira. O ClickHouse até tem as palavras-chave true e false, mas elas são
apenas apelidos para os valores numéricos um e zero, respectivamente.
Alguns exemplos:

13/20
SELECT 5 > 3

greater(5, 3)
1

O fato de ter retornado 1 (“true”, ou “verdadeiro”, em Português) deve ser interpretado como: “sim,
cinco é realmente maior que três”.
SELECT 5 < 3

less(5, 3)
0

O fato de ter retornado zero (“false”, ou “falso”, em Português) deve ser interpretado como: “não,
cinco não é menor que três”.
Como vimos acima, os operadores relacionais funcionam não só pra números, mas para textos,
também:
SELECT 'X' || 'Y' = 'XY'

equals(concat('X', 'Y'), 'XY')


1

Para dois textos serem considerados iguais, os textos têm de ser idênticos letra a letra: eles têm de
ter exatamente a mesma quantidade de caracteres e exatamente os mesmos caracteres, nas mesmas
posições. É o que aconteceu no exemplo acima: o resultado da concatenação de ‘X’ com ‘Y’ é
‘XY’, que é idêntico ao ‘XY’ do lado direito da igualdade.
Note que letras maiúsculas são consideradas distintas das letras minúsculas:
SELECT 'ab' = 'AB'

equals('ab', 'AB')
0

Caracteres acentuados também são considerados diferentes das suas versões com acentos:
SELECT 'c' ='ç'

equals('c', 'ç')
0

Apesar de serem “invisíveis”, espaços também contam como caracteres:


SELECT 'espaço no fim? ' == 'espaço no fim?'

equals('espaço no fim? ', 'espaço no fim?')


0

Um texto é considerado maior que o outro se o caractere na mesma posição nos dois textos vem um
depois após o outro na tabela Unicode – ela é grande demais para incluir neste texto, mas, pelo

14/20
menos para os caracteres mais comuns, dá pra sintetizar na tabela abaixo (o primeiro elemento da
primeira linha é o caractere “espaço”):
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 

Se o caractere for idêntico, o desempate é feito aplicando esse critério ao próximo caractere. Se esse
era o último caractere, o texto mais longo é considerado o maior. Se os dois textos são do mesmo
tamanho e têm os mesmos caracteres em todas as posições, eles são considerados iguais, como já
visto acima. Exemplo:
SELECT 'B' > 'AB'

greater('B', 'AB')
1

Outro exemplo:
SELECT 'ABA' > 'AB'

greater('ABA', 'AB')
1

Um detalhe curioso na tabela acima é que as letras maiúsculas são consideradas “menores” que as
minúsculas:
SELECT 'A' < 'a'

less('A', 'a')
1

E os digitos numéricos são considerados menores que as letras maiúsculas:


SELECT '9' < 'A'

less('9', 'A')
1

Às vezes precisamos especificar múltiplas condições. Nesse caso, precisamos conectá-las usando os
chamados operadores lógicos:

Operador Função correspondente Significado


c1 AND c2 and Verdadeiro apenas se as condições c1 e c2 forem
simultaneamente verdadeiras, falso caso contrário
c1 OR c2 or Falso apenas se as condições c1 e c2 forem
simultaneamente falsas, verdadeiro caso contrário
NOT c not Inverte o sentido: verdadeiro se a condição c for falsa,
falso se a condição c for verdadeira.
Exemplo:

15/20
SELECT * FROM Countries WHERE area >= 1000000 AND population >= 100000000

code name capital population area


BR Brasil Brasília 217240060 8515767

Dentre os apenas três países que constam na tabela, só o Brasil satisfaz simultaneamente as duas
condições: ter mais de um milhão de quilômetros quadrados e mais de cem milhões de habitantes. A
Argentina até satisfaz a primeira condição, mas não a segunda; e o Chile não satisfaz nenhuma das
duas, e, por isso, não aparece.
Trocando o AND por OR, teríamos:
SELECT * FROM Countries WHERE area >= 1000000 OR population >= 100000000

code name capital population area


BR Brasil Brasília 217240060 8515767
AR Argentina Buenos Aires 46044703 2780400

Nesse caso, basta o país satisfazer qualquer uma das condições para ser incluso; para ficar de fora,
ele precisa não satisfazer nenhuma das condições, que é o que acontece com o Chile.

Impondo Ordem Específica


Podemos pedir ao banco de dados que nos entregue as informações em uma ordem específica
através da cláusula ORDER BY, seguido do nome do campo que desejamos ordenar, e das cláusulas
ASC (“ascendente”, ou seja, do menor para o maior) ou DESC (“descendente”, ou, em outras
palavras, do maior para o menor). No exemplo abaixo, pedimos a lista de países em ordem
ascendente de área:
SELECT * FROM Countries ORDER BY area ASC

code name capital population area


CL Chile Santiago 18430408 756096
AR Argentina Buenos Aires 46044703 2780400
BR Brasil Brasília 217240060 8515767

Já o exemplo abaixo nos dá a lista de países do mais populoso para o menos populoso:
SELECT * FROM Countries ORDER BY population DESC

code name capital population area


BR Brasil Brasília 217240060 8515767
AR Argentina Buenos Aires 46044703 2780400
CL Chile Santiago 18430408 756096

E a consulta abaixo nos dá os países com os nomes em ordem alfabética (o SQL assume “ ASC” se o
omitirmos, como fizemos abaixo):
SELECT * FROM Countries ORDER BY name

16/20
code name capital population area
AR Argentina Buenos Aires 46044703 2780400
BR Brasil Brasília 217240060 8515767
CL Chile Santiago 18430408 756096

Pode-se usar mais de um critério de ordenação separando-os por vírgulas:


SELECT * FROM Countries ORDER BY name ASC, population DESC

O critério de ordenação secundário (em ordem decrescente de população, nesse exemplo) seria
acionado caso houvesse algum empate no critério primário (dois países com o mesmo nome). Nessa
tabela de exemplo, isso não faria diferença porque não há dois países com o mesmo nome.
É perfeitamente possível combinar WHERE com ORDER BY, lembrando apenas que o WHERE vem logo
após o FROM e o ORDER BY vem por último:
SELECT * FROM Countries WHERE area < 3000000 ORDER BY name

code name capital population area


AR Argentina Buenos Aires 46044703 2780400
CL Chile Santiago 18430408 756096

Limitando a Quantidade Total de Linhas


É muito comum as tabelas serem muito extensas, contendo milhões ou mesmo bilhões de
elementos. Nós, seres humanos, raramente temos paciência para ler muito mais que algumas
dezenas de linhas por vez. Por isso, é muito comum partir o resultado de uma consulta em
“páginas” com uma certa quantidade de elementos por vez. Para isso, existem as cláusulas LIMIT e
OFFSET.

A cláusula LIMIT encerra a consulta assim que a quantidade de linhas atinge a quantidade de
elementos desejada. Por exemplo, a consulta abaixo só mostrará apenas uma linha:
SELECT * FROM Countries LIMIT 1

code name capital population area


AR Argentina Buenos Aires 46044703 2780400

Mas lembre-se que, a menos que explicitamente imponhamos uma ordem, a linha exata que vai vir
acima pode variar (ou não! – se a tabela for pequena, quase sempre o ClickHouse começa sempre
no mesmo ponto; além disso, algumas tabelas do ClickHouse são estruturadas de uma forma que
impõe ordem naturalmente – o comportamento exato depende do tipo de tabela, como ela foi criada
e que índices ela tem.)
A cláusula OFFSET pula uma certa quantidade de linhas do início dos resultados. Por exemplo, a
consulta abaixo pula a primeira linha e limita o resultado total apenas a uma linha, resultado em
obter a segunda linha da tabela:
SELECT * FROM Countries LIMIT 1 OFFSET 1

17/20
code name capital population area
BR Brasil Brasília 217240060 8515767

Pode-se perfeitamente combinar a clásula LIMIT com ORDER BY e WHERE, lembrando que o WHERE
fica logo após o FROM, o OFFSET fica por último, o LIMIT fica antes do OFFSET e o ORDER BY fica
antes do LIMIT. O ClickHouse emite um erro de sintaxe se essa ordem não for respeitada.

Funções Comuns versus Funções de Agregação


Normalmente, quando você especifica uma função em uma consulta SQL, os nomes dos campos
dentro da expressão entre parênteses se refere apenas o valor daquele campo na linha atual. Por
exemplo, a consulta abaixo usa a função lengthUTF8 para contar quantos caracteres o nome de
cada capital tem:
SELECT capital, lengthUTF8(capital) FROM Countries

capital lengthUTF8(capital)
Buenos Aires 12
Brasília 8
Santiago 8

(usamos a função lengthUTF8 ela mede em o comprimento em caracteres segundo as regras da


codificação UTF8 do padrão Unicode, onde as letras acentuadas requerem dois bytes. Se tivéssemos
usado a função length, que mede o comprimento em bytes, o resultado para Brasília sairia 9, porque
o caractere “í com acento agudo” ocupa dois bytes.)
Note que a tabela resultante tem a mesma quantidade de linhas que a tabela original, pois a função
lengthUTF8 foi aplicada aos valores das colunas de cada linha, uma por uma.

A linguagem SQL, porém, define certas funções, chamadas funções de agregação, que calculam
resultados usando todos valores em todas as linhas da coluna especificada. Por exemplo, para
calcularmos a população total dos países, poderíamos usar a função sum (“soma”, em inglês):
SELECT sum(population) FROM Countries

sum(population)
281715171

É preciso saber antecipadamente se a função é normal ou de agregação – não há nenhuma distinção


visual entre elas.
Pode-se combinar várias agregações em uma consulta só. Por exemplo, a consulta abaixo calcula
quantos países existem, a área total, a maior área, a menor área e a média entre as áreas dos países:
SELECT
count() AS paises,
sum(area) AS total,
max(area) AS maior,
min(area) AS menor,

18/20
avg(area) AS media
FROM Countries

paises total maior menor media


3 12052263 8515767 756096 4017421

Note que, na consulta acima, usamos a função de agregação count (“contagem”, em inglês) com a
lista de argumento vazia (nada entre os parênteses). Ela tem a particularidade de não precisar de
argumentos porque ela mede a quantidade de linhas, independente de qual coluna seja. Não teria
dado erro se tivéssemos colocado count(area) ao invés de count(), mas também não faria
diferença.
O nome da função avg é abreviatura do termo em inglês “average”, que significa média. O nome
max é abreviatura de “maximum” (“máximo”), e min é abreviatura de “minimum” (“mínimo”).

Sempre que há uma função de agregação em uma consulta, a tabela resultante terá apenas uma linha
por grupo; como não especificamos um agrupamento (veremos como fazer isso na segunda parte
deste tutorial), o grupo fica sendo a tabela inteira, de sorte que o resultado terá uma linha só.
Isso significa que não é possível combinar agrupamentos e campos normais em uma mesma
consulta, pois a coluna da função de agregação resultam uma linha só, enquanto as demais resultam
em tantas linhas quanto a tabela tiver – não dá para uma tabela ser irregular, com quantidades de
linhas diferentes para cada coluna. Por exemplo, a seguinte consulta:
SELECT name, max(area) FROM Countries

Resulta em uma mensagem de erro do tipo NOT_AN_AGGREGATE:


Code: 215. DB::Exception: Column `name` is not under aggregate function and
not in GROUP BY. Have columns: ['max(area)']: While processing name,
max(area). (NOT_AN_AGGREGATE)

A mensagem de erro é até bem esclarecedora: ela explica bem direitinho que a coluna name não está
sob agregação, nem em uma cláusula GROUP BY (que a tornaria permitida porque seu valor seria
único por grupo), e ainda lista a agregação que deu origem à situação: max(area).
Se a intenção da consulta acima era descobrir qual o país que tem a maior área, há uma função de
agregação chamada argMax que faz isso:
SELECT argMax(name,area), max(area) FROM Countries

argMax(name,area) max(area)
Brasil 8515767

Note que a função argMax tem dois parâmetros: o primeiro diz qual campo cujo valor você
realmente quer, e o segundo diz qual o campo ela vai usar para achar a linha que tem o maior valor.
Como você pode imaginar, existe uma função argMin também. Um detalhe importante: se vários
itens tiverem o mesmo valor máximo ou mínimo, a função escolhe um deles a esmo (em geral, o
primeiro deles).

19/20
➢ As funções argMin e argMax são específicas do ClickHouse.

Passo a Passo para Escrever Consultas


De ante de tudo que cobrimos acima, podemos esboçar um pequeno roteiro dos passos a serem
seguidos para escrever consultas em SQL:
1. Identifique em que tabelas estão as informações que você deseja e coloque-as na cláusula FROM;
2. Identifique as colunas, dentro das tabelas que você identificou no passo anterior, que contêm as
informações que você deseja e coloque seus nomes entre o SELECT e o FROM;
3. Veja se você precisa fazer cálculos ou agregações com esses campos, e crie os campos
calculados e aplique as funções adequadas;
4. Se você deseja restringir por algum critério e coloque-os numa cláusula WHERE;
5. Pense a respeito da ordem em que você quer os dados, e coloque-as no ORDER BY;
6. Acrescente LIMIT e OFFSET se necessário.
7. Execute a consulta e interprete atentamente os resultados, conferindo se eles atendem e/ou
confirmam suas expectativas. Caso não, verifique se a consulta precisa ser revista, adaptada, ou
consertada. Se a consulta lhe parece certa, talvez a consulta tenha lhe feito descobrir algo novo a
respeito dos dados!

Em Caso de Erro
Confira:
• Que não esteja faltando alguma vírgula entre os nomes dos campos entre o SELECT e o FROM;
• Que não tenha uma vírgula sobrando após o último campo;
• Que todos as abre aspas (smples ou duplas) têm um fecha aspas correspondente e do mesmo
tipo;
• Que os parênteses estejam corretamente aninhados e balanceados (todo “abre parêntese” deve
ter um fecha parêntese correspondente)
• Que os nomes dos campos e das tabelas estejam grafados corretamente;
➢ Especial atenção ao caractere _ (“sublinhado”), que às vezes “desaparece” quando se dá
zoom no navegador, dando a impressão que é um espaço, ao invés.
• Que os nomes das palavras-chave da linguagem SQL ( SELECT, FROM, ORDER BY, LIMIT, OFFSET)
estejam grafados corretamente e na ordem certa;
• Leia o texto descritivo do erro. Por vezes ele dá boas pistas do que pode estar errado.

20/20

Você também pode gostar