SQL: fundamentos (DDL, DML, DQL, DCL, TCL)

Chegamos ao módulo em que a teoria que viemos construindo finalmente vira comando digitado e executado. Até aqui falamos de modelo relacional, de tabelas, de chaves e de relacionamentos como conceitos. Agora vou te apresentar a linguagem que materializa tudo isso dentro de um SGBD: a SQL. Quero que você termine esta leitura sabendo não só escrever um SELECT, mas entendendo por que a SQL é dividida em cinco sublinguagens, o que cada uma faz e onde elas se encaixam no dia a dia de quem administra ou consulta um banco. Esse assunto é campeão de questões em prova justamente porque permite cobrar tanto sintaxe seca quanto entendimento conceitual. Vou te mostrar os dois lados, sempre com PostgreSQL nos exemplos, que é o dialeto que você precisa dominar aqui.

Um aviso antes de começarmos: as consultas mais sofisticadas (junções entre tabelas, subconsultas, funções de janela e agregações) ficam reservadas para o módulo 10, e o estudo aprofundado de transações e isolamento vem no módulo 12. Aqui o objetivo é construir o alicerce: criar estruturas, povoá-las com dados, recuperá-las de forma simples, controlar quem acessa e agrupar operações em transações básicas.

Um pouco de história: de SEQUEL a SQL

Toda essa história começa em 1970, quando Edgar F. Codd, matemático da IBM, publicou o artigo que fundou o modelo relacional. Codd propôs organizar dados em tabelas bidimensionais apoiadas na teoria dos conjuntos e na lógica de predicados, rompendo com os modelos hierárquico e de rede que dominavam a época. A grande sacada foi separar a estrutura lógica dos dados de sua implementação física e apoiar tudo em uma base matemática sólida.

Inspirados nesse trabalho, Donald Chamberlin e Raymond Boyce, também na IBM, criaram em 1974 uma linguagem para concretizar as ideias de Codd: a SEQUEL, sigla de Structured English Query Language. A intenção era que ela fosse próxima do inglês, legível até por quem não era programador. Em 1977 o nome teve de mudar para SQL por uma questão de marca registrada, e a IBM passou a implementá-la no System R, um dos primeiros SGBDRs da história. Curiosamente, quem chegou primeiro ao mercado não foi a IBM: a Relational Software, que viria a se chamar Oracle, lançou seu produto comercial em 1979.

À medida que vários fornecedores criavam dialetos próprios, surgiu a necessidade de padronização. Em 1986 o ANSI publicou o SQL-86, seguido pelo padrão ISO em 1987. De lá para cá vieram revisões sucessivas, e vale guardar essa linha do tempo porque ela aparece em prova com frequência.

Versão Ano Principais novidades
SQL-86 1986 Primeiro padrão ANSI
SQL-89 1989 Revisão menor, integridade referencial
SQL-92 (SQL2) 1992 Grande revisão, muitos recursos novos
SQL:1999 (SQL3) 1999 Orientação a objetos, gatilhos, recursão
SQL:2003 2003 XML e funções de janela (window functions)
SQL:2008/2011/2016/2019 Refinamentos sucessivos, JSON, temporal

timeline
    title Evolucao da SQL
    1970 : Modelo relacional de Codd
    1974 : SEQUEL na IBM
    1977 : Renomeada para SQL
    1979 : Oracle comercial
    1986 : Padrao ANSI SQL-86
    1992 : SQL-92
    1999 : SQL-1999
    2003 : Window functions

Por que a SQL sobreviveu a tudo. Mesmo com a ascensão de bancos NoSQL e de plataformas de big data, a SQL permanece central. Isso se deve à sua maturidade e confiabilidade após décadas de uso em produção, à existência de padrões que favorecem portabilidade, à expressividade que permite descrever consultas complexas de forma concisa e a uma enorme base de profissionais que a dominam. Hoje ela reaparece adaptada para big data (Hive, Presto), para o NewSQL (Spanner, CockroachDB) e até para processamento em streaming (Flink, Kafka Streams).

A SQL não é uma só: as cinco sublinguagens

Aqui está o conceito organizador deste módulo. A SQL costuma ser tratada como uma linguagem única, mas na prática ela é um conjunto de sublinguagens, cada uma com um propósito bem delimitado. Entender essa divisão é o que separa quem decora comandos de quem realmente compreende a linguagem. Quando uma prova pergunta “o comando TRUNCATE pertence a qual categoria?”, ela está testando exatamente esse mapa mental que vou te dar agora.

