Skip to main content

Banco de dados com Mysql



Python com Mysql


Para usar o Python com Mysql vou usar o Pymsql e o container com Mysql que foi desmontrado na sessão anterior.

# Crie um ambiente virtual:
python3 -m venv mysql

# Entre no ambiente virtual:
cd mysql

# Ative o ambiente virtual:
source bin/activate

# Instale o pymsql:
pip3 install pymysql

Se você subiu o Mysql com método de autenticação para sha256_password ou caching_sha2_password, será necessário instalar o pacote abaixo:

pip3 install cryptography

Crie o arquivo do python, vou chamar de main.py:

# Crie o arquivo:
> main.py

# Crie o shebang do python:
echo '#!/usr/bin/env python3' > main.py

# Dê permissão de execução para o arquivo criado:
chmod +x main.py


Começando nosso código


Vamos começar com algo simples no código, nao vou me atentar muito na parte de segurança, começarei com hard coded e vamos aprimorando. Vejamos uma forma simples de como conectar no banco:

main.py
#!/usr/bin/env python3

import pymysql

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host="localhost",
user="usuario",
password="senha123",
database="dbtest"
)

# Cria um cursor (objeto usado para executar os comandos SQL na conexão criada acima):
cursor = conn.cursor()

cursor.execute("SELECT DATABASE()")
print(cursor.fetchone())

cursor.close()
conn.close()

Como nosso pymysql.connect é um context manager, já que a biblioteca implementa os métodos __enter__ e __exit__, podemos usar o with, o que garante que a conexão seja fechada automaticamente ao final do bloco, com isso nao vamos precisar fechar.

main.py
#!/usr/bin/env python3

import pymysql

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host="localhost",
user="usuario",
password="senha123",
database="dbtest"
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

cursor.execute("SELECT DATABASE()")
print(cursor.fetchone())


Agora vamos buscar as variáveis do arquivo .env, assim ficará mais simples quando migrarmos para container, vale lembrar que o módulo python-dotenv só será usado agora e nao deve ser levado para o container:

.env
MYSQL_ROOT_PASSWORD='root'
MYSQL_DATABASE='dbtest'
MYSQL_USER='usuario'
MYSQL_PASSWORD='senha123'
MYSQL_HOST='localhost'
TZ='America/Sao_Paulo'

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/fulano/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Agora você pode acessar as variáveis normalmente
db_user = os.environ("MYSQL_USER")
db_pass = os.environ("MYSQL_PASSWORD")
db_name = os.environ("MYSQL_DATABASE")
db_host = os.environ("MYSQL_HOST")

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=db_host,
user=db_user,
password=db_pass,
database=db_name
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

cursor.execute("SELECT DATABASE()")
print(cursor.fetchone())

Use os.getenv() quando você quer evitar erro se a variável não existir. Ele retorna None se a variável não estiver definida. Mas você pode usar os.environ["VAR"] quando a variável é obrigatória e você quer que o programa pare com erro (KeyError) se ela não estiver no ambiente.


Ainda podemos deixar mais simples e bonito o código:

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

cursor.execute("SELECT DATABASE()")
print(cursor.fetchone())


Criando uma tabela


Para criar uma tabela com pymysql, você usa um comando CREATE TABLE dentro de um cursor, como no exemplo abaixo:

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

