- Publicado em
O que é SQL? Entendendo seu propósito, suas origens e como usá-lo
- Autores
O que é SQL? Entendendo seu propósito, suas origens e como usá-lo
A maioria das aplicações modernas, de blogs simples a enormes redes sociais, armazena e recupera dados usando uma linguagem chamada SQL.
Neste artigo, você aprenderá:
- O que é SQL e por que foi criado
- Como os bancos de dados relacionais organizam informações
- Um exemplo concreto com duas tabelas
- Como executar consultas SQL reais, incluindo
JOINs - Por que SQL continua sendo o padrão depois de mais de 50 anos
Vamos começar.
O que é SQL?
SQL (Structured Query Language, ou linguagem de consulta estruturada) é uma linguagem de programação declarativa criada para gerenciar bancos de dados relacionais, bancos de dados construídos em torno de tabelas, linhas e relacionamentos entre elas.
SQL permite:
- Criar tabelas
- Inserir e modificar dados
- Consultar dados usando filtros
- Unir dados relacionados de várias tabelas
- Impor restrições, como e-mails únicos ou chaves estrangeiras
SQL é usado em muitos sistemas populares de bancos de dados relacionais. Veja alguns exemplos:
- PostgreSQL: banco de dados relacional avançado de código aberto com amplo suporte aos padrões SQL.
- MySQL: amplamente usado em aplicações web, conhecido pela velocidade e simplicidade.
- MariaDB: versão derivada do MySQL mantida pela comunidade, com recursos aprimorados de desempenho.
- SQLite: banco de dados leve baseado em arquivos, usado em aplicações móveis, navegadores e sistemas embarcados.
- Microsoft SQL Server: banco de dados comercial de nível empresarial com ferramentas robustas em ambientes Windows e de nuvem.
- Serviços SQL na nuvem totalmente gerenciados, como:
- Amazon RDS
- Google Cloud SQL
- PlanetScale
Essas plataformas (Amazon RDS, Google Cloud SQL, PlanetScale) cuidam de backups, escalabilidade, atualizações e manutenção, mantendo o poder do SQL à sua disposição.
Por que SQL foi criado
Na década de 1970, na IBM, os pesquisadores Edgar F. Codd e Donald Chamberlin trabalhavam em uma maneira melhor de armazenar e recuperar dados. Na época, os bancos de dados eram hierárquicos ou baseados em redes, e interagir com eles era:
- Complexo
- Sujeito a erros
- Fortemente ligado ao armazenamento físico
- Difícil de consultar de forma flexível
Para resolver isso, eles introduziram o modelo relacional, que organiza dados em tabelas com relacionamentos bem definidos.
SQL foi criado porque:
- Os desenvolvedores precisavam de uma linguagem simples e legível para consultar dados.
- As empresas precisavam de regras consistentes para armazenar relacionamentos.
- Os dados precisavam ser acessados sem conhecer os detalhes internos do armazenamento.
O resultado?
SQL se tornou o padrão em 1986 (ANSI SQL), e quase todos os principais bancos de dados atuais continuam sendo construídos em torno dele.
Entendendo tabelas, colunas e relacionamentos
Em um banco de dados relacional:
- Uma tabela é como uma planilha (linhas + colunas)
- Uma linha representa um único registro
- Uma coluna representa uma propriedade
- Uma chave primária identifica cada linha de forma única
- Uma chave estrangeira liga uma tabela a outra
Vamos analisar um exemplo simples com usuários e pedidos.
Exemplo de esquema: usuários e pedidos
Tabela 1: users
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
Tabela 2: orders
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
Relacionamento
- Cada pedido pertence a um usuário (
orders.user_id → users.id). - Um usuário pode ter vários pedidos (relacionamento um para muitos).
Criando as tabelas (exemplo SQL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
product TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
Isso cria duas tabelas relacionais fortemente tipadas com uma restrição de chave estrangeira. Mas o que significa "fortemente tipadas"? Em bancos de dados SQL como PostgreSQL e MySQL, cada coluna tem um tipo de dado estrito:
- Strings precisam ser strings
- Números precisam ser números
- Datas precisam ser datas
- Tipos incorretos causam erros
Isso evita erros como:
- Salvar "vinte dólares" em uma coluna numérica
- Salvar 123 em um campo de e-mail
- Salvar datas inválidas
SQLite é diferente: ele é flexível quanto aos tipos (“tipagem dinâmica”), o que significa que impõe os tipos de forma menos estrita. PostgreSQL é o mais fortemente tipado entre os principais bancos de dados.
Criação das tabelas explicada linha por linha
Tabela 1: users
CREATE TABLE users (
Explicação: Isso inicia a criação de uma nova tabela chamada users.
- Funciona em: PostgreSQL, MySQL, MariaDB, SQLite, SQL Server
- “Tabela” = conjunto estruturado de linhas e colunas no banco de dados.
id SERIAL PRIMARY KEY,
Explicação:
id → o nome da coluna.
SERIAL → inteiro com incremento automático. Específico do PostgreSQL.
Equivalentes em outros mecanismos:
- MySQL:
INT AUTO_INCREMENT - SQLite:
INTEGER PRIMARY KEY AUTOINCREMENT
- MySQL:
PRIMARY KEY → garante unicidade e identifica cada linha.
name TEXT NOT NULL,
Explicação:
- name → coluna para armazenar texto.
- TEXT → texto de comprimento variável (string).
- NOT NULL → não pode ficar vazio.
Funciona em: PostgreSQL, MySQL, MariaDB, SQLite (mas SQL Server usa VARCHAR(MAX) em vez de TEXT).
email TEXT UNIQUE NOT NULL
Explicação:
- email → armazena o endereço de e-mail do usuário.
- TEXT → tipo de texto.
- UNIQUE → impede e-mails duplicados.
- NOT NULL → campo obrigatório.
Funciona em: PostgreSQL, MySQL, MariaDB, SQLite (considerando diferenças internas do tipo TEXT).
);
Explicação: Encerra a definição da tabela.
Tabela 2: orders
CREATE TABLE orders (
Inicia uma nova tabela chamada orders. Funciona em todos os principais mecanismos.
id SERIAL PRIMARY KEY,
Mesmo significado que na tabela users:
- Chave primária com incremento automático
- Tipo específico do PostgreSQL (
SERIAL) - MySQL usa
INT AUTO_INCREMENT - SQLite usa
INTEGER PRIMARY KEY AUTOINCREMENT
user_id INTEGER NOT NULL REFERENCES users(id),
Explicação:
- user_id → um número que liga o pedido a uma linha em
users. - INTEGER → número inteiro.
- NOT NULL → todo pedido precisa pertencer a um usuário.
- REFERENCES users(id) → restrição de chave estrangeira.
Significado de chave estrangeira:
- Garante que
user_idcorresponda a umidreal na tabelausers. - Se um usuário for excluído, o banco de dados fará cumprir o relacionamento de acordo com as regras de chave estrangeira.
Compatibilidade dos mecanismos:
- PostgreSQL: suporte completo.
- MySQL/MariaDB: funciona, mas exige o mecanismo de armazenamento InnoDB.
- SQLite: compatível somente quando o suporte a chaves estrangeiras está ativado (
PRAGMA foreign_keys = ON;).
product TEXT NOT NULL,
Armazena o nome do produto comprado.
- Tipo TEXT → funciona de forma diferente entre mecanismos, mas sempre armazena strings.
- Obrigatório (
NOT NULL).
price NUMERIC(10,2) NOT NULL
Explicação:
price → o custo do produto.
NUMERIC(10,2) → número de precisão fixa com:
- até 10 dígitos no total
- 2 dígitos após o separador decimal
Isso é ideal para valores monetários porque evita erros de ponto flutuante.
Compatibilidade dos mecanismos:
- PostgreSQL: suporte perfeito.
- MySQL:
DECIMAL(10,2)é equivalente. - SQLite: aceita, mas armazena internamente como TEXT, INT ou REAL (tipagem dinâmica).
);
Encerra a tabela.
Inserindo os dados do exemplo
Agora que nossas tabelas foram criadas, vamos inserir os dados de exemplo apresentados anteriormente. Não forneceremos manualmente nenhum valor de id, o banco de dados os gerará automaticamente usando o tipo SERIAL (incremento automático).
Inserindo os dados de users
INSERT INTO users (name, email)
VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Caroline', 'caroline@example.com');
Inserção em users explicada linha por linha
INSERT INTO users (name, email)
INSERT INTO usersEstamos adicionando novas linhas à tabela users.(name, email)Fornecemos apenasnameeemail, nãoid.Por que não fornecer o ID? Como a coluna
idusa um tipo serial/com incremento automático (SERIALdo PostgreSQL), o banco de dados criará os IDs automaticamente:Primeiro usuário →
id = 1Segundo usuário →
id = 2Terceiro usuário →
id = 3
Isso é previsível no PostgreSQL, MySQL, MariaDB e SQLite (usando seus respectivos mecanismos de incremento automático).
VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Caroline', 'caroline@example.com');
VALUESintroduz uma lista de linhas a inserir.- Cada grupo
( ... )corresponde a uma nova linha. - O banco de dados atribuirá IDs automaticamente na ordem em que essas linhas aparecem.
Tabela users resultante após a inserção:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
Embora não tenhamos escrito nenhum ID manualmente, o mecanismo de incremento automático garante essa ordem.
Inserindo os dados de orders
Para inserir orders, precisamos, sim, fornecer user_id, porque um pedido deve pertencer a um usuário específico.
Embora não tenhamos definido os IDs dos usuários manualmente, ainda sabemos quais são:
- Alice → ID 1
- Bob → ID 2
- Caroline → ID 3
Eles foram gerados automaticamente na ordem de inserção, e o incremento automático sempre atribui IDs sequencialmente, a menos que linhas sejam excluídas.
Aqui está a inserção:
INSERT INTO orders (user_id, product, price)
VALUES
(1, 'Coffee Mug', 12.99),
(1, 'Hoodie', 39.99),
(2, 'Stickers', 4.99);
Inserção em orders explicada linha por linha
INSERT INTO orders (user_id, product, price)
Estamos inserindo novas linhas na tabela orders.
Especificamos:
user_id→ a qual usuário o pedido pertenceproduct→ nomeprice→ custo
Observe novamente: não fornecemos id (orders.id), porque ele é incrementado automaticamente.
VALUES
(1, 'Coffee Mug', 12.99),
(1, 'Hoodie', 39.99),
(2, 'Stickers', 4.99);
(1, 'Coffee Mug', 12.99)- Este pedido pertence ao usuário 1 (Alice).
(1, 'Hoodie', 39.99)- Outro pedido para Alice.
(2, 'Stickers', 4.99)- Pedido para o usuário 2 (Bob).
Como sabemos qual usuário tem qual ID? Porque:
Os usuários foram inseridos primeiro.
IDs com incremento automático sempre atribuem inteiros sequenciais.
Portanto:
- Primeiro usuário inserido → ID 1
- Segundo usuário inserido → ID 2
- Terceiro usuário inserido → ID 3
Isso torna totalmente previsível referenciar usuários pelo ID.
Tabelas finais resultantes
users
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
orders
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
Consultando dados com SQL
Aqui estão consultas SQL que podemos executar nas duas tabelas definidas anteriormente.
1. Selecionar todos os usuários
SELECT * FROM users;
Resultado:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
2. Selecionar usuários com um filtro
SELECT * FROM users
WHERE email LIKE '%alice@example.com';
O que isso significa:
LIKEé um operador SQL usado para comparar texto usando padrões.%é um curinga que corresponde a qualquer quantidade de caracteres, incluindo zero caracteres.
Portanto:
'%alice@example.com'significa: “Corresponder a qualquer texto que termine comalice@example.com.”
Isso retornará a linha em que o e-mail do usuário é exatamente:
alice@example.com
mas também corresponderia a valores como:
test-alice@example.comhello.alice@example.comanythingalice@example.com
se existissem.
Resultado:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
Esta consulta retorna apenas o usuário cujo e-mail corresponde ao filtro.
3. Inserir um novo pedido
INSERT INTO orders (user_id, product, price)
VALUES (2, 'T-Shirt', 29.99);
Tabela orders resultante após a inserção:
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
| 4 | 2 | T-Shirt | 29.99 |
- A nova linha tem
id = 4, gerado automaticamente.user_id = 2vincula esse pedido a Bob.
JOINs: uma parte poderosa do SQL
Os JOINs permitem consultar dados relacionados entre várias tabelas.
Vamos explorá-los usando nossas tabelas users e orders.
Exemplo 1: INNER JOIN (o mais comum)
Obtenha cada pedido com o usuário que o fez.
SELECT users.name, orders.product, orders.price
FROM orders
INNER JOIN users
ON orders.user_id = users.id;
Resultado:
| name | product | price |
|---|---|---|
| Alice | Coffee Mug | 12.99 |
| Alice | Hoodie | 39.99 |
| Bob | Stickers | 4.99 |
| Bob | T-Shirt | 29.99 |
Bob agora aparece duas vezes, por causa do novo pedido inserido anteriormente:
INSERT INTO orders (user_id, product, price)
VALUES (2, 'T-Shirt', 29.99);
Exemplo 2: LEFT JOIN
Obtenha todos os usuários, mesmo aqueles sem pedidos.
SELECT users.name, orders.product
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
Resultado:
| name | product |
|---|---|
| Alice | Coffee Mug |
| Alice | Hoodie |
| Bob | Stickers |
| Bob | T-Shirt |
| Caroline | null |
Caroline aparece com null porque não tem pedidos.
Exemplo 3: consulta de agregação
Total gasto por cada usuário:
SELECT users.name, SUM(orders.price) AS total_spent
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.name;
Resultado:
| name | total_spent |
|---|---|
| Alice | 52.98 |
| Bob | 34.98 |
| Caroline | null |
Explicação
- Alice: 12.99 + 39.99 = 52.98
- Bob: 4.99 + 29.99 = 34.98
- Caroline: sem pedidos → NULL
Onde aprender mais
- Documentação do PostgreSQL
- Manual de referência do MySQL
- Tutorial de SQL da W3Schools
- Documentação do SQLite
Considerações finais
SQL existe há mais de 50 anos, mas ainda sustenta as aplicações mais importantes do mundo, porque é:
- Declarativo
- Consistente
- Previsível
- Escalável
- Apoiado por décadas de pesquisa
Quando você entende como tabelas e junções funcionam, SQL se torna uma das ferramentas mais poderosas do seu conjunto de ferramentas de programação.