graph TD
    SQL[SQL] --> DDL[DDL - Definicao]
    SQL --> DML[DML - Manipulacao]
    SQL --> DQL[DQL - Consulta]
    SQL --> DCL[DCL - Controle de Acesso]
    SQL --> TCL[TCL - Controle de Transacao]
    DDL --> D1[CREATE ALTER DROP TRUNCATE]
    DML --> D2[INSERT UPDATE DELETE]
    DQL --> D3[SELECT]
    DCL --> D4[GRANT REVOKE]
    TCL --> D5[BEGIN COMMIT ROLLBACK SAVEPOINT]

Sublinguagem Propósito Comandos típicos
DDL Definir e alterar a estrutura CREATE, ALTER, DROP, TRUNCATE
DML Manipular os dados INSERT, UPDATE, DELETE
DQL Consultar os dados SELECT
DCL Controlar privilégios de acesso GRANT, REVOKE
TCL Gerenciar transações BEGIN, COMMIT, ROLLBACK, SAVEPOINT

Repare que muitos autores tratam DQL como parte da DML, já que o SELECT também opera sobre dados. Para fins de prova, vale conhecer as duas leituras: a divisão em cinco categorias é a mais didática e detalhada, mas você pode encontrar a versão em quatro, em que o SELECT é absorvido pela DML. Vou seguir a divisão em cinco porque ela deixa cada papel mais nítido.

DDL: definindo a estrutura

A DDL é onde desenhamos o esqueleto do banco. Antes de qualquer dado existir, precisamos declarar tabelas, colunas, tipos e restrições. Vou trabalhar com um cenário de universidade ao longo do módulo, porque é um domínio rico o bastante para mostrar quase tudo.

Tipos de dados no PostgreSQL

Cada coluna tem um tipo, e o tipo define o que pode ser armazenado e quanto espaço ocupa. O PostgreSQL é generoso em tipos; deixo abaixo os que você mais encontrará.

Tipo Uso Observação
INTEGER / INT inteiros de 32 bits faixa de cerca de -2,1 bi a 2,1 bi
BIGINT inteiros de 64 bits para valores muito grandes
SERIAL / BIGSERIAL inteiro autoincremento atalho que cria uma sequência
NUMERIC(p,s) decimal exato ideal para dinheiro
REAL / DOUBLE PRECISION ponto flutuante sujeito a arredondamento
VARCHAR(n) texto de tamanho variável limite de n caracteres
CHAR(n) texto de tamanho fixo preenchido com espaços
TEXT texto sem limite muito usado no PostgreSQL
DATE data sem horário
TIMESTAMP data e hora há a variante WITH TIME ZONE
BOOLEAN verdadeiro/falso aceita TRUE, FALSE, NULL
UUID identificador universal chaves distribuídas
JSONB JSON binário indexável, semiestruturado

Atenção a um erro frequente. Para valores monetários, nunca use REAL ou DOUBLE PRECISION. Esses tipos de ponto flutuante introduzem erros de arredondamento, e 0.1 + 0.2 pode não dar exatamente 0.3. Use NUMERIC(p,s), que armazena o valor de forma exata. Em prova, essa é uma pegadinha clássica.

CREATE: criando objetos

O CREATE TABLE é o comando que mais você vai escrever. Vou criar a tabela de cursos primeiro, porque ela será referenciada pelas demais.

