SQLite é um dos mais populares gerenciadores de bases de dados relacionais (RDBMS). É leve, o que significa que não ocupa muito espaço no seu sistema. Uma de suas melhores características é que é servidor-livre, portanto você não precisa instalar ou gerenciar um servidor separado para usá-lo.

Ao invés disso, ele armazena tudo em um simples arquivo no seu computador. Além disso, ele não requer nenhuma configuração, portanto não há nenhum processo de configuração complicado, fazendo dele perfeito para iniciantes e pequenos projetos.

SQLite é uma ótima escolha para aplicações pequenas a médias porque é fácil de usar, rápido e consegue realizar a maioria das tarefas que bancos de dados maiores conseguem, mas sem a necessidade de gerenciar software extra. Se você está construindo um projeto pessoal ou prototipando uma nova aplicação, SQLite é uma opção sólida para iniciar rapidamente.

Neste tutorial, você vai aprender a trabalhar com SQLite usando Python. Aqui está o que vamos abordar neste tutorial:

Este tutorial é perfeito para qualquer pessoa que quer começar com bancos de dados sem mergulhar em configurações complexas.

Como configurar seu ambiente Python

Antes de trabalhar com SQLite, vamos garantir que seu ambiente Python está pronto. Aqui está como configurar tudo.

Instalando o Python

Se você ainda não tiver o Python instalado no seu sistema, você pode baixá-lo do site oficial do Python. Siga com as instruções de instalação para o seu sistema operacional (Windows, macOS ou Linux).

Para verificar se o Python está instalado, abra seu terminal (ou prompt de comando) e digite:

python --version

Isso deve mostrar a versão atual do Python instalada. Se não estiver instalada, siga com as instruções no site do Python.

Instalando o módulo SQLite3

A boa notícia é que SQLite3 vem built-in com Python! Você não precisa instalá-lo separadamente, pois já está incluído na biblioteca padrão do Python. Isso significa que você pode começar a usá-lo mesmo assim sem nenhuma configuração adicional.

É uma boa ideia criar um ambiente virtual para cada projeto para manter suas dependências organizadas. Um ambiente virtual é como um tabuleiro limpo onde você pode instalar pacotes sem afetar sua instalação global do Python.

Para criar um ambiente virtual, siga estes passos:

  1. Primeiro, abra seu terminal ou prompt de comando e navegue até o diretório onde você quer criar seu projeto.

  2. Execute o seguinte comando para criar um ambiente virtual:

python -m venv env

Aqui, env é o nome do ambiente virtual. Você pode atribuir qualquer nome que desejar.

  1. Ative o ambiente virtual:
# Use o comando para Windows
env\Scripts\activate

# Use o comando para macOS/Linux:
env/bin/activate

Após ativar o ambiente virtual, você notará que o prompt do terminal mudou, mostrando o nome do ambiente virtual. Isto significa que você agora está trabalhando dentro dele.

Instalando Bibliotecas Necessárias

Vamos precisar de algumas bibliotecas adicionais para esse projeto. Especificamente, vamos usar:

  • pandas: Esta é uma biblioteca opcional para manipular e exibir dados em formato de tabela, útil para casos avançados de uso.

  • faker: Esta biblioteca ajudará-nos a gerar dados falsos, como nomes e endereços aleatórios, que podemos inserir em nossa base de dados para fins de teste.

Para instalar pandas e faker, basta executar os seguintes comandos:

pip install pandas faker

Isso instala ambos pandas e faker na sua ambiente virtual. Com isso, o seu ambiente está configurado e você está pronto para começar a criar e gerenciar sua base de dados SQLite em Python!

Como Criar uma Base de Dados SQLite

Uma base de dados é uma maneira estruturada de armazenar e gerenciar dados de forma que sejam fáceis de acessar, atualizar e organizar. É como um sistema de arquivos digital que permite armazenar grandes quantidades de dados de forma eficiente, seja para uma aplicação simples ou um sistema mais complexo. As bases de dados usam tabelas para organizar dados, com linhas e colunas representando registros individuais e suas propriedades.

Como as bases de dados SQLite funcionam

Diferentemente de outros sistemas de banco de dados, o SQLite é um banco de dados sem servidor. Isto significa que não é necessário configurar ou gerenciar um servidor, o que o torna leve e fácil de usar. Todos os dados são armazenados em um único arquivo no seu computador, que você pode mover, compartilhar ou fazer backup facilmente. Apesar de sua simplicidade, o SQLite é o suficiente potente para lidar com muitas tarefas de banco de dados comuns e é amplamente usado em aplicativos móveis, sistemas embarcados e projetos de pequeno a médio porte.

Como Criar um Novo Banco de Dados SQLite

Vamos criar um novo banco de dados SQLite e aprender a interagir com ele usando a biblioteca sqlite3 do Python.

Conectando ao Banco de Dados

Como o sqlite3 está pré-instalado, você só precisa importá-lo em seu script Python. Para criar um novo banco de dados ou conectar a um existente, usamos o método sqlite3.connect(). Este método aceita o nome do arquivo do banco de dados como um argumento. Se o arquivo não existir, o SQLite criará automaticamente.

import sqlite3

# Conectar ao banco de dados SQLite (ou criá-lo se não existir)
connection = sqlite3.connect('my_database.db')

Neste exemplo, um arquivo chamado my_database.db é criado no mesmo diretório onde está o script. Se o arquivo já existir, o SQLite abrirá apenas a conexão com ele.

Criando um Cursor

Uma vez que você tem uma conexão, o próximo passo é criar um objeto cursor. O cursor é responsável por executar comandos e consultas SQL no banco de dados.

# Criar um objeto cursor
cursor = connection.cursor()

Fechando a Conexão

Após terminar de trabalhar com o banco de dados, é importante fechar a conexão para liberar quaisquer recursos. Você pode fechar a conexão com o seguinte comando:

# Fechar a conexão com o banco de dados
connection.close()

No entanto, você deve fechar a conexão apenas depois de terminar todas suas operações.

Quando você executar o script Python, um arquivo chamado my_database.db será criado no diretório de trabalho atual. Agora você criou seu primeiro banco de dados SQLite com sucesso!

Como Usar o Gerenciador de Contexto para Abrir e Fechar Conexões