cursor.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
preco DECIMAL(10,2) NOT NULL
)
""")
# Aplica no banco:
conn.commit()


Criando tabelas no container


Como não é comum o programador criar tabelas dinamicamente via código, porque isso mistura responsabilidades, o código deveria se concentrar na lógica da aplicação, não na estrutura do banco. Por isso, é uma prática comum já deixar o banco pronto para uso assim que o container sobe.


Vamos fazer isso criando um script SQL com os comandos CREATE TABLE e instruindo o container do MySQL a executá-lo automaticamente. Teremos uma tabela chamada produtos com os seguintes campos:

  • id: Será o 'id' do produto e esse campo será chave primária;
  • nome: Nome do produto;
  • valor: Preço do produto;

Também teremos uma segunda tabela chamada funcionarios com os seguintes campos:

  • id: Será o 'id' do funcionario e esse campo será chave primária;
  • nome: Nome do funcionário;
  • data_nascimento: Data de nascimento;

E por fim, teremos uma tabela chamada vendas com os seguintes campos:

  • id: Será o 'id' da venda e esse campo será chave primária;
  • id_funcionario: ID do funcionário que fez a venda. Deve corresponder ao ID da tabela de funcionarios;
  • quantidade: Número de vendas realizada;
  • mes: Registra em qual mês a venda ocorreu (de 1 a 12);
  • id_produto: Mostra o que foi vendido. Deve corresponder ao ID da tabela de produtos;

Crie um arquivo init.sql no mesmo diretório onde está o container, ele deve ter o seguinte conteúdo:

init.sql
CREATE TABLE IF NOT EXISTS produtos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
valor DECIMAL(10, 2) NOT NULL
);

CREATE TABLE IF NOT EXISTS funcionarios (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
data_nascimento DATE NOT NULL
);

CREATE TABLE IF NOT EXISTS vendas (
id INT AUTO_INCREMENT PRIMARY KEY,
mes INT NOT NULL CHECK (mes BETWEEN 1 AND 12),
id_produto INT NOT NULL,
quantidade INT NOT NULL,
id_funcionario INT NOT NULL,
FOREIGN KEY (id_produto) REFERENCES produtos(id),
FOREIGN KEY (id_funcionario) REFERENCES funcionarios(id)
);

FOREIGN KEY

Uma FOREIGN KEY (chave estrangeira) é uma regra de integridade referencial usada em bancos de dados relacionais para garantir que um valor em uma tabela corresponda a um valor existente em outra tabela.


Na prática, ela cria um vínculo entre duas tabelas, uma coluna em uma tabela faz referência à chave primária de outra tabela. Isso garante que os dados estejam sempre consistentes.


Agora atualize o docker-compose.yml para incluir o script sql:

    volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
- mysql_data:/var/lib/mysql

Agora delete e suba novamente o container:

# Pare o ambiente que a gente subiu:
docker compose down -v

# Suba novamente:
docker compose up -d


Inserindo dados na tabela


Vamos ver como inserir dados na tabela:

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
result = cursor.execute("INSERT INTO produtos (nome, valor) VALUES (%s, %s)", ("Caneta", 1.99))
result += cursor.execute("INSERT INTO produtos (nome, valor) VALUES (%s, %s)", ("Caderno", 12.50))

# Inserir funcionário
result += cursor.execute("INSERT INTO funcionarios (nome, data_nascimento) VALUES (%s, %s)", ("João Silva", "1990-03-12"))
result += cursor.execute("INSERT INTO funcionarios (nome, data_nascimento) VALUES (%s, %s)", ("Maria Oliveira", "2000-03-12"))

print(f'Quantidade de linhas afetadas para o commit na criação dos produtos e funcionários: {result}')
# Aplica os "executes" acima no banco:
conn.commit()

# Zera o nosso result:
result = 0


## Agora vamos efetuar o resto do cadastro que dependia dos ID de produto e ID de funcionários:

# Ober o id do João:
cursor.execute("SELECT id FROM funcionarios WHERE nome = %s", ("João Silva",))
id_funcionario = cursor.fetchone()[0]

# Obter o id do produto caneta:
cursor.execute("SELECT id FROM produtos WHERE nome = %s", ("Caneta",))
id_caneta = cursor.fetchone()[0]

# Obter o id do produto caderno:
cursor.execute("SELECT id FROM produtos WHERE nome = %s", ("Caderno",))
id_caderno = cursor.fetchone()[0]


# Cadastrar a venda:
result += cursor.execute("INSERT INTO vendas (mes, id_produto, quantidade, id_funcionario) VALUES (%s, %s, %s, %s)", (2, id_caneta, 3, id_funcionario))
result += cursor.execute("INSERT INTO vendas (mes, id_produto, quantidade, id_funcionario) VALUES (%s, %s, %s, %s)", (2, id_caderno, 1, id_funcionario))

print(f'Quantidade de linhas afetadas para o commit no insert de dados na tabela: {result}')

# Commita no banco:
conn.commit()
print("Dados inseridos com sucesso.")


Inserindo dados na tabela usando dicionário


As vezes você recebe um dicionário e quer inserir os dados desse dict na tabela, para isso vamos usar o placeholders nomeados com %(chave)s ao invés de %s.

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

dict_produtos = {
'valor_produto': 2.50,
'nome_produto': "Borracha"
}

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
result = cursor.execute("INSERT INTO produtos (nome, valor) VALUES (%(nome_produto)s, %(valor_produto)s)", dict_produtos)

print(f'Quantidade de linhas afetadas para o commit na criação dos produtos e funcionários: {result}')
# Aplica os "executes" acima no banco:
conn.commit()

print("Dados inseridos com sucesso.")

Perceba que os nomes das chaves no dicionário são diferentes dos nomes das colunas da tabela. Isso mostra que o que realmente importa é que os placeholders na consulta SQL correspondam exatamente às chaves do dicionário, a ordem dos dados não influencia, o que influencia é a ordem em (nome, valor), você deve fornecer os dados nessa ordem..


Agora vamos inserir vários dados vindos de um dict grande. Para isso vamos usar executemany e o dicionário deve ficar dentro de uma tupla.

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

dict_produtos = (
{ 'valor_produto': 2.50, 'nome_produto': "Borracha" },
{ 'valor_produto': 3.50, 'nome_produto': "Lapis" },
{ 'valor_produto': 32.75, 'nome_produto': "grampeador" },
{ 'valor_produto': 5.60, 'nome_produto': "Corretivo" },
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
result = cursor.executemany("INSERT INTO produtos (nome, valor) VALUES (%(nome_produto)s, %(valor_produto)s)", dict_produtos)

print(f'Quantidade de linhas afetadas para o commit na criação dos produtos e funcionários: {result}')
# Aplica os "executes" acima no banco:
conn.commit()

print("Dados inseridos com sucesso.")


Lendo dados da tabela


Vamos ver como ler dados da tabela.

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
cursor.execute("select * from produtos")

# Pegar todos os valores:
read = cursor.fetchall()

## Pegar apenas um valor:
#read = cursor.fetchone()

for row in read:
print(*row)

Se você quiser, pode usar o método cursor.mogrify() para gerar a string SQL final com os valores já interpolados, mas sem executar a consulta, ou seja, você irá ver a string SQL completa, para ver como está sendo fornecida ao banco de dados, mas de uma forma que ela não seja executada.



Deletar dados da tabela


Para deletar dados de uma tabela, vamos usar o comando SQL DELETE FROM com segurança via placeholders, como em qualquer execute().

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

id_deletar = int(5)

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
cursor.execute("delete from produtos WHERE id = %s", (id_deletar,))

# Aplica o delete no banco:
conn.commit()

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
cursor.execute("select * from produtos")

# Pegar todos os valores:
read = cursor.fetchall()

# Exibe os dados da tabela:
for row in read:
print(*row)


Atualizar dados da tabela


Para atualizar dados de uma tabela, vamos usar o comando SQL UPDATE com segurança via placeholders, como em qualquer execute().

main.py
#!/usr/bin/env python3

import pymysql

### Esse módulo nao será necessário quando passar para container:
from dotenv import load_dotenv
from pathlib import Path
import os

### Caminho manual para o '.env':
dotenv_path = Path('/home/bruno/Nextcloud/docker/mysql/.env')
load_dotenv(dotenv_path=dotenv_path)

# Cria a conexão com o banco de dados:
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"]
)

# Abre uma conexão com o banco e garante que será fechada automaticamente:
with conn:

# Cria um cursor para executar comandos SQL e fecha automaticamente após o uso:
with conn.cursor() as cursor:

modificar = {
"produto_id": 2,
"valor": 72.35
}

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
cursor.execute("UPDATE produtos SET valor = %(valor)s WHERE id = %(produto_id)s", modificar)

# Aplica o delete no banco:
conn.commit()

# Os 'cursors' abaixo criam um pacote, que não fará nada no servidor até que 'commit()' seja usado:
cursor.execute("select * from produtos")

# Pegar todos os valores:
read = cursor.fetchall()

# Exibe os dados da tabela:
for row in read:
print(*row)


Outros Cursors


O pymysql oferece diferentes tipos de cursors além do padrão. Esses cursors controlam como os resultados são retornados quando você faz consultas. Isso é útil para adaptar o comportamento do cursor ao tipo de dado que você quer manipular.


Para mudar o cursor faz o seguinte, no connect() adicione cursorclass=pymysql.cursors.CURSOR_NAME:

conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"],
cursorclass=pymysql.cursors.DictCursor
)

pymysql.cursors.Cursor


Retorna cada linha como uma tupla, esse é o padrão.

(1, 'Caneta', 2.50)


pymysql.cursors.DictCursor


Retorna cada linha como um dicionário, com os nomes das colunas como chaves:

{'id': 1, 'nome': 'Caneta', 'valor': 2.50}

Uso:

with conn.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute("SELECT * FROM produtos")
for linha in cursor.fetchall():
print(linha['nome'], linha['valor'])


pymysql.cursors.SSCursor e pymysql.cursors.SSDictCursor


Esses são cursores "server-side", usados para grandes volumes de dados. Eles não carregam todos os resultados de uma vez, mas um por um, direto do servidor.

  • SSCursor: retorna tuplas, como o padrão.
  • SSDictCursor: retorna dicionários, como o DictCursor.

Uso:

with conn.cursor(pymysql.cursors.SSDictCursor) as cursor:
cursor.execute("SELECT * FROM produtos")
for linha in cursor:
print(linha)

Esses são úteis para consultas muito grandes (milhares ou milhões de linhas) onde o uso de memória importa.



Trabalhando com linhas


Os atributos rowcount, rownumber e lastrowid são propriedades de cursores em bibliotecas como pymysql e servem para obter informações sobre o resultado ou efeito de operações SQL. Cada um tem uma função específica.



cursor.rowcount


Retorna a quantidade de linhas afetadas pela última operação SQL (como INSERT, UPDATE, DELETE, ou até SELECT). Exemplo:

cursor.execute("DELETE FROM produtos WHERE valor < %s", (1.00,))
print(cursor.rowcount) # Exibe quantas linhas foram deletadas

É útil para saber se uma operação teve efeito.



cursor.rownumber


Essa propriedade é comum no psycopg2, mas não existe no pymysql. Ela indica o índice da próxima linha que será retornada num fetch. Pode ser útil ao iterar manualmente, mas não é muito usado.



cursor.lastrowid


Retorna o ID da última linha inserida em uma tabela com chave primária AUTO_INCREMENT. Exemplo:

cursor.execute("INSERT INTO produtos (nome, valor) VALUES (%s, %s)", ("Lápis", 1.00))
print(cursor.lastrowid) # ID gerado automaticamente para esse produto

É muito útil para associar esse novo ID a outras tabelas ou para saber o que acabou de ser criado.