CREATE TABLE cursos (
    id          SERIAL PRIMARY KEY,
    nome        VARCHAR(80) NOT NULL UNIQUE,
    duracao     INTEGER NOT NULL CHECK (duracao BETWEEN 1 AND 12),
    criado_em   TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Veja quanta informação está condensada aqui. SERIAL PRIMARY KEY cria uma coluna inteira que se autoincrementa e serve de chave primária. NOT NULL proíbe ausência de valor. UNIQUE impede nomes de curso repetidos. CHECK impõe uma regra de domínio (duração entre 1 e 12 semestres). E DEFAULT CURRENT_TIMESTAMP preenche automaticamente a data de criação. Restrições assim são parte da DDL e são o que garante a integridade dos dados desde o nascimento da tabela.

Agora a tabela de estudantes, que se conecta a cursos por uma chave estrangeira.

CREATE TABLE estudantes (
    id               SERIAL PRIMARY KEY,
    nome             VARCHAR(100) NOT NULL,
    email            VARCHAR(120) UNIQUE,
    data_nascimento  DATE,
    curso_id         INTEGER REFERENCES cursos(id),
    mensalidade      NUMERIC(8,2) DEFAULT 0,
    ativo            BOOLEAN NOT NULL DEFAULT TRUE
);

A linha curso_id INTEGER REFERENCES cursos(id) declara a chave estrangeira: cada estudante aponta para um curso existente, e o banco recusará um curso_id que não exista em cursos. Isso é a integridade referencial atuando, exatamente o conceito de relacionamento 1:N que vimos antes — um curso tem muitos estudantes, cada estudante pertence a um curso.

Vou deixar um diagrama do nosso pequeno esquema para você visualizar.

erDiagram
    CURSOS ||--o{ ESTUDANTES : possui
    CURSOS {
        int id PK
        varchar nome
        int duracao
    }
    ESTUDANTES {
        int id PK
        varchar nome
        int curso_id FK
        numeric mensalidade
    }

Quando você precisa de uma restrição com nome próprio (útil para depois alterá-la), pode declará-la explicitamente:

CREATE TABLE matriculas (
    estudante_id  INTEGER NOT NULL,
    curso_id      INTEGER NOT NULL,
    semestre      VARCHAR(6) NOT NULL,
    CONSTRAINT pk_matriculas PRIMARY KEY (estudante_id, curso_id, semestre),
    CONSTRAINT fk_mat_estudante FOREIGN KEY (estudante_id) REFERENCES estudantes(id),
    CONSTRAINT fk_mat_curso FOREIGN KEY (curso_id) REFERENCES cursos(id)
);

Essa tabela matriculas é a tabela de junção que implementa um relacionamento muitos-para-muitos: como um estudante cursa várias disciplinas ao longo de vários semestres, a chave primária é composta por três colunas. Guarde esse padrão, ele aparece sempre que houver um M:N.

ALTER: modificando o que já existe

A estrutura raramente nasce pronta. ALTER TABLE permite evoluí-la sem recriar nada.

ALTER TABLE estudantes ADD COLUMN telefone VARCHAR(20);

ALTER TABLE estudantes ALTER COLUMN email SET NOT NULL;

ALTER TABLE estudantes RENAME COLUMN telefone TO celular;

ALTER TABLE estudantes
    ADD CONSTRAINT chk_mensalidade CHECK (mensalidade >= 0);

ALTER TABLE estudantes DROP COLUMN celular;

Cada uma dessas linhas faz uma cirurgia precisa: adicionar coluna, tornar uma coluna obrigatória, renomear, adicionar restrição e remover coluna. O PostgreSQL executa boa parte dessas operações de forma transacional, ou seja, dentro de uma transação você pode até desfazê-las, o que é uma vantagem importante.

DROP e TRUNCATE: removendo

Há uma diferença que cai muito em prova, e quero que você nunca mais confunda. DROP remove o objeto inteiro — a tabela deixa de existir, estrutura e dados. TRUNCATE mantém a tabela viva, apenas esvazia todas as linhas de uma vez.

TRUNCATE TABLE matriculas;

DROP TABLE matriculas;

DROP TABLE IF EXISTS tabela_temporaria;
Aspecto DELETE TRUNCATE DROP
Categoria DML DDL DDL
Remove dados sim (linha a linha) sim (todas de uma vez) sim
Remove estrutura não não sim
Aceita WHERE sim não não
Reversível por ROLLBACK sim sim no PostgreSQL sim no PostgreSQL
Velocidade em massa lenta muito rápida imediata

Cuidado com a categorização. O DELETE parece primo do TRUNCATE, mas pertence à DML, não à DDL. A diferença não é só de família: o DELETE percorre linha a linha respeitando o WHERE e gera log detalhado, enquanto o TRUNCATE realoca o armazenamento de forma global e por isso é muito mais rápido para esvaziar tabelas grandes — ao custo de não aceitar filtro.

DML: manipulando os dados

Com a estrutura pronta, é hora de povoá-la. A DML cuida das três operações de escrita: inserir, atualizar e apagar.

INSERT

INSERT INTO cursos (nome, duracao)
VALUES ('Ciência da Computação', 8);

INSERT INTO cursos (nome, duracao)
VALUES ('Engenharia de Software', 8),
       ('Sistemas de Informação', 8),
       ('Análise e Desenvolvimento de Sistemas', 6);

O segundo INSERT mostra a inserção múltipla: várias linhas em uma única instrução, o que é mais eficiente do que repetir o comando. Repare que não informei id nem criado_em: o SERIAL e o DEFAULT cuidam disso. Agora os estudantes:

INSERT INTO estudantes (nome, email, data_nascimento, curso_id, mensalidade)
VALUES ('João Silva',  'joao@usp.br',  '2003-05-15', 1, 1200.00),
       ('Maria Souza', 'maria@usp.br', '2002-11-30', 1, 1200.00),
       ('Ana Lima',    'ana@usp.br',   '2004-02-20', 2,  950.50);

O PostgreSQL oferece um recurso elegante para recuperar valores gerados na própria inserção, a cláusula RETURNING:

INSERT INTO estudantes (nome, email, curso_id)
VALUES ('Pedro Rocha', 'pedro@usp.br', 3)
RETURNING id, nome;

UPDATE

O UPDATE modifica linhas existentes, e aqui mora um perigo que todo iniciante leva um susto ao descobrir.

UPDATE estudantes
SET mensalidade = mensalidade * 1.10
WHERE curso_id = 1;

UPDATE estudantes
SET curso_id = 2, ativo = TRUE
WHERE id = 4;

O esquecimento mais caro da SQL. Um UPDATE ou DELETE sem cláusula WHERE afeta TODAS as linhas da tabela. Se você rodar UPDATE estudantes SET ativo = FALSE; sem filtro, desativa o banco inteiro. Antes de executar uma alteração em produção, ganhe o hábito de escrever primeiro o WHERE e só depois o resto. Esse é, de longe, o erro operacional mais comum e mais doloroso.

DELETE

DELETE FROM estudantes
WHERE ativo = FALSE;

DELETE FROM estudantes
WHERE id = 4;

Vale o mesmo aviso do UPDATE: sem WHERE, o DELETE apaga tudo. A diferença para o TRUNCATE, que já mostrei, é que o DELETE é seletivo e mais lento em massa.

DQL: consultando os dados

Chegamos ao SELECT, o comando mais usado e mais versátil de toda a SQL. Aqui vou cobrir apenas o esqueleto fundamental — projeção, filtro, ordenação, distinção e limite. Junções, agrupamentos e subconsultas ficam para o módulo 10, então não estranhe se eu parar antes do JOIN.

A ordem em que escrevemos as cláusulas é fixa, e convém memorizá-la:

SELECT [DISTINCT] colunas
FROM tabela
[WHERE condição]
[ORDER BY colunas]
[LIMIT n];

SELECT e FROM: a projeção

O SELECT escolhe quais colunas retornar (a projeção), e o FROM indica a origem.

SELECT nome, email FROM estudantes;

SELECT * FROM estudantes;

O * traz todas as colunas. É prático para explorar, mas em código de produção prefira listar as colunas explicitamente: fica mais legível e evita surpresas se a tabela mudar.

Suponha que a consulta SELECT nome, mensalidade FROM estudantes; devolva:

nome mensalidade
João Silva 1320.00
Maria Souza 1320.00
Ana Lima 950.50

WHERE: o filtro

O WHERE restringe quais linhas entram no resultado, aplicando uma condição lógica linha a linha.

SELECT nome, mensalidade
FROM estudantes
WHERE mensalidade > 1000;

SELECT nome
FROM estudantes
WHERE curso_id = 1 AND ativo = TRUE;

SELECT nome
FROM estudantes
WHERE nome LIKE 'A%';

SELECT nome
FROM estudantes
WHERE curso_id IN (1, 2);

SELECT nome, email
FROM estudantes
WHERE email IS NULL;

Quero chamar sua atenção para o último exemplo. Para testar ausência de valor, usamos IS NULL e IS NOT NULL, nunca = NULL. Isso acontece porque NULL não é um valor comum: ele representa “desconhecido”, e qualquer comparação com ele resulta em NULL, que não é verdadeiro. Essa lógica de três valores (verdadeiro, falso, desconhecido) é um tema delicado e muito cobrado.

Operador Significado Exemplo
= <> < > <= >= comparação idade >= 18
AND OR NOT conectores lógicos a AND b
BETWEEN faixa inclusiva nota BETWEEN 7 AND 10
IN pertence a conjunto uf IN ('SP','RJ')
LIKE padrão textual nome LIKE 'Jo%'
IS NULL ausência de valor email IS NULL

Sobre o LIKE. No padrão de texto, % casa qualquer sequência de caracteres e _ casa exatamente um caractere. Assim, 'A%' pega tudo que começa com A, '%a' tudo que termina em a, e '_aria' pega Maria, Daria e similares de cinco letras. No PostgreSQL existe ainda o ILIKE, que ignora maiúsculas e minúsculas.

ORDER BY: a ordenação

O resultado de uma consulta não tem ordem garantida a menos que você peça. O ORDER BY impõe a ordenação, crescente por padrão (ASC) ou decrescente (DESC).

SELECT nome, mensalidade
FROM estudantes
ORDER BY mensalidade DESC, nome ASC;

Aqui ordeno primeiro pela mensalidade, da maior para a menor, e em caso de empate desempato pelo nome em ordem alfabética. Ordenar por mais de uma coluna assim é comum e elegante.

DISTINCT: eliminando duplicatas

O DISTINCT remove linhas repetidas do resultado, devolvendo apenas valores únicos.

SELECT DISTINCT curso_id
FROM estudantes;

Se cinco estudantes pertencem ao curso 1 e três ao curso 2, sem o DISTINCT viriam oito linhas; com ele, apenas duas — 1 e 2. É a forma direta de descobrir quais cursos têm ao menos um estudante.

LIMIT: restringindo a quantidade

O LIMIT corta o resultado a um número máximo de linhas, recurso indispensável para paginação e para espiar amostras de tabelas enormes.

SELECT nome, mensalidade
FROM estudantes
ORDER BY mensalidade DESC
LIMIT 3;

SELECT nome
FROM estudantes
ORDER BY id
LIMIT 5 OFFSET 10;

A primeira consulta devolve as três maiores mensalidades — repare como ORDER BY e LIMIT trabalham juntos para responder “os top N”. A segunda usa OFFSET para pular as dez primeiras linhas e trazer as cinco seguintes, que é exatamente o mecanismo por trás da navegação por páginas em uma aplicação.

graph LR
    A[FROM tabela] --> B[WHERE filtra linhas]
    B --> C[SELECT projeta colunas]
    C --> D[DISTINCT remove duplicatas]
    D --> E[ORDER BY ordena]
    E --> F[LIMIT corta]

Esse diagrama mostra a ordem lógica de processamento de uma consulta, que é diferente da ordem em que escrevemos as cláusulas. Primeiro o banco identifica a origem, depois filtra, então projeta as colunas, elimina duplicatas, ordena e por fim aplica o limite. Entender essa sequência ajuda a prever o resultado de consultas que parecem ambíguas.

DCL: controlando o acesso

A DCL trata de segurança: quem pode fazer o quê. No PostgreSQL, usuários e grupos são modelados como roles. Os dois comandos são GRANT, que concede privilégios, e REVOKE, que os retira.

GRANT SELECT, INSERT ON estudantes TO usuario_secretaria;

GRANT ALL PRIVILEGES ON cursos TO admin_academico;

REVOKE INSERT ON estudantes FROM usuario_secretaria;

GRANT SELECT ON estudantes TO PUBLIC;

A leitura é direta: o primeiro comando deixa a secretaria consultar e inserir em estudantes; o segundo dá poder total sobre cursos a um administrador; o terceiro tira da secretaria a permissão de inserir; e o último libera leitura para todos, já que PUBLIC representa qualquer role. Esse controle granular é o que permite que o banco seja compartilhado por muitos perfis sem que um interfira indevidamente no domínio do outro.

Comando Função Exemplo
GRANT concede privilégio GRANT SELECT ON t TO r
REVOKE remove privilégio REVOKE SELECT ON t FROM r

TCL: agrupando operações em transações

Por fim, a TCL, que gerencia transações — blocos de comandos que devem ser tratados como uma unidade indivisível. Vou apenas apresentar os comandos aqui; o estudo a fundo das propriedades ACID e dos níveis de isolamento espera por você no módulo 12.

A ideia é simples e poderosa: dentro de uma transação, ou tudo dá certo e é confirmado, ou algo falha e tudo é desfeito. Imagine uma transferência de uma matrícula que envolve apagar um registro e inserir outro; você não quer que o primeiro passo seja efetivado e o segundo falhe, deixando o banco inconsistente.

BEGIN;
    UPDATE estudantes SET mensalidade = mensalidade - 100 WHERE id = 1;
    UPDATE estudantes SET mensalidade = mensalidade + 100 WHERE id = 2;
COMMIT;

O BEGIN abre a transação, os comandos no meio são provisórios, e o COMMIT confirma tudo de uma vez, tornando as mudanças permanentes e visíveis para os demais usuários. Se algo der errado antes do COMMIT, usamos ROLLBACK:

BEGIN;
    DELETE FROM estudantes WHERE curso_id = 3;
    -- percebi que era o curso errado
ROLLBACK;

O ROLLBACK aborta a transação e devolve o banco ao estado anterior ao BEGIN, como se nada tivesse acontecido. E quando queremos um desfazer parcial, entra o SAVEPOINT, que marca um ponto intermediário ao qual podemos retornar sem cancelar a transação inteira:

BEGIN;
    INSERT INTO cursos (nome, duracao) VALUES ('Robótica', 6);
    SAVEPOINT ponto1;
    INSERT INTO cursos (nome, duracao) VALUES ('Curso Inválido', 99);
    ROLLBACK TO ponto1;
    INSERT INTO cursos (nome, duracao) VALUES ('Data Science', 8);
COMMIT;

Acompanhe a lógica: insiro Robótica, marco ponto1, tento inserir um curso inválido, volto ao ponto1 desfazendo só essa última inserção, insiro Data Science e finalmente confirmo. Ao final, Robótica e Data Science persistem; o curso inválido nunca existiu. O SAVEPOINT dá granularidade fina ao controle transacional.

stateDiagram-v2
    [*] --> Ativa : BEGIN
    Ativa --> Ativa : INSERT UPDATE DELETE
    Ativa --> Ponto : SAVEPOINT
    Ponto --> Ativa : ROLLBACK TO
    Ativa --> Confirmada : COMMIT
    Ativa --> Abortada : ROLLBACK
    Confirmada --> [*]
    Abortada --> [*]

Detalhe do PostgreSQL. Por padrão, no PostgreSQL cada comando isolado roda em modo autocommit, ou seja, é confirmado imediatamente. As transações explícitas com BEGIN existem justamente para agrupar vários comandos sob um único COMMIT ou ROLLBACK. Sem o BEGIN, cada INSERT ou UPDATE já é definitivo no instante em que executa.

Síntese para a prova

Feche este módulo com o mapa das cinco sublinguagens fixado: a DDL define estrutura (CREATE, ALTER, DROP, TRUNCATE), a DML manipula dados (INSERT, UPDATE, DELETE), a DQL consulta (SELECT), a DCL controla acesso (GRANT, REVOKE) e a TCL gerencia transações (BEGIN, COMMIT, ROLLBACK, SAVEPOINT). A pegadinha mais frequente é a confusão entre DELETE, TRUNCATE e DROP: o primeiro é DML e seletivo via WHERE; o segundo é DDL, esvazia tudo rapidamente sem filtro e preserva a estrutura; o terceiro é DDL e elimina o objeto inteiro. Lembre que TRUNCATE não pertence à DML, embora apague dados.

No SELECT, domine a ordem das cláusulas — SELECT, FROM, WHERE, ORDER BY, LIMIT — e a diferença entre a ordem de escrita e a ordem lógica de processamento, que começa pelo FROM. Guarde que NULL exige IS NULL e nunca = NULL, que DISTINCT elimina duplicatas e que LIMIT com OFFSET é a base da paginação. Na história, ancore as datas: Codd em 1970, SEQUEL em 1974, renomeação para SQL em 1977 e primeiro padrão ANSI em 1986. E não esqueça que, para dinheiro, a escolha correta é NUMERIC, jamais um tipo de ponto flutuante. Com esses pontos firmes, você responde com segurança a praticamente qualquer questão de fundamentos de SQL.