O Python fornece uma maneira mais eficiente e mais limpa de lidar com conexões de banco de dados usando a instrução with, também conhecida como gerenciador de contexto. A instrução with abre e fecha automaticamente a conexão, garantindo que a conexão seja fechada corretamente mesmo se um erro ocorrer durante as operações de banco de dados. Isso elimina a necessidade de chamar manualmente connection.close().

Aqui está como você pode usar a instrução with para lidar com conexões de banco de dados:

import sqlite3

# Passo 1: Use 'with' para se conectar ao banco de dados (ou criar um) e fechar automaticamente quando tiver feito
with sqlite3.connect('my_database.db') as connection:

    # Passo 2: Crie um objeto cursor para interagir com o banco de dados
    cursor = connection.cursor()

    print("Database created and connected successfully!")

# Não há necessidade de chamar connection.close(); ele é feito automaticamente!

A partir de agora, vamos usar a estrutura de dados with em nossos exemplos de código futuros para gerenciar conexões com bancos de dados de forma eficiente. Isso tornará o código mais conciso e fácil de manter.

Como Criar Tabelas de Banco de Dados

Agora que temos criado um banco de dados SQLite e conectado a ele, o próximo passo é criar tabelas no banco de dados. Uma tabela é onde armazenaremos nossos dados, organizados em linhas (registros) e colunas (atributos). Neste exemplo, vamos criar uma tabela chamada Students para armazenar informações sobre alunos, que reutilizaremos nas seções seguintes.

Para criar uma tabela, usamos o comando SQL CREATE TABLE. Este comando define a estrutura da tabela, incluindo os nomes das colunas e os tipos de dados para cada coluna.

Aqui está um simples comando SQL para criar uma tabela Students com os seguintes campos:

  • id: Um identificador único para cada aluno (um inteiro).

  • nome: O nome do aluno (texto).

  • idade: A idade do aluno (um inteiro).

  • email: O endereço de email do aluno (texto).

O comando SQL para criar esta tabela teria o seguinte aspecto:

CREATE TABLE Students (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    age INTEGER,
    email TEXT
);

Nós podemos executar este comando SQL CREATE TABLE em Python usando a biblioteca sqlite3. Vamos ver como fazer isso.

import sqlite3

# Use 'with' para se conectar à base de dados SQLite e fechar automaticamente a conexão quando terminar
with sqlite3.connect('my_database.db') as connection:

    # Crie um objeto cursor
    cursor = connection.cursor()

    # Escreva o comando SQL para criar a tabela de Alunos
    create_table_query = '''
    CREATE TABLE IF NOT EXISTS Students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        email TEXT
    );
    '''

    # Executar o comando SQL
    cursor.execute(create_table_query)

    # Confirmar as mudanças
    connection.commit()

    # Exiba uma mensagem de confirmação
    print("Table 'Students' created successfully!")
  • IF NOT EXISTS: Isso garante que a tabela só será criada se ela ainda não existir, evitando erros se a tabela já tiver sido criada antes.

  • connection.commit(): Isso salva (confirma) as mudanças na base de dados.

Quando você executar o código Python acima, ele criará a tabela Alunos no arquivo de base de dados my_database.db. Você também verá uma mensagem no terminal confirmando que a tabela foi criada com sucesso.

Se você estiver usando o Visual Studio Code, você pode instalar a extensão SQLite Viewer para visualizar bancos de dados SQLite.

Tipos de Dados no SQLite e Sua Correspondência com Tipos em Python

O SQLite suporta vários tipos de dados, os quais precisamos entender ao definir nossas tabelas. Aqui está uma visão geral rápida dos tipos de dados comuns no SQLite e como eles se correspondem aos tipos em Python:

Tipo de Dado no SQLite Descrição Equivalente em Python
INTEGER Números inteiros int
TEXT Cadeias de texto str
REAL Números de ponto flutuante float
BLOB Dados binários (por exemplo, imagens, arquivos) bytes
NULL Representa nenhum valor ou dado ausente None

Em nossa tabela Students:

  • id é do tipo INTEGER, que corresponde ao int do Python.

  • name e email são do tipo TEXT, que correspondem ao str do Python.

  • idade também é do tipo INTEGER, mapeado para o Python int.

Como Inserir Dados em uma Tabela

Agora que nossa tabela Alunos foi criada, é hora de começar a inserir dados no banco de dados. Nessa seção, vamos aprender a inserir registros individuais e múltiplos usando Python e SQLite, e como evitar problemas de segurança comuns, como o ataque de injeção de SQL, usando consultas parametrizadas.

Como Inserir um Registro Único

Para inserir dados no banco de dados, usamos o comando SQL INSERT INTO. Vamos começar inserindo um único registro na nossa tabela Alunos.

Aqui está a sintaxe básica SQL para inserir um registro único:

INSERT INTO Students (name, age, email) 
VALUES ('John Doe', 20, '[email protected]');

No entanto, em vez de escrever SQL diretamente no nosso script Python com valores codificados, vamos usar consultas parametrizadas para tornar o nosso código mais seguro e flexível. As consultas parametrizadas ajudam a prevenir o ataque de injeção de SQL, um ataque comum em que os usuários maliciosos podem manipular a query SQL passando entradas prejudiciais.

Aqui é como podemos inserir um registro único na tabela Alunos usando uma consulta parametrizada:

import sqlite3

# Use 'with' to open and close the connection automatically
with sqlite3.connect('my_database.db') as connection:
    cursor = connection.cursor()

    # Inserir um registro na tabela Students
    insert_query = '''
    INSERT INTO Students (name, age, email) 
    VALUES (?, ?, ?);
    '''
    student_data = ('Jane Doe', 23, '[email protected]')

    cursor.execute(insert_query, student_data)

    # Commit the changes automatically
    connection.commit()

    # Não é necessário chamar connection.close(); é feito automaticamente!
    print("Record inserted successfully!")

Os marcadores de posição ? representam os valores a serem inseridos na tabela. Os valores reais são passados como um tuplo (student_data) no método cursor.execute().

Como Inserir Múltiplos Registros

Se você quiser inserir múltiplos registros de uma só vez, você pode usar o método executemany() em Python. Este método pega uma lista de tuplos, onde cada tuplo representa um registro.

