O SQL não é uma linguagem de programação completa: não suporta laços, entrada de usuário, chamadas de rede ou lógica de apresentação. Para construir uma aplicação real, é preciso combinar SQL com uma linguagem hospedeira — aqui, Python.
A abordagem padrão é usar uma biblioteca de acesso a banco de dados (database API) que:
Abre uma conexão com o SGBD
Envia instruções SQL pelo canal aberto
Recebe os resultados (linhas, contagens, erros) de volta
Confirma ou desfaz transações
Em Python, a biblioteca padrão para PostgreSQL é o psycopg2, que implementa a especificação DB-API 2.0 (PEP 249) — a mesma interface usada por sqlite3, pymysql e outras.
Instalação
pip install psycopg2-binary
Conectando ao PostgreSQL
import psycopg2import osconn = psycopg2.connect( dbname ="curso", host ="localhost", port =5432, user ="postgres", password = os.getenv("POSTGRES_PASSWORD", "postgres"))
Objeto
Papel
conn
Representa a sessão com o banco; mantém estado de transação
cur = conn.cursor()
Canal pelo qual se enviam SQL e se recebem resultados
Executando Consultas SELECT
cur = conn.cursor()cur.execute("SELECT nome, salario FROM professor ORDER BY salario DESC")for nome, salario in cur.fetchall():print(f"{nome}: R$ {salario:.2f}")cur.close()
Helena Carvalho: R$ 6700.00
Carla Pinto: R$ 6600.00
Bruno Teixeira: R$ 6500.00
Felipe Araujo: R$ 6400.00
Gustavo Costa: R$ 3300.00
Eduarda Souza: R$ 3200.00
Igor Melo: R$ 3100.00
Alternativas de fetch:
Método
Retorna
fetchone()
Próxima linha (ou None)
fetchmany(n)
Até n linhas
fetchall()
Todas as linhas restantes
SQL Injection — O Perigo das Strings Concatenadas
Aviso
SQL Injection é uma das vulnerabilidades mais críticas (OWASP Top 10). Ocorre quando dados do usuário são inseridos diretamente em uma string SQL — permitindo que um atacante altere a estrutura da consulta.
Exemplo inseguro
O código abaixo concatena o input do usuário diretamente na string SQL:
# VULNERÁVEL — nunca faça issoescola_id =input("ID da escola: ")sql ="SELECT nome FROM professor WHERE id_escola = "+ escola_idcur.execute(sql)
Quando o usuário digita 31 OR 1=1 --, a variável sql fica:
SELECT nome FROM professor WHERE id_escola =31OR1=1--
O fragmento OR 1=1 é sempre verdadeiro — o filtro é completamente ignorado. Executando exatamente essa query no banco:
cur = conn.cursor()# Simulando o código vulnerável com input maliciosoescola_id_malicioso ="31 OR 1=1 --"sql_injetado ="SELECT nome FROM professor WHERE id_escola = "+ escola_id_maliciosocur.execute(sql_injetado)rows = cur.fetchall()print(f"Professores retornados com input '{escola_id_malicioso}':")print(f" → {len(rows)} linha(s) — deveria retornar apenas os da escola 31\n")for (nome,) in rows:print(f" {nome}")cur.close()
Professores retornados com input '31 OR 1=1 --':
→ 7 linha(s) — deveria retornar apenas os da escola 31
Eduarda Souza
Felipe Araujo
Gustavo Costa
Helena Carvalho
Igor Melo
Bruno Teixeira
Carla Pinto
Todos os professores foram retornados — independente da escola pedida. Além do OR 1=1, um atacante pode usar UNION SELECT para exfiltrar dados de outras tabelas ou ; DROP TABLE para destruir dados.
Solução: Consultas Parametrizadas
Com consultas parametrizadas, o valor do usuário é enviado separado da query — nunca como parte do texto SQL:
# SEGURO — use sempre parâmetrosescola_id =input("ID da escola: ")cur.execute("SELECT nome FROM professor WHERE id_escola = %s", (escola_id,) # psycopg2 envia como parâmetro $1, nunca concatena no SQL)
Quando escola_id = "31 OR 1=1 --", psycopg2 envia ao PostgreSQL:
Query template:SELECT nome FROM professor WHERE id_escola = $1
Parâmetro $1: a string literal 31 OR 1=1 --
O PostgreSQL recebe um texto e tenta compará-lo com a coluna id_escola (tipo INTEGER). Como "31 OR 1=1 --" não é um inteiro válido, o banco rejeita e lança um erro — que o Python captura normalmente:
import psycopg2.errorscur = conn.cursor()escola_id_malicioso ="31 OR 1=1 --"try: cur.execute("SELECT nome FROM professor WHERE id_escola = %s", (escola_id_malicioso,) ) rows = cur.fetchall()print(f"{len(rows)} linha(s) retornada(s)")except psycopg2.errors.InvalidTextRepresentation as e: conn.rollback() # limpa o estado de erro da conexãoprint("PostgreSQL bloqueou a operação:")print(f" {e.diag.message_primary}")print(f"\n → O input foi tratado como DADO, não como SQL.")print(f" → A estrutura da query permanece intacta.")cur.close()
PostgreSQL bloqueou a operação:
invalid input syntax for type integer: "31 OR 1=1 --"
→ O input foi tratado como DADO, não como SQL.
→ A estrutura da query permanece intacta.
Mesmo que o atacante forneça um input que seja um inteiro válido (como "999"), a query simplesmente retorna 0 linhas — nenhum SQL extra é executado:
cur = conn.cursor()escola_id_invalido ="999"cur.execute("SELECT nome FROM professor WHERE id_escola = %s", (int(escola_id_invalido),))rows = cur.fetchall()print(f"Escola {escola_id_invalido}: {len(rows)} professor(es) — escola inexistente, 0 linhas.")cur.close()
Escola 999: 0 professor(es) — escola inexistente, 0 linhas.
Em psycopg2, o marcador de posição é %s para qualquer tipo. A biblioteca envia query e parâmetros separadamente ao servidor via protocolo wire — o SQL enviado nunca contém os dados do usuário concatenados como texto.
Compare com JDBC (Java), onde o marcador é ? em PreparedStatement — o mesmo princípio.
Operações DML (INSERT, UPDATE, DELETE)
cur = conn.cursor()# INSERT com parâmetroscur.execute(""" INSERT INTO evento_academico (nome, tipo, id_escola, data_inicio, data_fim, hora_inicio, hora_fim) VALUES (%s, %s, %s, %s, %s, %s, %s) """, ("Hackathon UnDF", "Workshop", 31, "2026-09-15", "2026-09-16", "08:00", "22:00"))# UPDATEcur.execute("UPDATE professor SET salario = %s WHERE id_prof = %s", (6900.00, 31400002))print(f"Linhas afetadas: {cur.rowcount}")conn.commit()cur.close()
Controle de Transações
Por padrão, psycopg2 inicia uma transação implicitamente na primeira instrução. Para confirmar ou desfazer:
cur = conn.cursor()try:# As duas instruções são atômicas: confirmam juntas ou nenhuma confirma cur.execute("UPDATE professor SET salario = salario * 1.10 WHERE id_escola = 31") cur.execute("UPDATE professor SET salario = salario * 0.95 WHERE id_escola = 21") conn.commit()print("Transação confirmada.")exceptExceptionas e: conn.rollback()print(f"Erro — rollback realizado: {e}")finally: cur.close()
Para operações de leitura simples onde transação explícita não é necessária:
conn.autocommit =True# cada instrução é confirmada imediatamente
Padrão Completo com Context Manager
import psycopg2, osfrom contextlib import closingdsn =dict(dbname="curso", host="localhost", port=5432, user="postgres", password=os.getenv("POSTGRES_PASSWORD","postgres"))with psycopg2.connect(**dsn) as conn:with closing(conn.cursor()) as cur: cur.execute(""" SELECT e.sigla, COUNT(p.id_prof) AS professores FROM escola e LEFT JOIN professor p ON p.id_escola = e.id_escola GROUP BY e.sigla ORDER BY professores DESC """)for sigla, total in cur.fetchall():print(f"{sigla}: {total} professor(es)") conn.commit()
Nota
O with psycopg2.connect(...) chama conn.commit() ao sair sem exceção, ou conn.rollback() se uma exceção ocorrer.
Para Praticar
Os trechos abaixo são exercícios para executar localmente (requerem Python + psycopg2):
# Exercício 1: listar alunos de um curso específicoimport psycopg2, osdef alunos_do_curso(id_curso: int): conn = psycopg2.connect( dbname="curso", host="localhost", port=5432, user="postgres", password=os.getenv("POSTGRES_PASSWORD","postgres") ) cur = conn.cursor() cur.execute(""" SELECT a.nome, a.ano_ingresso FROM aluno a WHERE a.id_curso = %s ORDER BY a.nome """, (id_curso,))for nome, ano in cur.fetchall():print(f" {nome} ({ano})") cur.close() conn.close()alunos_do_curso(311) # Engenharia de Software
# Exercício 2: matrícula segura — trata violações de FK e UNIQUEdef matricular(conn, id_aluno, id_disciplina, ano, semestre, turma): cur = conn.cursor()try: cur.execute(""" INSERT INTO matricula_disciplina (id_disciplina, ano, semestre, turma, id_aluno) VALUES (%s, %s, %s, %s, %s) """, (id_disciplina, ano, semestre, turma, id_aluno)) conn.commit()print("Matrícula realizada.")except psycopg2.errors.ForeignKeyViolation: conn.rollback()print("Turma não encontrada — matrícula cancelada.")except psycopg2.errors.UniqueViolation: conn.rollback()print("Aluno já matriculado nesta turma.")finally: cur.close()