Sumário
l Introdução aos sistemas de bases de dados: § Modelo relacional § SQL (Structured Query Language) § SQL e MATLAB § Um exemplo prá(co
Bases de Dados
l Base de Dados: Conjunto de dados estruturado.
§ Semelhante a vetores de estruturas vistos na aula passada.
l Bastante comum para guardar dados.
§ Permite usar os dados em diferentes programas (até usando
diferentes linguagens).
l Inclui:
§ Suporte ao armazenamento permanente (em ficheiros).
§ Operações para criar, inserir, consultar/procurar, alterar, apagar,
l Tabela de Filmes de uma base de dados DBvideos
Exemplo – Tabela de Filmes
Tabelas e Chaves
l A tabela Filmes tem quatro campos § FiIme_id § Titulo § Tipo § Classificacao l Chave primária (primary key)§ Um campo ou combinação de campos que iden(fica
inequivocamente esse registo
l Tabela de Clientes de uma base de dados DBvideos
Exemplo – Tabela de Clientes
SQL
l Uma linguagem para manipular dados e efetuar perguntas
Criação de Tabelas em SQL
l Tabelas são criadas com a instrução CREATE
em que para além do nome da tabela são iden(ficados os seus campos com os respec(vos (pos.
l Tipicamente os (pos u(lizados são numéricos, textuais ou
Booleanos (BOOL).
CREATE TABLE nome-tabela ( Campo-1 tipo-1,
Campo-2 tipo-2, ...
l Exemplo: Tabela de elementos químicos com as respec(vas massas atómicas. § Tipos numéricos mais u(lizados: • INTEGER: Inteiro; • Real: Real • DOUBLE: Real com precisão dupla § Tipos de string: • CHAR(N): string com exactamente N caracteres. • VARCHAR(N): string com N caracteres no máximo • TEXT: string mais longa Nota: em Sqlite3: é tudo tratado como TEXT
db >> CREATE TABLE Massas ( Simbolo TEXT, Massa DOUBLE);
db >>
l Em SQL é indiferente escrever as palavras-chave em
maiúsculas ou minúsculas.
§ No entanto, é comum escrever-se em maiúsculas.
l Quanto aos nomes dos campos e tabelas depende do sistema.
§ À cautela, é melhor escrever estes nomes de forma
consistente.
db >> CREATE TABLE Massas ( Simbolo TEXT, Massa DOUBLE);
db >>
l Na criação de Tabelas deve ser especificada uma chave primária
l A chave primária corresponde ao campo (ou combinação de
campos) que iden(ficam inequivocamente cada registo.
§ Repe(ções (registos com a mesma chave primária) não são
permi(das (é reportado um erro).
db >> CREATE TABLE Massas
..>> ( Simbolo TEXT, Massa DOUBLE;
..>> PRIMARY KEY(Simbolo));
db >>
l Exemplo: Tabela de Filmes
db >> CREATE TABLE Filmes ( Filme_id INTEGER, Titulo TEXT,
...>> Tipo TEXT, Classificacao TEXT, PRIMARY
...>> KEY(Filme_id) );
% Nota: A tabela é criada sem registos
db >>
l Exemplo: Tabela de Clientes
db >> CREATE TABLE Clientes ( Cliente_id INTEGER, Nome
...>> TEXT, Morada TEXT, Numero_cartao_credito TEXT,
...>> PRIMARY KEY(Cliente_id) );
db >>
l Exemplo: Tabela de Alugueres
db >> CREATE TABLE Alugueres ( Aluguer_id INTEGER,
...>> Cliente_id INTEGER, Filme_id INTEGER,
...>> Data_aluguer TEXT, Data_entrega TEXT,
...>> PRIMARY KEY(Aluger_id) );
db >>
Eliminação de Tabelas em SQL
l Eliminar Tabela: DROP § Após a criação de uma tabela que por algum mo(vo não nos interessa, por exemplo, ela pode ser eliminada (com todos os registos nale incluidos) através do comando DROPdb >> CREATE TABLE Errada (Num INTEGER, Nome TEXT);
Inserção de Registos em SQL
l Inserir registos: INSERT§ Um registo pode ser inserido numa tabela com a instrução
INSERT
§ Os valores dos campos neste registo, são indicados na mesma
ordem da definição original dos campos no CREATE TABLE.
db >> INSERT INTO Filmes VALUES (
...>> 10,"Meaning of Life","comedia","maiores de 16");
db >>
INSERT INTO tabela VALUES
Selecção de Registos em SQL
l Selecionar registos: SELECT
§ Um ou mais registos podem ser selecionados duma ou mais
l Alguns exemplos (uma só tabela): db >> SELECT * FROM Filmes;
% mostra todos os registos da tabela Filmes
db >> SELECT Titulo FROM Filmes WHERE Tipo = "comedia"; % mostra os títulos dos registos da tabela Filmes
% cujo tipo seja "comedia"
db >> SELECT Titulo, Tipo FROM Filmes ORDER BY Titulo;
% mostra os campos Titulo e Tipo dos registos da tabela % Filmes ordenados alfabeticamente pelo campo Titulo
Modificação de Registos em SQL
l A instrução UPDATE permite alterar valores dos campos de uma
tabela, para os registos selecionados
db >> UPDATE Filmes SET Tipo = "drama"
...>> WHERE Titulo = "Elizabeth”;
% altera todos os registos da tabela Filmes, cujo título seja "Elizabeth", alterando o valor do campo Tipo para drama.
Eliminação de Registos em SQL
l A instrução DELETE permite apagar todos os registos de uma
tabela, selecionados por uma condição (opcional).
l Nota: Apagar os registos todos de uma tabela não elimina a
tabela.
§ Para o fazer deve ser usado o comando DROP
>> DELETE FROM Filmes WHERE Nome = "Avengers"; % apaga todos os registos da tabela Filmes, cujo título seja ”Avengers”.
DELETE
FROM tabela
l Para executar: descarregar o executável do SQLite de hMps://sqlite.org/download.html
§ No Windows: descarregar um dos ficheiros
sqlite-dll-win64-x32-3180000.zip
sqlite-dll-win64-x64-3180000.zip
Descomprimir e colocar o ficheiro sqlite3.exe na pasta de trabalho
§ No Mac: descarregar o ficheiro
sqlite-tools-osx-x86-3180000.zip
Descomprimir e colocar o ficheiro sqlite3 na pasta de trabalho
l Para criar/u(lizar em SQLITE uma base de dados, com nome
baseDados, existente na directoria corrente, (tal como o programa sqlite3) pode u(lizar-se a consola do sistema (e.g. Cmd no Windows ou Terminal no Mac) e u(lizar o comando
sqlite3 baseDados
l A par(r da janela de comandos do OCTAVE deverá proceder-se
semelhantemente, mas especificando que se trata de uma chamada do sistema opera(vo (sqlite3 não é um ficheiro .m )
l
No final, volta-se à janela de comandos do OCTAVE com a
comando sqlite “.quit”.
>> system(‘sqlite3 exemplo.db’)
Exemplo:
l Criação de uma base de dados, com uma tabela de elementos
químicos, introduzindo-se os elementos H e He.
>> system(‘sqlite3 quimica.db’)
sqlite> CREATE TABLE Elementos
..>> ( Numero INTEGER, Simbolo TEXT,
..>> PRIMARY KEY(Numero));
sqlite> INSERT INTO Elementos VALUES (1, ‘H’);
sqlite> INSERT INTO Elementos VALUES (2, “He”);
sqlite> SELECT * FROM Elementos
1|H 2|He
sqlite> .quit >>
§ .prompt first cont
• Modifica o prompt normal do sqlite para first e o das
linhas de con(nuação para cont § .tables • lista as tabelas § .separator sep • especifica sep como separador dos campos § .import ficheiro tabela
• Importa o ficheiro para a tabela assumindo os campos
separados pelo separador definido
l Exemplo: Com a base de dados, quimica.db, já criada sqlite> .PROMPT ‘db >>’ ‘ -->> ’
db >> .schema
CREATE TABLE elementos (Numero INTEGER, Simbolo TEXT,PRIMARY KEY (Numero));
db >> INSERT INTO Elementos
-->> VALUES (3, “Li”);
db >> SEPARATOR ‘ :: ‘
db >> SELECT * FROM Elementos
MATLAB + SQLite
l Para combinar SQL com MATLAB, usamos uma função que corre o
SQLite para executar o comando SQL que queremos.
l Como vimos, o SQLite pode ser executado com parâmetros na
linha de comando (consola do sistema)
§ nome do ficheiro com a base de dados.
§ e um comando SQL
l A mesma operação pode ser feita a par(r da janela de comandos
Exemplo:
l A inserção do elemento Berilio (Be) na base de dados
“quimica.db” anteriormente criada, pode ser feita a par(r da consola do sistema
l Ou através da chamada de sistema a par(r da janela de
comandos OCTAVE
db >> INSERT INTO Elementos VALUES (4, “Be”);
db >>
>> system(‘sqlite quimica.db “INSERT INTO Elementos ...
VALUES (4, “Be”);’) >>
l Em geral, a execução de comandos SQL a par(r de um programa
MATLAB pode ser feita com a função sqlite, com assinatura
§ Basicamente, esta função chama a função system do OCTAVE,
com o comando fornecido na string sql passada como argumento
• Garan(ndo que as plicas são subs(tuidas por aspas, para
ser compayvel com a linguagem SQL.
§ Nota: esta função tem elementos que não fazem parte da
matéria de ICE.
function result = sqlite(sql,db)
% result=sqlite(sql,db)
% Executa o sqlite3 para processar o comando SQL
% fornecido no primeiro argumento. Devolve o resultado % como string. Opcionalmente, pode receber o nome
% do ficheiro da base de dados como segundo argumento. Se % omitido, assume ser temp.db.
if nargin < 2 % se o segundo argumento nao existe
db = 'temp.db';
end
sql = strrep(sql,'"',''''); % troca plicas por aspas
[stat,result] = system(['sqlite3 ',db,' "',sql,'"']); end
l Testar
>> sqlite(‘sqlite3 atoms.db’)
ans =
>> sqlite(‘CREATE TABLE Atomos (Num INTEGER, Simb TEXT);’,
‘atoms.db’) ans =
>> sqlite(‘INSERT INTO Atomos VALUES (1,”H”);’,‘atoms.db’)
ans =
>> sqlite(‘INSERT INTO Atomos VALUES (2,”He”);’,‘atoms.db’)
ans =
>> sqlite('SELECT * FROM Atomos;',’atoms.db')
ans = 1|H 2|He >>
l Sendo disponibilizado um conjunto de dados sobre praias, e a
l A criação da tabela, que chamaremos Projetos, é feita através de
um só comando SQL, chamado do OCTAVE pela função sqlite definida atrás.
l De notar a especificação da PRIMARY KEY, que garante uma
iden(ficação única de cada registo e a não existência de repe(ções.
§ Neste caso, as obras têm um nome único.
function criaTabela(nomeBD)
% Cria a tabela Projectos na base de dados indicada.
sqlite([’CREATE TABLE Projectos ( Concelho text,',...
'Nome text, Custo float, Prioridade text,',...
'PRIMARY KEY(nome));'], nomeBD);
end
l Porquê PRIMARY KEY?
§ Numa base de dados relacional, as chaves permitem
relacionar tabelas diferentes.
§ E mesmo só com uma tabela devemos poder iden(ficar
inequivocamente cada registo • por exemplo, para evitar a inserção de duplicados • Neste caso assumimos que dois projectos com o mesmo nome seria um erro l O SQLite cria sempre um atributo chamado Rowid que iden(fica inequivocamente cada registo, mesmo que não se crie uma chave primária.
§ No entanto, esse não serve para evitar registos repe(dos
porque incrementa automa(camente.
l Testes
§ Podemos começar por criar a tabela de Projectos numa BD,
que chamaremos de praias.db, para podermos testar a função
que carrega os dados.
>> criatabela(’praias.db');
% verificar que tabelas existem na BD ‘praias.db
>> sqlite('.tables',’praias.db')
ans = Projectos >>
l Os dados podem ser carregados na tabela através de uma função
com assinatura
function carrega_praias(nomeFich, nomeBD)
function carrega_praias(nomeFich, nomeBD)
% documentação em anexo
fid = fopen(nomeFich,'r'); while ~feof(id)
linha = fgetl(fid);
pVirgs = findstr(';', linha); concelho = linha(1:pVirgs(1)-1);
nome = linha(pVirgs(1)+1:pVirgs(2)-1);
custo = str2num(linha(pVirgs(2)+1:pVirgs(3)-1)); prioridade = linha(pVirgs(3)+1:end);
sql = sprintf('insert into Projectos ... values("%s", "%s",%f,"%s");’,... concelho, nome, custo, prioridade) sqlite(sql, nomeBD);
end
fclose(fid);
end
l Testes
§ podemos agora inserir os dados do ficheiro praias.txt, e
verificar se eles estão corretamente inserido
>> carrega_praias(’praias.txt’, ‘praias.db');
% verificar que registos existem na tabela
>> sqlite(’SELECT * FROM Projectos;','praias.db’)
ans = 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 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.00|Elevada >>
l Em SQL podem ser feitas consultas (queries) às bases de dados
em que as condições WHERE não exigem igualdades estritas mas apenas “semelhanças”, em que alguns caracteres nos campos a pesquisar podem não ser iguais.
l Para esse efeito
§ Usa-se a palavra LIKE, em vez do simbolo de igualdade
§ U(lizam-se wildcards, %, para susbs(tuirem quaisquer
caracteres.
l Como anteriormente, estas consultas podem ser feitas
directamente do OCTAVE
l Exemplo 1 (com LIKE e wildcards)
>> sqlite('SELECT Nome FROM Projectos
WHERE Nome LIKE "Arriba%";',’praias.db’)
% quais os registos cujo Nome comeca por Arriba ans = Arribas junto ao forte do Paimogo
Arribas em Porto das Barcas Arribas em Porto Dinheiro
Arribas da praia Grande do Rodizio Arribas da praia das Azenhas do Mar Arribas da praia Pequena
Arriba da Praia Fonte do Cortico Arribas da praia de Galapos
>>
l Exemplo 2 (com LIKE e wildcards)
>> sqlite('SELECT Nome FROM Projectos
WHERE Nome LIKE "%praia%";',’praias.db')
% quais os registos cujo Nome contem a palavra praia
ans = Esporao da Praia da Vieira
Arribas da praia Grande do Rodizio Arribas da praia das Azenhas do Mar Arribas da praia Pequena
Arriba da Praia Fonte do Cortico
Relocalizacao de acessos das praias de Morgavel,...Farol Arribas da praia de Galapos
Alimentacao artificial da praia da Mareta Saneamento da arriba na praia do Vau
>>
l Exemplo 3 (com DISTINCT)
>> sqlite(‘SELECT DISTINCT Concelho FROM Projectos;’,
’praias.db')
% quais os concelhos mencionados nos registos sem repeticoes ! ans = Marinha Grande
Lourinha Sintra Santiago do Cacem Sines Setubal Vila do Bispo Portimao
>> sqlite(‘SELECT DISTINCT Prioridade FROM Projectos;’,
l As consultas SQL, feitas através da função sqlite, retornam uma string,
eventualmente com várias linhas, em que os campos dos registos retornados são delimitados pelo separador corrente (por omissão, o separador é “|”).
>> sqlite(’SELECT * FROM Projectos;','praias.db’)
ans = 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 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.00|Elevada >>
l Em geral, será conveniente estruturar melhor estas respostas,
transformando as strings retornadas num vetor de estruturas, correspondendo aos registos retornados.
l Para esse efeito pode usar-se a função com assinatura abaixo que é
especificada de seguida
function recs = parseRecords (sqlResult,nameString)
Nota: esta função tem elementos que não fazem parte da matéria de ICE.
function recs = parseRecords(sqlResult,nameString)
% recs=parserecords(sqlResult,nameString)
% Devolve um vector de estruturas organizando os dados na string % devolvida pelo SQLite. Assume que os campos estao separados % por uma barra |. O segundo argumento, opcional, contem uma % string com os nomes dos campos das estruturas devolvidas, % tambem separados por |.
recs = [];
if nargin < 2
names = {};
else
names = strsplit(nameString,'|');
end
ca = strsplit(sqlResult,"\n");
for f = 1:length(ca)-1
... % converte a string sqlResult num vetor de estruturas
end end
for f = 1:length(ca)-1
line = deblank(ca{f});
if length(line) == 0 break end
rec = strsplit(ca{f},'|');
for g = 1:length(rec)
if g > length(names) campo = ['campo_',num2str(g)]; else campo = names{g};
end
% converte em numero, se possivel
testNum = str2double(rec{g}); if isnan(testNum)
valor = rec{g}; else
valor = testNum; end
recs(f).(campo) = valor;
end end
l Query: § Obter nome e custo de projetos com uma dada prioridade.
Nota: A função sprinX funciona como fprinX mas em vez de escrever num ficheiro ou consola devolve a string formatada.
function projs = sel_prioridade(tabela, baseDados, prioridade)
% projs=selprioridade(tabela, baseDados, prioridade)
% selecciona da tabela da base de dados indicada os campos do % nome e custo dos projectos com a prioridade especificada.
query = sprintf('SELECT Nome, Custo FROM %s WHERE ...
Prioridade="%s";', tabela, prioridade);
result = sqlite(query, baseDados);
% obter vetor de estruturas com campos nome e custo
projs = parseRecords(result, 'nome|custo');
end
l Testar
>> elevada = sel_prioridade('Projectos', ’praias.db', 'Elevada');
>> elevada(1)
nome = Alimentacao artificial da praia da Mareta custo = 2500000
>> elevada(2)
nome = Saneamento da arriba na praia do Vau custo = 30000
>> baixa = sel_prioridade('Projectos', ’praias.db', 'Baixa')
baixa =
nome = Arribas da praia de Galapos custo = 100000
>>
l Query: § Obter as obras separadas por prioridades « Basta juntar as queries/funções anteriores
function [elevada,media,baixa] = processa_projs(fich, baseDados)
% [alta,media,baixa]=processaprojs(fich, baseDados) % Cria a tabela Projectos na base de dados indicada, % carrega os dados do ficheiro e selecciona da tabela % os campos do nome do projecto e custo, separando em % tres vectores de estruturas conforme a prioridade.
criatabela(baseDados);
carrega_praias('praias.txt', baseDados);
elevada = selprioridade('Projectos', baseDados, 'Elevada')
media = selprioridade('Projectos', baseDados, 'Media')
baixa = selprioridade('Projectos', baseDados, 'Baixa')
end
>> [elev, medi,baix] = processaprojs('praias.txt',’outra.db')
elev =
1x2 struct array containing the fields: nome custo
medi =
1x9 struct array containing the fields: nome custo
baix =
scalar structure containing the fields:
nome = Arribas da praia de Galapos % mostra o valor dos custo = 100000 % campos por so ter um elemento >> ...
l Testar
>> elev.nome
ans = Alimentacao artificial da praia da Mareta ans = Saneamento da arriba na praia do Vau
>> med.nome
ans = Esporao da Praia da Vieira
ans = Arribas junto ao forte do Paimogo ans = Arribas em Porto das Barcas
ans = Arribas em Porto Dinheiro
ans = Arribas da praia Grande do Rodizio ans = Arribas da praia das Azenhas do Mar ans = Arribas da praia Pequena
ans = Arriba da Praia Fonte do Cortico
ans = Relocalizacao de acessos das praias de Morgavel, Franquia e Farol
>>