Para tornar nosso exemplo mais dinâmico, podemos usar a biblioteca Faker para gerar dados de estudante aleatórios. Isso é útil para testes e simulando cenários reais.

from faker import Faker
import sqlite3

# Initialize Faker
fake = Faker(['en_IN'])

# Use 'with' to open and close the connection automatically
with sqlite3.connect('my_database.db') as connection:
    cursor = connection.cursor()

    # Inserir um registro na tabela Students
    insert_query = '''
    INSERT INTO Students (name, age, email) 
    VALUES (?, ?, ?);
    '''
    students_data = [(fake.name(), fake.random_int(
        min=18, max=25), fake.email()) for _ in range(5)]

    # Execute the query for multiple records
    cursor.executemany(insert_query, students_data)

    # Commit the changes
    connection.commit()

    # Print confirmation message
    print("Fake student records inserted successfully!")

Neste código:

  • Faker() gera nomes, idades e e-mails aleatórios para estudantes. Passar a localidade ([‘pt_BR’]) é opcional.

  • cursor.executemany(): Este método permite inserir vários registros de uma vez, tornando o código mais eficiente.

  • students_data: Uma lista de tuplas onde cada tupla representa os dados de um estudante.

Como Lidar com Problemas Comuns: Injeção SQL

A injeção SQL é uma vulnerabilidade de segurança onde os atacantes podem inserir ou manipular consultas SQL fornecendo entrada prejudicial. Por exemplo, um atacante pode tentar injetar código como '; DROP TABLE Students; -- para excluir a tabela.

Usando consultas parametrizadas (como demonstrado acima), evitamos esse problema. Os espaços reservados ? em consultas parametrizadas garantem que os valores de entrada sejam tratados como dados, não como parte do comando SQL. Isso torna impossível a execução de código malicioso.

Como Fazer Consultas de Dados

Agora que temos inserido algum dado na nossa tabela Students, vamos aprender como recuperar dados da tabela. Vamos explorar diferentes métodos para buscar dados em Python, incluindo fetchone(), fetchall() e fetchmany().

Para consultar dados de uma tabela, usamos a instrução SELECT. Aqui está um comando SQL simples para selecionar todas as colunas da tabela Students:

SELECT * FROM Students;

Este comando recupera todos os registros e colunas da tabela Students. Nós podemos executar essa consulta SELECT em Python e buscar os resultados.

Como Buscar Todos os Registros

Veja como podemos buscar todos os registros da tabela Students:

import sqlite3

# Use 'with' para se conectar à base de dados SQLite
with sqlite3.connect('my_database.db') as connection:

    # Crie um objeto cursor
    cursor = connection.cursor()

    # Escreva o comando SQL para selecionar todos os registros da tabela Students
    select_query = "SELECT * FROM Students;"

    # Execute o comando SQL
    cursor.execute(select_query)

    # Busque todos os registros
    all_students = cursor.fetchall()

    # Exiba resultados no terminal
    print("All Students:")
    for student in all_students:
        print(student)

Neste exemplo, o método fetchall() recupera todas as linhas retornadas pela consulta como uma lista de tuplas.

All Students:
(1, 'Jane Doe', 23, '[email protected]')
(2, 'Bahadurjit Sabharwal', 18, '[email protected]')
(3, 'Zayyan Arya', 20, '[email protected]')
(4, 'Hemani Shukla', 18, '[email protected]')
(5, 'Warda Kara', 20, '[email protected]')
(6, 'Mitali Nazareth', 19, '[email protected]')

Como Buscar um Registro Único

Se você quiser recuperar apenas um único registro, você pode usar o método fetchone():

import sqlite3

# Use 'with' para conectar à base de dados SQLite
with sqlite3.connect('my_database.db') as connection:

    # Crie um objeto cursor
    cursor = connection.cursor()

    # Escreva o comando SQL para selecionar todos os registros da tabela Students
    select_query = "SELECT * FROM Students;"

    # Execute o comando SQL
    cursor.execute(select_query)

    # Busque um registro
    student = cursor.fetchone()

    # Exiba o resultado
    print("First Student:")
    print(student)

Output:

First Student:
(1, 'Jane Doe', 23, '[email protected]')

Como buscar vários registros

Para buscar um número específico de registros, você pode usar fetchmany(tamanho):

import sqlite3

# Use 'with' para conectar à base de dados SQLite
with sqlite3.connect('my_database.db') as connection:

    # Crie um objeto cursor
    cursor = connection.cursor()

    # Escreva o comando SQL para selecionar todos os registros da tabela Students
    select_query = "SELECT * FROM Students;"

    # Execute o comando SQL
    cursor.execute(select_query)

    # Busque três registros
    three_students = cursor.fetchmany(3)

    # Exiba os resultados
    print("Three Students:")
    for student in three_students:
        print(student)

Output:

Three Students:
(1, 'Jane Doe', 23, '[email protected]')
(2, 'Bahadurjit Sabharwal', 18, '[email protected]')
(3, 'Zayyan Arya', 20, '[email protected]')

Como usar pandas para uma melhor apresentação de dados

Para uma melhor apresentação de dados, podemos usar a biblioteca pandas para criar um DataFrame a partir dos nossos resultados de consulta. Isso facilita a manipulação e visualização dos dados.

Aqui está como buscar todos os registros e exibi-los como um DataFrame pandas:

import sqlite3
import pandas as pd

# Use 'with' to connect to the SQLite database
with sqlite3.connect('my_database.db') as connection:
    # Write the SQL command to select all records from the Students table
    select_query = "SELECT * FROM Students;"

    # Use pandas to read SQL query directly into a DataFrame
    df = pd.read_sql_query(select_query, connection)

# Display the DataFrame
print("All Students as DataFrame:")
print(df)

Output:

All Students as DataFrame:
   id                  name  age                        email
0   1              Jane Doe   23             [email protected]
1   2  Bahadurjit Sabharwal   18  [email protected]
2   3           Zayyan Arya   20  [email protected]
3   4         Hemani Shukla   18    [email protected]
4   5            Warda Kara   20           [email protected]
5   6       Mitali Nazareth   19          [email protected]

A função pd.read_sql_query() executa a consulta SQL e retorna diretamente os resultados como um DataFrame do pandas.

Como Atualizar e Excluir Dados

