ICE-B 16 - Sqlite e Python Ludwig Krippahl
Sqlite + Python Resumo Usar Sqlite a partir de Python Módulo sqlite3 Ligação à base de dados Cursor para executar comandos SQL Exemplo prático: criar uma tabela e consultar os dados 1
Sqlite + Python Usar Sqlite em Python 2
Sqlite + Python Porquê usar um motor de bases de dados? A linguagem SQL facilita muito algumas operações Criar tabelas, inserir registos, consultar dados O motor de bases de dados está optimizado para gerir os dados Garante integridade da informação, mesmo em caso de falhas Está preparado para gerir grandes volumes de dados Oferece uma interface standard (SQL) acessível a outros sistemas 3
Sqlite + Python Como usar o Sqlite em Python com o módulo sqlite3 Criar uma ligação à base de dados: conn = connect(db_name) Obter um cursor, que executa SQL: cursor = conn.cursor() Dar o comando ou commandos SQL: cursor.execute(sql) Actualizar a base de dados: conn.commit() Isto garante atomicidade das transacções Até ao commit() a BD não é alterada. Apenas parece alterada da perspectiva desta ligação No commit() são feitas todas as alterações. Se houver erro não é feita nenhuma alteração Se nos arrependemos, em vez do commit() podemos fazer rollback() Fechar a ligação: conn.close() 4
Sqlite + Python Exemplo: Renovação de praias 5
Temos um ficheiro de texto com dados de projectos Ficheiro praias.txt Concelho, nome, custo e prioridade. Marinha Grande;Esporao da Praia da Vieira;720000.00;Media Lourinha;Arribas junto ao forte do Paimogo;591000.00;Media Lourinha;Arribas em Porto das Barcas;691000.00;Media Lourinha;Arribas em Porto Dinheiro;606500.00;Media Sintra;Arribas da praia Grande do Rodizio;39951.63;Media Sintra;Arribas da praia das Azenhas do Mar;326560.08;Media Sintra;Arribas da praia Pequena;428015.58;Media Santiago do Cacem;Arriba da Praia Fonte do Cortico;23000.00;Media Sines;Relocalizacao de acessos das praias de Morgavel, Franquia... Setubal;Arribas da praia de Galapos;100000.00;Baixa Vila do Bispo;Alimentacao artificial da praia da Mareta;2500000.00;Elevada Portimao;Saneamento da arriba na praia do Vau;30000.00;Elevada Queremos organizar isto numa tabela para poder consultar Por exemplo, todos os projectos com uma certa prioridade 6
Plano do programa: Criar a tabela Projectos com os campos necessários: concelho, nome, custo, prioridade Carregar os dados do ficheiro para a tabela Projectos Criar uma função que liste os projectos com a prioridade especificada Consultando a tabela na BD import sqlite3 def create_table(db_name): """create the Projects table in the database""" def load_projects(file_name, db_name): """load projects from file into projects table""" def with_priority(db_name,priority): "return dictionary with projects of given priority" 7
Criar a tabela def create_table(db_name): """create the projects table in the database""" conn = sqlite3.connect(db_name) cursor = conn.cursor() cursor.execute('create TABLE Projects ( Concelho TEXT, Nome TEXT,'+ 'Custo FLOAT, Prioridade TEXT, PRIMARY KEY(Nome));') conn.commit() conn.close() Ligar à base de dados (connect) Obter o cursor para essa ligação Executar o código SQL para criar a tabela e fazer commit Fechar a ligação. 8
Criar a tabela def create_table(db_name): """create the projects table in the database""" conn = sqlite3.connect(db_name) cursor = conn.cursor() cursor.execute('create TABLE Projects ( Concelho TEXT, Nome TEXT,'+ 'Custo FLOAT, Prioridade TEXT, PRIMARY KEY(Nome));') conn.commit() conn.close() In : create_table('teste.db') In : Como sabemos se funcionou? 9
Como verificar o resultado Opção 1: usar o sqlite3 directamente: SQLite version 3.20.1 2017-08-24 16:21:36 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite>.open teste.db sqlite>.tables Projects sqlite>.schema CREATE TABLE Projects ( Concelho TEXT, Nome TEXT,Custo FLOAT, Prioridade TEXT, PRIMARY KEY(Nome)); sqlite>.quit 10
Como verificar o resultado Opção 2: usar um gestor para Sqlite. E.g. sqliteman http://sqliteman.yarpen.cz/ 11
Problema: se a tabela já existe ocorre uma exception In : create_table('teste.db')... OperationalError: table Projects already exists Isto é inconveniente porque bloqueia a execução Para evitar que pare vamos usar um bloco try... except try:... except:... Se ocorrer um erro no bloco try a execução passa ao except Nota: não é boa prática fazer isto assim; devíamos capturar o tipo específico de erro. Mas vamos simplificar. 12
Criar a tabela def create_table(db_name): """create the projects table in the database""" conn = sqlite3.connect(db_name) cursor = conn.cursor() try: cursor.execute('create TABLE Projects ( Concelho TEXT, Nome TEXT,'+ 'Custo FLOAT, Prioridade TEXT, PRIMARY KEY(nome));') conn.commit() except: print('error: could not create table.') conn.close() In : create_table('teste.db') In : create_table('teste.db') Error: could not create table. In : 13
Inserir os dados do ficheiro Marinha Grande;Esporao da Praia da Vieira;720000.00;Media Lourinha;Arribas junto ao forte do Paimogo;591000.00;Media Lourinha;Arribas em Porto das Barcas;691000.00;Media Lourinha;Arribas em Porto Dinheiro;606500.00;Media Sintra;Arribas da praia Grande do Rodizio;39951.63;Media Sintra;Arribas da praia das Azenhas do Mar;326560.08;Media Sintra;Arribas da praia Pequena;428015.58;Media Santiago do Cacem;Arriba da Praia Fonte do Cortico;23000.00;Media Sines;Relocalizacao de acessos das praias de Morgavel, Franquia... Setubal;Arribas da praia de Galapos;100000.00;Baixa Vila do Bispo;Alimentacao artificial da praia da Mareta;2500000.00;Elevada Portimao;Saneamento da arriba na praia do Vau;30000.00;Elevada 14
Inserir os dados do ficheiro def load_projects(file_name, db_name): """load projects from file into projects table""" lines = open(file_name).readlines() conn = sqlite3.connect(db_name) cursor = conn.cursor() for line in lines: try: cells = line.strip().split(';') sql = 'INSERT INTO Projects VALUES ( "{0}", "{1}", {2}, "{3}");' sql = sql.format(cells[0],cells[1],cells[2],cells[3]) cursor.execute(sql) conn.commit() except: print('error on ',line) conn.close() Tentamos inserir cada registo e fazer logo o commit Se alguma coisa correr mal, avisamos que houve um erro naquela linha Alernativa: fazer todos os execute e depois um só commit 15
Inserir os dados do ficheiro Testar: In : load_projects('praias.txt','teste.db') Verificar com a consola do sqlite3 SQLite version 3.20.1 2017-08-24 16:21:36 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite>.open teste.db sqlite> select * from projects; Marinha Grande Esporao da Praia da Vieira 720000.0 Media Lourinha Arribas junto ao forte do Paimogo 591000.0 Media Lourinha Arribas em Porto das Barcas 691000.0 Media Lourinha Arribas em Porto Dinheiro 606500.0 Media Sintra Arribas da praia Grande do Rodizio 39951.63 Media sqlite>.quit 16
Inserir os dados do ficheiro Testar: In : load_projects('praias.txt','teste.db') Verificar com o sqliteman 17
Com a tabela no sqlite3 podemos: Ver os custos dos projectos de nome começado por Arriba: sqlite> SELECT Nome, Custo FROM Projects WHERE Nome LIKE "Arriba%"; Arribas junto ao forte do Paimogo 591000.0 Arribas em Porto das Barcas 691000.0 Arribas em Porto Dinheiro 606500.0 Arribas da praia Grande do Rodizio 39951.63 Arribas da praia das Azenhas do Mar 326560.08 Arribas da praia Pequena 428015.58 Arriba da Praia Fonte do Cortico 23000.0 Arribas da praia de Galapos 100000.0 sqlite> O operador LIKE compara strings admitindo wildcards O sinal % na string do LIKE substitui qualquer substring 18
Com a tabela no sqlite3 podemos: Ver os custos dos projectos com praia no nome: sqlite> SELECT Nome, Custo FROM Projects WHERE Nome LIKE "%praia%"; Esporao da Praia da Vieira 720000.0 Arribas da praia Grande do Rodizio 39951.63 Arribas da praia das Azenhas do Mar 326560.08 Arribas da praia Pequena 428015.58 Arriba da Praia Fonte do Cortico 23000.0 Relocalizacao de acessos das praias de Morgavel, Franquia e Farol 100100.0 Arribas da praia de Galapos 100000.0 Alimentacao artificial da praia da Mareta 2500000.0 Saneamento da arriba na praia do Vau 30000.0 sqlite> O operador LIKE compara strings admitindo wildcards O sinal % na string do LIKE substitui qualquer substring O operador LIKE não distingue maiúsculas e minúsculas 19
Com a tabela no sqlite3 podemos: Ver os concelhos diferentes onde há projectos (DISTINCT) sqlite> SELECT DISTINCT Concelho FROM Projects; Marinha Grande Lourinha Sintra Santiago do Cacem Sines Setubal Vila do Bispo Portimao Ou prioridade: sqlite> SELECT DISTINCT Prioridade FROM Projects; Media Baixa Elevada sqlite> 20
Consultas usando Python Com o objecto cursor podemos executar o SELECT Neste caso não há commit porque não alteramos a base de dados Depois de executar o SELECT, podemos obter todos os resultados com cursor.fetchall() Uma lista de tuplos com os valores dos campos In : conn = sqlite3.connect('teste.db') In : cursor = conn.cursor() In : cursor.execute('select * FROM Projects WHERE Nome LIKE "%praia%";') In : cursor.fetchall() Out: [('Marinha Grande', 'Esporao da Praia da Vieira', 720000.0, 'Media'), ('Sintra', 'Arribas da praia Grande do Rodizio', 39951.63, 'Media'), ('Sintra', 'Arribas da praia das Azenhas do Mar', 326560.08, 'Media'), ('Sintra', 'Arribas da praia Pequena', 428015.58, 'Media'),... ('Portimao', 'Saneamento da arriba na praia do Vau', 30000.0, 'Elevada')] In : conn.close() 21
Consultas usando Python Vamos criar uma função auxiliar para simplificar isto E organizar os dados como uma lista de dicionários, usando cursor.description Tem um tuplo de tuplos com o nome da coluna como primeiro elemento Podemos usar isto para criar uma lista de dicionários In : conn = sqlite3.connect('teste.db') In : cursor = conn.cursor() In : cursor.execute('select * FROM Projects WHERE Nome LIKE "%praia%";') In : cursor.description Out: (('Concelho', None, None, None, None, None, None), ('Nome', None, None, None, None, None, None), ('Custo', None, None, None, None, None, None), ('Prioridade', None, None, None, None, None, None)) In : conn.close() 22
Consultas usando Python Função auxiliar para simplificar consultas def query_db(db_name,query): "return dictionary with result of SELECT" conn = sqlite3.connect(db_name) cursor = conn.cursor() cursor.execute(query) records = cursor.fetchall() conn.close() result = [] for record in records: current = {} for ix in range(len(record)): current[cursor.description[ix][0]] = record[ix] result.append(current) return result 23
Consultas usando Python Função auxiliar para simplificar consultas In : query_db('teste.db','select * FROM Projects;') Out[36]: [{'Concelho': 'Marinha Grande', 'Custo': 720000.0, 'Nome': 'Esporao da Praia da Vieira', 'Prioridade': 'Media'}, {'Concelho': 'Lourinha', 'Custo': 591000.0, 'Nome': 'Arribas junto ao forte do Paimogo', 'Prioridade': 'Media'}, {'Concelho': 'Lourinha', 'Custo': 691000.0, 'Nome': 'Arribas em Porto das Barcas', 'Prioridade': 'Media'},... {'Concelho': 'Portimao', 'Custo': 30000.0, 'Nome': 'Saneamento da arriba na praia do Vau', 'Prioridade': 'Elevada'}] 24
Consultas usando Python Função auxiliar para simplificar consultas In : query_db('teste.db','select * FROM Projects WHERE Prioridade="Baixa"') Out: [{'Concelho': 'Setubal', 'Custo': 100000.0, 'Nome': 'Arribas da praia de Galapos', 'Prioridade': 'Baixa'}] In : query_db('teste.db','select * FROM Projects WHERE Prioridade="Elevada"') Out: [{'Concelho': 'Vila do Bispo', 'Custo': 2500000.0, 'Nome': 'Alimentacao artificial da praia da Mareta', 'Prioridade': 'Elevada'}, {'Concelho': 'Portimao', 'Custo': 30000.0, 'Nome': 'Saneamento da arriba na praia do Vau', 'Prioridade': 'Elevada'}] 25
Função para obter projectos dada a prioridade Esta agora é fácil: def with_priority(db_name,priority): "return dictionary with projects of given priority" query = 'SELECT * FROM Projects WHERE Prioridade = "{0}";' query = query.format(priority) return query_db(db_name,query) In : with_priority('teste.db','baixa') Out: [{'Concelho': 'Setubal', 'Custo': 100000.0, 'Nome': 'Arribas da praia de Galapos', 'Prioridade': 'Baixa'}] 26
Função para organizar projectos por prioridade Devolve um dicionário com todas as prioridades na tabela Cada entrada desse dicionário tem a lista de dicionários com os projectos correspondentes def projects_by_priority(db_name): "return dictionary with priorities and all projects in list" projects = {} priorities = query_db(db_name,'select DISTINCT Prioridade from Projects;') for entry in priorities: priority = entry['prioridade'] projects[priority] = with_priority(db_name,priority) return projects Usamos SELECT DISTINCT Prioridade para listar prioridades Para cada uma criamos a entrada no dicionário projects 27
Função para organizar projectos por prioridade In : projects = projects_by_priority('teste.db') In : projects.keys() Out: dict_keys(['media', 'Baixa', 'Elevada']) In : projects['elevada'] Out: [{'Concelho': 'Vila do Bispo', 'Custo': 2500000.0, 'Nome': 'Alimentacao artificial da praia da Mareta', 'Prioridade': 'Elevada'}, {'Concelho': 'Portimao', 'Custo': 30000.0, 'Nome': 'Saneamento da arriba na praia do Vau', 'Prioridade': 'Elevada'}] In : projects['baixa'] Out: [{'Concelho': 'Setubal', 'Custo': 100000.0, 'Nome': 'Arribas da praia de Galapos', 'Prioridade': 'Baixa'}] 28
Sqlite + Python Resumo 29
Resumo Sqlite e Python Python permite-nos processar os dados de muitas formas Gráficos, ajuste de modelos, processar ficheiros, etc Mas um SGBD permite gerir os dados de forma independente Mais fácil de trocar informação com outros programas (e.g. sqliteman) Optimizado para gerir dados (atomicidade, grande volume, etc) Combinar os dois pode ser uma boa solução Módulo sqlite3: ligar, criar cursor, executar e fechar Leitura adicional: Recomendada: Capítulo 16 dos apontamentos Opcional: Livro, capítulo 33 (exceptions); sqlite3: https://docs.python.org/3.6/library/sqlite3.html 30