Nesta seção, vamos aprender a atualizar registros existentes e excluir registros da nossa tabela Students usando comandos SQL em Python. Isso é essencial para gerenciar e manter seus dados de forma eficiente.

Atualizar Registros Existentes

Para modificar registros existentes em um banco de dados, usamos o comando SQL UPDATE. Este comando permite que mudemos os valores de colunas específicas em uma ou mais linhas com base em uma condição especificada.

Por exemplo, se quisermos atualizar a idade de um aluno, o comando SQL teria o seguinte aspecto:

UPDATE Students 
SET age = 21 
WHERE name = 'Jane Doe';

Agora, vamos escrever código Python para atualizar a idade de um aluno específico na nossa tabela Students.

import sqlite3

# Utilize 'with' para se conectar à base de dados SQLite
with sqlite3.connect('my_database.db') as connection:
    cursor = connection.cursor()

    # Comando SQL para atualizar a idade de um aluno
    update_query = '''
    UPDATE Students 
    SET age = ? 
    WHERE name = ?;
    '''

    # Dados para a atualização
    new_age = 21
    student_name = 'Jane Doe'

    # Executar o comando SQL com os dados
    cursor.execute(update_query, (new_age, student_name))

    # Confirmar as mudanças para salvar a atualização
    connection.commit()

    # Imprimir uma mensagem de confirmação
    print(f"Updated age for {student_name} to {new_age}.")

Neste exemplo, utilizamos consultas parametrizadas para prevenir injeção de SQL.

Como Excluir Registros da Tabela

Para remover registros de um banco de dados, usamos o comando SQL DELETE. Este comando nos permite excluir uma ou mais linhas com base em uma condição específica.

Por exemplo, se quisermos excluir um aluno chamado ‘Jane Doe’, o comando SQL teria este aspecto:

DELETE FROM Students 
WHERE name = 'Jane Doe';

Vamos escrever um código Python para excluir um aluno específico de nossa tabela Students usando a instrução with.

import sqlite3

# Utilize 'with' para se conectar à base de dados SQLite
with sqlite3.connect('my_database.db') as connection:
    cursor = connection.cursor()

    # Comando SQL para excluir um aluno
    delete_query = '''
    DELETE FROM Students 
    WHERE name = ?;
    '''

    # Nome do aluno a ser excluído
    student_name = 'Jane Doe'

    # Executar o comando SQL com os dados
    cursor.execute(delete_query, (student_name,))

    # Confirmar as mudanças para salvar a exclusão
    connection.commit()

    # Imprimir uma mensagem de confirmação
    print(f"Deleted student record for {student_name}.")

Considerações Importantes

  • Condições: Sempre use a cláusula WHERE ao atualizar ou excluir registros para evitar modificar ou remover todas as linhas na tabela. Sem a cláusula WHERE, o comando afeta todas as linhas na tabela.

  • Backup: É uma boa prática fazer backup de seu banco de dados antes de realizar atualizações ou exclusões, especialmente em ambientes de produção.

Como Usar Transações

Uma transação é uma seqüência de uma ou mais operações SQL que são tratadas como uma única unidade de trabalho. No contexto de um banco de dados, uma transação permite que você realize várias operações que sejam todas bem-sucedidas ou nenhuma delas. Isso garante que seu banco de dados permaneça em um estado consistente, mesmo diante de erros ou problemas inesperados.

Por exemplo, se você estiver transferindo dinheiro entre duas contas bancárias, você gostaria que ambas as debitações de uma conta e o crédito na outra fossem bem-sucedidas ou falhasssem juntas. Se uma operação falhar, a outra não deve ser executada para manter a consistência.

Por que usar transações?

  1. Atomicity: As transações garantem que uma série de operações é tratada como uma unidade única. Se uma operação falhar, nenhuma das operações será aplicada no banco de dados.

  2. Consistency: As transações auxiliam a manter a integridade do banco de dados, garantindo que todas as regras e restrições forem seguidas.

  3. Isolation: Cada transação opera independentemente das outras, evitando interferências não intencionadas.

  4. Durability: Uma vez que uma transação é confirmada, as mudanças são permanentes, mesmo em caso de falha do sistema.

Quando usar Transações?

Você deve usar transações quando:

  • Realizar múltiplas operações relacionadas que devem ser bem-sucedidas ou falhar juntas.

  • Modificar dados críticos que requerem consistência e integridade.

  • Trabalhando com operações que podem falhar, como transações financeiras ou migrações de dados.

Como Gerenciar Transações em Python

No SQLite, as transações são gerenciadas usando os comandos BEGIN, COMMIT, e ROLLBACK. No entanto, quando usando o módulo sqlite3 em Python, você gerencia as transações através do objeto de conexão.

Iniciando uma Transação

Uma transação começa implícitamente quando você executa qualquer instrução SQL. Para iniciar uma transação explicitamente, você pode usar o comando BEGIN:

cursor.execute("BEGIN;")

No entanto, normalmente é desnecessário iniciar uma transação manualmente, pois o SQLite inicia uma transação automaticamente quando você executa uma instrução SQL.

Como Comitar uma Transação

Para salvar todas as mudanças feitas durante uma transação, você usa o método commit(). Isso torna todas as modificações permanentes no banco de dados.

connection.commit()

Nós já usamos o método commit() nos exemplos fornecidos acima.

Rolling Back a Transaction

Se algo der errado e você quiser reverter as mudanças feitas durante uma transação, você pode usar o método rollback(). Isso desfaz todas as mudanças feitas desde o início da transação.

connection.rollback()

Exemplo de Uso de Transações em Python

Para ilustrar o uso de transações em um cenário real do mundo, vamos criar uma nova tabela chamada Clientes para gerenciar as contas de clientes. Neste exemplo, assumiremos que cada cliente tem um saldo. Vamos adicionar dois clientes a esta tabela e realizar uma operação de transferência de fundos entre eles.

Primeiro, vamos criar a tabela Clientes e inserir dois clientes:

import sqlite3

# Criar a tabela Clientes e adicionar dois clientes
with sqlite3.connect('my_database.db') as connection:
    cursor = connection.cursor()

    # Criar tabela Clientes
    create_customers_table = '''
    CREATE TABLE IF NOT EXISTS Customers (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL UNIQUE,
        balance REAL NOT NULL
    );
    '''
    cursor.execute(create_customers_table)

    # Inserir dois clientes
    cursor.execute(
        "INSERT INTO Customers (name, balance) VALUES (?, ?);", ('Ashutosh', 100.0))
    cursor.execute(
        "INSERT INTO Customers (name, balance) VALUES (?, ?);", ('Krishna', 50.0))

    connection.commit()

Agora, vamos realizar a operação de transferência de fundos entre Ashutosh e Krishna:

import sqlite3


def transfer_funds(from_customer, to_customer, amount):
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        try:
            # Iniciar uma transação
            cursor.execute("BEGIN;")

            # Subtrair quantia do remetente
            cursor.execute(
                "UPDATE Customers SET balance = balance - ? WHERE name = ?;", (amount, from_customer))
            # Adicionar quantia ao destinatário
            cursor.execute(
                "UPDATE Customers SET balance = balance + ? WHERE name = ?;", (amount, to_customer))

            # Comitar as mudanças
            connection.commit()
            print(
                f"Transferred {amount} from {from_customer} to {to_customer}.")

        except Exception as e:
            # Se ocorrer um erro, desfazer a transação
            connection.rollback()
            print(f"Transaction failed: {e}")


# Exemplo de uso
transfer_funds('Ashutosh', 'Krishna', 80.0)

Neste exemplo, primeiro criamos uma tabela Clientes e inserimos dois clientes, Ashutosh com um saldo de ₹100, e Krishna com um saldo de ₹50. Em seguida, realizamos um transferência de fundos de ₹80 de Ashutosh para Krishna. Utilizando transações, garantimos que ambos os débitos de conta de Ashutosh e o crédito na conta de Krishna forem executados como uma única operação atómica, mantendo a integridade dos dados em caso de quaisquer erros. Se a transferência falhar (por exemplo, devido a fundos insuficientes), a transação será revertida, deixando as duas contas inalteradas.

Como Otimizar o Desempenho de Consultas do SQLite com Indexação

A indexação é uma técnica poderosa usada em bases de dados para melhorar o desempenho de consultas. Um índice, essencialmente, é uma estrutura de dados que armazena a localização das linhas com base em valores de colunas específicas, semelhante a um índice no final de um livro que nos ajuda a localizar rapidamente um tópico.

Sem uma indexação, o SQLite precisa scanner toda a tabela linha por linha para encontrar os dados relevantes, o que se torna ineficiente conforme o conjunto de dados cresce. Utilizando um índice, o SQLite pode pular diretamente para as linhas necessárias, o que acelera significativamente a execução das consultas.

Como Preencher o Banco de Dados com Dados Falsos

Para testar eficazmente o impacto da indexação, precisamos de um conjunto de dados razoavelmente grande. Em vez de adicionar registros manualmente, podemos usar a biblioteca faker para gerar dados falsos rapidamente. Nessa seção, vamos gerar 10.000 registros falsos e inserí-los na nossa tabela Alunos. Isto simulará um cenário do mundo real onde as bases de dados crescem e o desempenho das consultas se torna importante.

Vamos usar o método executemany() para inserir os registros conforme abaixo:

import sqlite3
from faker import Faker

# Inicializar a biblioteca Faker
fake = Faker(['en_IN'])


def insert_fake_students(num_records):
    """Generate and insert fake student data into the Students table."""
    fake_data = [(fake.name(), fake.random_int(min=18, max=25),
                  fake.email()) for _ in range(num_records)]

    # Usar 'with' para manipular a conexão com o banco de dados
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Inserir dados falsos na tabela Students
        cursor.executemany('''
        INSERT INTO Students (name, age, email) 
        VALUES (?, ?, ?);
        ''', fake_data)

        connection.commit()

    print(f"{num_records} fake student records inserted successfully.")


# Inserir 10.000 registros falsos na tabela Students
insert_fake_students(10000)

Ao executar este script, 10.000 registros de estudantes falsos serão adicionados à tabela Students. Na próxima seção, nós consultaremos o banco de dados e compararemos o desempenho das consultas com e sem índices.

Como Consultar Sem Índices

Nesta seção, nós consultaremos a tabela Students sem nenhum índice para observar como o SQLite age quando não há nenhuma otimização em vigor. Isto servirá como uma linha de base para comparar o desempenho quando adicionamos índices depois.

Sem índices, o SQLite realiza uma scanner de tabela inteira, o que significa que ele deve verificar todas as linhas da tabela para encontrar resultados de match. Para pequenos conjuntos de dados, isso é manejável, mas conforme o número de registros cresce, o tempo gasto para a busca aumenta dramaticamente. Vamos ver isso em ação executando uma consulta básica de SELECT para procurar um estudante específico por nome e medir quanto tempo leva para executar.

Primeiro, nós consultaremos a tabela Students procurando por um estudante com um nome específico. Nós vamos registrar o tempo de execução da consulta usando o módulo time do Python para medir o desempenho.

import sqlite3
import time


def query_without_index(search_name):
    """Query the Students table by name without an index and measure the time taken."""

    # Conectar com o banco de dados usando 'with'
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Medir a hora de início
        start_time = time.perf_counter_ns()

        # Executar uma consulta SELECT para encontrar um aluno por nome
        cursor.execute('''
        SELECT * FROM Students WHERE name = ?;
        ''', (search_name,))

        # Recuperar todos os resultados (no prática deve haver apenas um ou poucos)
        results = cursor.fetchall()

        # Medir a hora de fim
        end_time = time.perf_counter_ns()

        # Calcular o tempo total gasto
        elapsed_time = (end_time - start_time) / 1000

        # Exibir os resultados e o tempo gasto
        print(f"Query completed in {elapsed_time:.5f} microseconds.")
        print("Results:", results)


# Exemplo: Pesquisar um aluno por nome
query_without_index('Ojasvi Dhawan')

Aqui está a saída:

Query completed in 1578.10000 microseconds.
Results: [(104, 'Ojasvi Dhawan', 21, '[email protected]')]

Ao executar o script acima, verá quanto tempo leva para pesquisar na tabela Students sem nenhuma índice. Por exemplo, se houver 10.000 registros na tabela, a consulta pode levar de 1000 a 2000 microsegundos, dependendo do tamanho da tabela e do hardware. Isto pode não parecer muito lento para um pequeno conjunto de dados, mas a performance degrada conforme mais registros são adicionados.

Nós usamos time.perf_counter_ns() para medir o tempo de execução da consulta em nanosegundos. Este método é altamente preciso para benchmarkar pequenos intervalos de tempo. Convertemos o tempo para microsegundos (us) para melhor leitura.

Apresentando o Plano de Consulta

Ao trabalhar com bancos de dados, entender como as consultas são executadas pode ajudar a identificar pontos de bottleneck de desempenho e a otimizar seu código. O SQLite fornece uma ferramenta útil para isso chamada EXPLAIN QUERY PLAN, que permite que você analyze os passos que o SQLite toma para recuperar dados.

Nesta seção, nós introduziremos como usar EXPLAIN QUERY PLAN para visualizar e entender o funcionamento interno de uma consulta – especificamente, como o SQLite realiza um scan de tabela completo quando não há nenhum índice presente.

Vamos usar EXPLAIN QUERY PLAN para ver como o SQLite recupera dados da tabela Students sem nenhum índice. Vamos procurar um aluno por nome, e o plano de consulta revelará os passos que o SQLite toma para encontrar as linhas correspondentes.

import sqlite3


def explain_query(search_name):
    """Explain the query execution plan for a SELECT query without an index."""

    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Use EXPLAIN QUERY PLAN para analisar como a consulta é executada
        cursor.execute('''
        EXPLAIN QUERY PLAN
        SELECT * FROM Students WHERE name = ?;
        ''', (search_name,))

        # Busque e mostre o plano de consulta
        query_plan = cursor.fetchall()

        print("Query Plan:")
        for step in query_plan:
            print(step)


# Exemplo: Análise do plano de consulta para a busca por nome
explain_query('Ojasvi Dhawan')

Quando você executar este código, o SQLite retornará um resumo de como planeja executar a consulta. Aqui está um exemplo de como a saída pode parecer:

Query Plan:
(2, 0, 0, 'SCAN Students')

Isso indica que o SQLite está fazendo um scan de tabela inteiro (full table scan) para encontrar as linhas onde a coluna name corresponde ao valor fornecido (Ojasvi Dhawan). since there is no index on the name column, SQLite must examine each row in the table.

Como criar um índice

Criar um índice em uma coluna permite que o SQLite encontre linhas mais rápido durante as operações de consulta. Em vez de scanner toda a tabela, o SQLite pode usar o índice para pular diretamente para as linhas relevantes, acelerando significativamente as consultas — especialmente aquelas envolvendo grandes conjuntos de dados.

Para criar um índice, use o seguinte comando SQL:

CREATE INDEX IF NOT EXISTS index-name ON table (column(s));

Neste exemplo, vamos criar um índice na coluna name da tabela Students. Veja como você pode fazer isso usando o Python:

import sqlite3
import time


def create_index():
    """Create an index on the name column of the Students table."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Comando SQL para criar um índice na coluna name
        create_index_query = '''
        CREATE INDEX IF NOT EXISTS idx_name ON Students (name);
        '''

        # Medir o tempo de início
        start_time = time.perf_counter_ns()

        # Executar o comando SQL para criar o índice
        cursor.execute(create_index_query)

        # Medir o tempo de início
        end_time = time.perf_counter_ns()

        # Comitar as mudanças
        connection.commit()

        print("Index on 'name' column created successfully!")

        # Calcular o tempo total gasto
        elapsed_time = (end_time - start_time) / 1000

        # Exibir os resultados e o tempo gasto
        print(f"Query completed in {elapsed_time:.5f} microseconds.")


# Chamar a função para criar o índice
create_index()

Output:

Index on 'name' column created successfully!
Query completed in 102768.60000 microseconds.

Ainda que criar o índice leve este tempo (102768,6 microsegundos), é uma operação apenas uma vez. Você ainda obterá uma aceleração substancial quando executar várias consultas. Nas seções seguintes, nós consultaremos novamente o banco de dados para observar as melhorias de desempenho possíveis com este índice.

Como consultar com índices

Nesta seção, performaremos a mesma consulta SELECT que executamos anteriormente, mas desta vez, aproveitaremos do índice que criamos na coluna name da tabela Students. Mediremos e registraremos o tempo de execução para observarmos as melhorias de desempenho fornecidas pelo índice.

import sqlite3
import time


def query_with_index(student_name):
    """Query the Students table using an index on the name column."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Comando SQL para selecionar um aluno por nome
        select_query = 'SELECT * FROM Students WHERE name = ?;'

        # Medir o tempo de execução
        start_time = time.perf_counter_ns()  # Iniciar o cronômetro

        # Executar a consulta com o nome do aluno fornecido
        cursor.execute(select_query, (student_name,))
        result = cursor.fetchall()  # Recuperar todos os resultados

        end_time = time.perf_counter_ns()  # Parar o cronômetro

        # Calcular o tempo decorrido em microsegundos
        execution_time = (end_time - start_time) / 1000

        # Exibir resultados e tempo de execução
        print(f"Query result: {result}")
        print(f"Execution time with index: {execution_time:.5f} microseconds")


# Exemplo: Pesquisando um aluno por nome
query_with_index('Ojasvi Dhawan')

Aqui’s o que obtemos no output:

Query result: [(104, 'Ojasvi Dhawan', 21, '[email protected]')]
Execution time with index: 390.70000 microseconds

Podemos observar uma redução significativa no tempo de execução em comparação com quando a consulta era executada sem um índice.

Vamos analisar o plano de execução da consulta para a consulta com o índice na coluna name da tabela Students. Se você executar o mesmo script novamente para explicar a consulta, você obterá a saída abaixo:

Query Plan:
(3, 0, 0, 'SEARCH Students USING INDEX idx_name (name=?)')

O plano agora mostra que a consulta usa o índice idx_name, reduzindo significativamente o número de linhas que precisam ser扫描, o que resulta em execução de consulta mais rápida.

Comparação de Resultados de Performance

Agora, vamos resumir os resultados de desempenho obtidos ao realizar consultas com e sem índices.

Comparação de Tempo de Execução

Tipo de Consulta Tempo de Execução (microsegundos)
Sem Índice 1578,1
Com Índice 390,7

Resumo da Melhoria de Desempenho

  • A consulta com índice é aproximadamente 4,04 vezes mais rápida que a consulta sem índice.

  • O tempo de execução melhorou em cerca de 75,24% após a adição do índice.

Práticas Recomendadas para Usar Índices

Índices podem melhorar significativamente o desempenho do seu banco de dados SQLite, mas devem ser usados com sabedoria. Aqui estão algumas práticas recomendadas a serem consideradas ao trabalhar com índices:

Quando e por que usar índices

  1. Colunas de consulta frequentes: Use índices em colunas que são frequentemente usadas em consultas SELECT, especialmente aquelas usadas em cláusulas WHERE, JOIN e ORDER BY. Isso porque a indexação destas colunas pode reduzir significativamente o tempo de execução das consultas.

  2. Constraints de Unicidade: Quando você tem colunas que devem manter valores únicos (como nomes de usuário ou endereços de e-mail), criar um índice pode enforcar essa restrição de forma eficiente.

  3. Conjuntos de Dados Grandes: Para tabelas com um grande número de registros, os índices se tornam cada vez mais benéficos. Eles permitem consultas rápidas, o que é essencial para manter o desempenho conforme seus dados crescem.

  4. Índices compostos: Considere criar índices compostos para consultas que filtram ou classificam por várias colunas. Por exemplo, se você frequentemente procura por alunos por ambos nome e idade, um índice em ambas as colunas pode otimizar essas consultas.

Potenciais desvantagens dos índices

Enquanto os índices oferecem vantagens significativas, existem algumas potenciais desvantagens:

  1. Operações de inserção/atualização mais lentas: Quando você insere ou atualiza registros em uma tabela com índices, o SQLite também deve atualizar o índice, o que pode desacelerar essas operações. Isto ocorre porque cada inserção ou atualização exige overheard adicional para manter a estrutura do índice.

  2. Requisições de armazenamento aumentadas: Índices consomem espaço de disco adicional. Para tabelas grandes, o custo de armazenamento pode ser substancial. Considerar isso quando estiver projetando seu esquema de banco de dados, especialmente para sistemas com recursos de armazenamento limitados.

  3. Gestão de Índices Complexos: Ter muitos índices pode complicar a gestão de bases de dados. Pode levar a situações onde há índices redundantes, o que pode degradar o desempenho em vez de melhorá-lo. Rever e optimizar regularmente seus índices é uma boa prática.

Índices são ferramentas poderosas para otimizar consultas de banco de dados, mas exigem consideração cuidadosa. Encontrar o equilíbrio entre o melhor desempenho de leitura e o potencial sobrecarga em operações de gravação é chave. Aqui estão algumas estratégias para alcançar esse equilíbrio:

  • Monitorar Desempenho de Consultas: Use o EXPLAIN QUERY PLAN do SQLite para analisar como suas consultas performam com e sem índices. Isso pode ajudar a identificar quais índices são benéficos e quais podem ser desnecessários.

  • Manutenção Periódica: Revise regularmente seus índices e avalie se eles ainda são necessários. Remova índices redundantes ou raramente usados para streamline as operações de sua base de dados.

  • Testar e Avaliar: Antes de implementar índices em um ambiente de produção, realizar testes aprofundados para entender o impacto tanto nas operações de leitura quanto nas operações de gravação.

Ao seguir essas melhores práticas, você pode aproveitar os benefícios da indexação enquanto minimiza os potenciais desvantagens, melhorando assim a performance e eficiência do seu banco de dados SQLite.

Como lidar com Erros e Exceções

Nesta seção, vamos discutir como lidar com erros e exceções ao trabalhar com SQLite em Python. A correta gerenciamento de erros é crucial para manter a integridade do seu banco de dados e para garantir que seu aplicativo se comporta de maneira predizível.

Erros Comuns em Operações SQLite

Ao interagir com um banco de dados SQLite, podem ocorrer vários erros comuns:

  1. Violações de Constraint: Este erro ocorre quando você tenta inserir ou atualizar dados que violam uma restrição do banco de dados, como a unicidade do primary key ou restrições de foreign key. Por exemplo, tentar inserir uma chave primária duplicada disparará um erro.

  2. Conflitos de Tipo de Dado: Tentar inserir dados do tipo errado (por exemplo, inserir uma string onde é esperado um número) pode resultar em um erro.

  3. Erros de Banco de Dados Bloqueado: Se um banco de dados está sendo gravado por outro processo ou conexão, tentar acessá-lo pode resultar em um erro “banco de dados está bloqueado”.

  4. Erros de Sintaxe: Erros nas suas instruções SQL resultarão em erros quando você tentar executar seus comandos.

Como Usar a Exception Handling do Python

As mecanismos de tratamento de exceções integrado do Python (try e except) são essenciais para gerenciar erros nas operações SQLite. Utilizando estes construtos, você pode pegar exceções e responder apropriadamente sem quebrar seu programa.

Aqui está um exemplo básico de como lidar com erros ao inserir dados no banco de dados:

import sqlite3


def add_customer_with_error_handling(name, balance):
    """Add a new customer with error handling."""
    try:
        with sqlite3.connect('my_database.db') as connection:
            cursor = connection.cursor()
            cursor.execute(
                "INSERT INTO Customers (name, balance) VALUES (?, ?);", (name, balance))
            connection.commit()
            print(f"Added customer: {name} with balance: {balance}")

    except sqlite3.IntegrityError as e:
        print(f"Error: Integrity constraint violated - {e}")

    except sqlite3.OperationalError as e:
        print(f"Error: Operational issue - {e}")

    except Exception as e:
        print(f"An unexpected error occurred: {e}")


# Exemplo de uso
add_customer_with_error_handling('Vishakha', 100.0)  # Válido
add_customer_with_error_handling('Vishakha', 150.0)  # Entrada duplicada

Neste exemplo:

  • Nós pegamos IntegrityError, que é levantado para violações como restrições de unicidade.

  • Nós pegamos OperationalError para problemas relacionados a base de dados gerais (como erros de banco de dados bloqueado).

  • Também temos um bloco except genérico para lidar com quaisquer exceções inesperadas.

Saída:

Added customer: Vishakha with balance: 100.0
Error: Integrity constraint violated - UNIQUE constraint failed: Customers.name

Práticas Recomendadas para Garantir a Integridade do Banco de Dados

  1. Usar Transações: Sempre use transações (como discutido na seção anterior) quando realizar várias operações relacionadas. Isto ajuda a garantir que either todas as operações forem bem-sucedidas ou nenhuma, mantendo consistência.

  2. Validar Dados de Entrada: Antes de executar comandos SQL, valide os dados de entrada para garantir que eles atendam aos critérios esperados (por exemplo, tipos corretos, dentro de intervalos permitidos).

  3. Capturar Exceções Específicas: Sempre capture exceções específicas para lidar com diferentes tipos de erros apropriadamente. Isto permite uma tratamento de erros mais claro e melhora a depuração.

  4. Registrar Erros: Em vez de apenas imprimir erros na consola, considere registrar-los em um arquivo ou sistema de monitoramento. Isso ajudará você a rastrear problemas em produção.

  5. Degradação Elegante: Desenhe sua aplicação para lidar com erros de forma elegante. Se uma operação falhar, forneça um feedback significativo ao usuário em vez de fechar a aplicação.
  6. Backup de Dados Regular: Faça backup regularmente de seu banco de dados para evitar perda de dados em caso de falhas críticas ou corrupção.

  7. Use instruções preparadas: As instruções preparadas ajudam a prevenir ataques de injeção SQL e também podem proporcionar melhor desempenho para consultas repetidas.

Como Exportar e Importar Dados [Seção Bônus]

Nesta seção, vamos aprender a exportar dados de um banco de dados SQLite para formatos comuns como CSV e JSON, bem como como importar dados para SQLite a partir destes formatos usando Python. Isso é útil para compartilhamento de dados, backup e integração com outros aplicativos.

Exportando Dados do SQLite para CSV

A exportação de dados para um arquivo CSV (Valores Separados por Vírgulas) é direta com as bibliotecas internas do Python. Os arquivos CSV são amplamente usados para armazenamento e troca de dados, tornando-se um formato conveniente para a exportação de dados.

Aqui está como exportar dados de uma tabela SQLite para um arquivo CSV:

import sqlite3
import csv

def export_to_csv(file_name):
    """Export data from the Customers table to a CSV file."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Executar uma consulta para obter todos os dados do cliente
        cursor.execute("SELECT * FROM Customers;")
        customers = cursor.fetchall()

        # Escrever dados para CSV
        with open(file_name, 'w', newline='') as csv_file:
            csv_writer = csv.writer(csv_file)
            csv_writer.writerow(['ID', 'Name', 'Balance'])  # Escrevendo cabeçalho
            csv_writer.writerows(customers)  # Escrevendo linhas de dados

        print(f"Data exported successfully to {file_name}.")

# Exemplo de uso
export_to_csv('customers.csv')

Como exportar dados para JSON

De forma similar, você pode exportar dados para um arquivo JSON (Notação de Objeto JavaScript), que é um formato popular para intercâmbio de dados, especialmente em aplicações web.

Aqui está um exemplo de como exportar dados para JSON:

import json
import sqlite3


def export_to_json(file_name):
    """Export data from the Customers table to a JSON file."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        # Executar uma consulta para obter todos os dados do cliente
        cursor.execute("SELECT * FROM Customers;")
        customers = cursor.fetchall()

        # Converta dados em uma lista de dicionários
        customers_list = [{'ID': customer[0], 'Name': customer[1],
                           'Balance': customer[2]} for customer in customers]

        # Escrever dados para JSON
        with open(file_name, 'w') as json_file:
            json.dump(customers_list, json_file, indent=4)

        print(f"Data exported successfully to {file_name}.")


# Exemplo de uso
export_to_json('customers.json')

Como importar dados para SQLite de CSV

Você também pode importar dados de um arquivo CSV para uma base de dados SQLite. Isso é útil para preencher sua base de dados com conjuntos de dados existentes.

Aqui está como importar dados the um arquivo CSV:

import csv
import sqlite3


def import_from_csv(file_name):
    """Import data from a CSV file into the Customers table."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        Abrir o arquivo CSV para leitura
        with open(file_name, 'r') as csv_file:
            csv_reader = csv.reader(csv_file)
            next(csv_reader)  Pular a linha de cabeçalho

            Inserir cada linha na tabela Customers
            for row in csv_reader:
                cursor.execute(
                    "INSERT INTO Customers (name, balance) VALUES (?, ?);", (row[1], row[2]))

        connection.commit()
        print(f"Data imported successfully from {file_name}.")


Exemplo de uso
import_from_csv('customer_data.csv')

Como importar dados para o SQLite a partir de JSON

De forma similar, importar dados de um arquivo JSON é simples. Você pode ler o arquivo JSON e inserir os dados na sua tabela SQLite.

Veja como fazer isso:

import json
import sqlite3


def import_from_json(file_name):
    """Import data from a JSON file into the Customers table."""
    with sqlite3.connect('my_database.db') as connection:
        cursor = connection.cursor()

        Abrir o arquivo JSON para leitura
        with open(file_name, 'r') as json_file:
            customers_list = json.load(json_file)

            Inserir cada cliente na tabela Customers
            for customer in customers_list:
                cursor.execute("INSERT INTO Customers (name, balance) VALUES (?, ?);", (customer['Name'], customer['Balance']))

        connection.commit()
        print(f"Data imported successfully from {file_name}.")


Exemplo de uso
import_from_json('customer_data.json')

Encerrando

E aqui chega o fim! Este guia apresentou-lhe os fundamentos de trabalhar com SQLite em Python, abrangendo tudo desde a configuração do seu ambiente até as consultas e manipulações de dados, bem como as exportações e importações de informações. Espero que tenha encontrado útil e que tenha despertado seu interesse em usar SQLite para seus projetos.

Agora é hora de colocar seu conhecimento adquirido em prática! Encorajado a criar seu projeto usando SQLite e Python. Seja uma aplicação simples para gerenciar sua biblioteca, uma ferramenta de orçamento ou algo único, as possibilidades são infinitas.

Uma vez que você complete seu projeto, compartilhe-o no Twitter e marque-me! Adoraria ver o que você criou e comemorar seus feitos.

Toda o código deste tutorial pode ser encontrado no GitHub. Obrigado por seguir comigo, e feliz codificação!

Crie uma Tabela de Conteúdos para seus artigos do freeCodeCamp de graça usando a ferramenta TOC Generator.