INTRODUÇÃO A SISTEMAS DE BANCO DE DADOS 2012 BANCO DE DADOS Aplicação da Structure Query Language Este e-book visa, através de exemplos práticos, aplicar os conceitos da Structure Query Language (SQL) na manipulação de Banco de Dados relacionais. Ricardo R. Barcelar http://www.ricardobarcelar.com.br [email protected] APRESENTAÇÃO APRESENTAÇÃO O objetivo deste material é apresentar a Structure Query Language como linguagem padrão para manipulação de bancos de dados relacionais, norteando o estudo de banco de dados através de exemplos práticos aplicados a um Sistema Gerenciador de Banco de Dados amplamente conhecido e utilizado, o PostgreSQL. CELSO HENRIQUE PODEROSO DE OLIVEIRA, NAVATE e ELMASRI são referências para os estudos traçados neste material. Dessa forma, o conteúdo apresentado certamente poderá ser utilizado como referência na vida prática do profissional de computação que lida diariamente com Banco de Dados. Ricardo R. Barcelar PROJETO FÍSICO DE BANCO DE DADOS 1 Parte PROJETO FÍSICO DE BANCO DE DADOS A primeira etapa a fazer ao começa a trabalhar com banco de dados é estudar as técnicas utilizadas para se criar um projeto de banco de dados: Modelo EntidadeRelacionamento, Modelo Lógico usando ferramenta Case, Normalização, Estratégias de Projeto de Banco de Dados, etc. Conhecedores dos conceitos de Codd, Chen, e outros autores que definiram o padrão relacional para banco de dados relacionais, é então, possível avançar mais um passo: implementar o banco de dados em uma ferramenta apropriada. Mas antes, é necessário conhecer alguns conceitos do Projeto Físico e dos Sistemas Gerenciadores de Banco de Dados especificamente. O Modelo Físico de Dados é a especificação em SQL - Structure Query Language – Linguagem de Consulta Estruturada - do esquema relacional para o Sistema Gerenciador de Banco de Dados escolhido. Nesta fase, as estruturas de armazenamento e de recuperação de informações, bem como os mecanismos de acesso devem ser escolhidos, visando sempre o aprimoramento da performance dos aplicativos de Banco de Dados. Nesta fase devem ser especificados não apenas as tabelas criadas, mas também os índices necessários, as restrições de integridade (checks e triggers), algumas operações de inclusão, exclusão e atualização de dados para cada tabela, bem como as consultas que a aplicação deve realizar. 1.1 FATORES QUE INFLUENCIAM O PROJETO FÍSICO DE BANCO DE DADOS Esta fase do projeto de Banco de Dados objetiva não só propor uma apropriada estruturação de dados, mas fazê-lo de maneira que garanta um bom desempenho. Sendo assim, não é possível tomar decisões significativas sobre projetos físicos e análises de desempenho, até que conheçamos as consultas, as transações e as aplicações que devem ser executadas no banco de dados. Dessa forma, vamos discutir alguns fatores: a. Analisar as consultas e transações do Banco de Dados É importante ter uma ideia das intenções de uso do banco de dados, definindo as consultas e transações que esperamos que sejam realizadas em alto nível, especificando o seguinte: - Os arquivos que serão acessados pela consulta; 3 PROJETO FÍSICO DE BANCO DE DADOS - Os atributos nos quais quaisquer condições de seleção para a consulta estejam especificadas; - Os atributos nos quais quaisquer condições de junção ou condições para ligar múltiplas tabelas ou objetos para a consulta estejam especificadas; - Os atributos cujos valores serão trazidos através da consulta. Para cada operação ou transação de atualização, devemos especificar o seguinte: - Os arquivos que serão atualizados; - O tipo de operação em cada arquivo (insert, update, delete); - Os atributos nos quais as condições de seleção para uma exclusão ou atualização estejam especificadas. - Os atributos cujos valores serão alterados através de uma operação de atualização. b. Analisar a frequência esperada de solicitação (execução) de consultas e transações É necessário considerar as taxas de solicitação (execução), usadas de forma estatística em situações práticas para grandes volumes de processamento. c. Analisar as restrições de tempo de consulta e transações Neste caso, algumas consultas e transações podem ter rigorosas restrições de desempenho. Por exemplo, uma transação que deve ser interrompida se não for concluída em 20 segundos. d. Analisar as frequências esperadas de operações de atualização Um número mínimo de caminhos de acesso aos dados deve ser especificado para um arquivo que seja frequentemente atualizado, uma vez que atualizar os próprios caminhos de acesso desacelera as operações de atualização. e. Analisar as restrições de Unicidade em Atributos Caminhos de acesso aos dados devem ser especificados em todos os atributos candidatos a chave ou chave primária. A existência de um índice facilita a pesquisa nos arquivos de dados, pois este define um caminho de dados. Realizadas as análises iniciais, outra decisão importante é definir qual Sistema Gerenciador de Banco de Dados usar. É relevante levar em consideração vários fatores, como: - O modelo físico, visto que determinará qual a carga de trabalho que será exigida pelo Sistema Gerenciador de Banco de Dados, isso já eliminará algumas opções. - O custo. Uma locadora, por exemplo, irá adquirir uma licença Oracle ou SQL Server? - Plataforma de Sistema Operacional. Esta é uma decisão que não será analisada neste momento. 4 PROJETO FÍSICO DE BANCO DE DADOS 1.2 SQL - STRUCTURE QUERY LANGUAGE - LINGUAGEM ESTRUTURADA CONSULTA DE Como visto no módulo passado, é muito simples criar um script SQL através de uma ferramenta Case. No entanto, este não será o foco desta sessão. Aqui aprenderemos a criá-lo sem auxílio de ferramentas case. Para tanto, é necessário conhecermos a linguagem SQL. Apesar do nome, fazer consultas a bancos de dados não é a única função de SQL. Ela é utilizada para criar tabelas, inserir, excluir e alterar dados no banco de dados, além de outras utilizações. A SQL foi desenvolvida na década de 70 pela IBM em uma das primeiras tentativas de desenvolver um banco de dados relacional. Tornou-se padrão de fato depois de 1986, quando a ANSI (American National Standards Institute) endossou como linguagem padrão para Banco de Dados relacionais e desde então, já sofreu três atualizações oficiais: em 1989, 1992 e 1999. Nesta última, algumas das mudanças mais significativas estão relacionadas com a definição dos padrões para Banco de Dados Objeto-Relacionais. Devido ao sucesso da forma de consultar e manipular dados da SQL, dentro de um ambiente de banco de dados, a utilização da SQL foi se tornando ainda maior. Cabe ressaltar que cada implementação de SQL possui uma adaptação da sintaxe para resolver determinados problemas, portanto, qualquer comando mostrado pode ser usado de forma diferente em um determinado Sistema Gerenciador de Banco de Dados. Recomenda-se a leitura do manual do fabricante para maiores informações sobre o uso da linguagem SQL em Sistema Gerenciador de Banco de Dados comerciais. Atualmente, a linguagem SQL assume um papel muito importante nos sistemas de gerenciamento de banco de dados, podendo ter muitos enfoques. Através de comandos SQL, é possível montar consultas poderosas sem a necessidade da criação de um programa, ou utilizar comandos SQL embutidos em programas de aplicação que acessam os dados armazenados. Devido ao fato de possuir várias aplicações, a linguagem SQL provê suporte a várias funções. Que consiste em: - DDL (Linguagem de definição de dados), onde os dados a serem armazenados são definidos e estruturados; (Create, Alter e Drop). - DML (Linguagem de manipulação de dados), que permite a inclusão, remoção, seleção ou atualização de dados armazenados no banco de dados; Controle de acesso, permitindo proteção dos dados de manipulações não autorizadas; (Select, Insert, Update, Delete, Commit e Rollback). - DCL (Linguagem de controle de dados) uma subclasse da DML responsável pelas permissões de acesso ao banco; (Grant e Revoke). Restrições de Integridade, que auxiliam no processo de definição da integridade dos dados, protegendo contra corrupções, inconsistências e falhas do sistema de computação. Além dessas características principais, ainda podemos citar: - Visões, onde são especificadas as consultas disponíveis através de tabelas virtuais (Views). 5 PROJETO FÍSICO DE BANCO DE DADOS - Gatilhos a fim de automatizar processos no banco (Triggers). Outra característica é a capacidade de cancelar uma série de atualizações ou gravar depois de concluir uma série de atualizações. 1.3 CRIANDO UM BANCO DE DADOS Esta etapa trata da Data Definition Language (DDL). O processo básico consiste simplesmente em atribuir tipo de dado e tamanho para cada um dos atributos que foram identificados. Atenção: - Cada Sistema Gerenciador de Banco de Dados adota um terminador de comando. O mais comum é o ponto e vírgula (;) - Quanto aos comandos apresentados, os Sistemas Gerenciadores de Banco de Dados não são sensíveis a letras maiúsculas ou minúsculas. Contudo, o conteúdo das colunas normalmente é sensível, portanto muito cuidado. 1.3.1 Desnormalização de dados Em uma análise mais profunda, é muitas vezes conveniente avaliar a necessidade de alguns campos redundantes na tabela. Esse processo é chamado de desnormalização de dados. Ocorre que, quando idealizamos o modelo de dados, utilizamos o conceito de processador perfeito, em que as informações são transmitidas sem custo instantaneamente. No entanto, mesmo bancos de dados avançados possuem limitações. Esta técnica pode ser exemplificada pelo caso dos campos calculados que são eliminados na 3ª Forma Normal. Quando se tem um grande banco de dados que trata de Notas Fiscais, por questões de performance costuma-se “driblar” um pouco a regra e coloca-se o valor total da nota como um campo na tabela. Isso pode ser feito quando o banco de dados possui mecanismo de controle de transações, em que uma eventual gravação no banco de dados implique necessariamente a gravação na outra ponta. Esta operação deve ser feita por meio de gatilhos (triggers) para garantir a integridade das informações. 1.3.2 Definição de dados Antes de criar as tabelas no banco de dados, é preciso definir quais são as características de cada um dos campos. As características que o SQL exige são o tipo do dado e o tamanho de cada campo. As informações aqui apresentadas são utilizadas pela maioria dos bancos de dados. Eventuais mudanças devem ser consultadas na documentação do Sistema Gerenciador de Banco de dados adotado. TIPO DE DADO DESCRIÇÃO Integer ou Int Número positivo ou negativo inteiro. O número de bytes utilizado varia de acordo com o banco de dados utilizado. Smallint Mesma função do Inteiro, mas ocupa cerca da metade do espaço 6 PROJETO FÍSICO DE BANCO DE DADOS Numeric Número positivo ou negativo de ponto flutuante. Deve-se informar tamanho do campo e a quantidade de casas decimais. Decimal Semelhante ao Numeric, mas, em alguns bancos de dados, poderá ter uma maior precisão após a vírgula. Real Número de ponto flutuante de simples precisão. A diferença básica é que os valores serão armazenados em representação exponencial, portanto será arredondado para o nível mais próximo de precisão. Double Precision Número de ponto flutuante de dupla precisão. Comporta-se como o Real, mas permite maior aproximação de resultados. Float Número de ponto flutuante em que você define o nível de precisão (número de dígitos significativos) Bit Armazenamento de um número fixo de bits. O número de bits deve ser indicado, do contrário o padrão será 1. Bit Varying Igual ao Bit, permitindo armazenar valores maiores. Normalmente, utilizase para armazenar imagens. Date Permite armazenar datas Time Permite armazenar horários Timestamp Permite armazenar uma combinação de data e hora Character ou char Permite armazenar cadeias de caracteres (letras, símbolos e números). O tamanho informado é fixo e indica o tamanho máximo da cadeia de caracteres. Character Varying ou Varchar Permite armazenar cadeias de caracteres, mas com tamanho variável. Neste caso, especifica-se o tamanho máximo da coluna. Se for utilizado menos espaço que o máximo definido, o espaço restante não será ocupado. Interval Intervalo de data ou hora 1.3.3 Adaptando o modelo de dados Com base nas informações acima, adaptaremos nosso modelo de dados. Para nossos estudos utilizaremos o modelo lógico abaixo: 7 PROJETO FÍSICO DE BANCO DE DADOS Figura 1- Modelo de dados Notação dos relacionamentos 1:n – FK obrigatória. Parte da chave primária 1:n – FK não obrigatória n:m – Muitos para muitos (cria-se a tabela associativa) 1.3.4 Criação de Tabelas Tabelas são as estruturas mais importantes de um banco de dados. Nas tabelas estará o conteúdo que representa cada objeto do mundo real. As tabelas criadas no banco de dados ficam armazenadas em outras tabelas internas do gerenciador de banco de dados denominadas Dicionário de Dados. 8 PROJETO FÍSICO DE BANCO DE DADOS Sintaxe: Exemplo: CREATE TABLE nome_da_tabela CREATE departamento (coluna1 tipo_de_dado constraint, (dep_id INTEGER NOT NULL, coluna2 tipo_de_dado constraint, dep_nome VARCHAR(100), coluna3 tipo_de_dado constraint); dep_atividade VARCHAR(100), empr_id INTEGER NOT NULL); 1.3.5 Constraints – Integridade Referencial – Restrição de Integridade (RI) Constraints são regras agregadas a colunas ou tabelas. Assim, pode-se definir um campo como obrigatório ou não, ou aceitar apenas alguns valores predefinidos. No caso de regras aplicadas a tabelas, tem-se a definição de chaves primárias (PK) e estrangeiras (FK). Um bom Sistema Gerenciador de Banco de Dados deve evitar a entrada de informação incorreta ou inconsistente em sua base de dados, garantindo, com isso, a qualidade da informação inserida. Uma restrição de integridade (RI) é uma condição especificada no esquema da base de dados para restringir a informação a ser armazenada. As RI são especificadas e conferidas em dois momentos diferentes: - Na especificação da RI: se dá na definição do esquema da base de dados pelo usuário ou pelo administrador da base de dados (DBA); - Na conferência da RI: é feita pelo banco de dados toda vez que uma relação é modificada por uma aplicação sendo executada. 1.3.5.1 TIPOS DE CONSTRAINTS MAIS COMUNS As constraints podem variar muito de um banco para outro. a) Chave Primária: é a coluna identificadora de um registro na tabela. Para representála basta acrescentar a palavra chave PRIMARY KEY seguida do nome da coluna. Exemplo: ... PRIMARY KEY (cliente_id), ... b) Chave Estrangeira: é o campo que estabelece o relacionamento entre duas tabelas. Dessa forma, deve-se especificar na tabela que contém a chave estrangeira quais são essas colunas e a qual tabela está relacionada. Ao determinar este tipo de relacionamento, fica garantida a integridade das informações. Os valores presentes na coluna definida com chave estrangeira devem ter um correspondente na outra tabela. 9 PROJETO FÍSICO DE BANCO DE DADOS Exemplo1: ... FOREING KEY(coluna) REFERENCES (tabela) ON UPDATE ação ON DELETE ação Ação: Determina qual ação o banco de dados deve tomar quando for excluída ou alterada uma linha da tabela que contém referência a esta chave. Pode ser: - SET NULL: Altera o conteúdo da coluna para nulo, perdendo a referência, sem deixar valores inconsistentes. - SET DEFAULT: Altera o conteúdo da coluna pra um valor especificado na cláusula DEFAULT, se houver. - CASCATE: Exclui ou altera todos os registros que se relacionam com a ele. - NO ACTION: Em caso de alteração, não modifica os valores que se relacionam a eles. - RESTRICT: Não permite a exclusão da PK. Exemplo2: CREATE TABLE funcionario (fun_id fun_admissao DATE, dept_id INTEGER, INTEGER, fun_nome VARCHAR(50), PRIMARY KEY (fun_id), FOREIGN KEY (dept_id) REFERENCES departamento ON DELETE CASCADE ON UPDATE NO ACTION c) DEFAULT: Atribui um conteúdo padrão a uma coluna da tabela. Exemplo: ... prod_quantidade INTEGER DEFAULT 1, ... d) NOT NULL: Indica que o conteúdo de uma coluna não poderá ser Nulo. Lembre-se, em banco de dados SQL, colunas sem valor atribuído possuem conteúdo Nulo. 10 PROJETO FÍSICO DE BANCO DE DADOS Exemplo: ... cliente_nome VARCHAR(50) NOT NULL, ... e) UNIQUE: Indica que não pode haver repetição no conteúdo da coluna. Não é a mesma coisa que chave primária. A chave primária, além de não permitir repetição, não pode conter valores nulos, dentre outras característica não existentes nesta restrição. Exemplo: ... cliente_CPF NUMERIC(11) UNIQUE, ... f) CHECK: Definição de domínio: Um domínio é uma expressão de valores possíveis para o conteúdo de uma coluna. Exemplo: ... sexo CHAR(1) CHECK (UPPER(sexo) = ‘M’ OR UPPER(sexo) = ‘F’), … 1.3.6 Alteração da estrutura da tabela Para alterar a estrutura de uma tabela, utilizamos o comando ALTER TABLE. 1.3.6.1 ACRESCENTAR NOVAS COLUNAS O comando utilizado para acrescentar novas colunas é muito semelhante ao da criação de colunas em uma tabela: Sintaxe: Exemplo: ALTER TABLE nome_da_tabela ADD ALTER TABLE departamento coluna1 tipo_de_dado constraint, coluna2 ... tipo_de_dado ADD dep_ramal NUMERIC(4) UNIQUE constraint, 1.3.6.2 ACRESCENTAR NOVAS CONSTRAINTS 11 PROJETO FÍSICO DE BANCO DE DADOS O comando utilizado para acrescentar novas constraints é muito semelhante ao da criação de constraints em uma tabela: Sintaxe: Exemplo: ALTER TABLE nome_da_tabela ALTER TABLE departamento ADD (constraint) ADD PRIMARY KEY (dep_id) 1.3.6.3 MODIFICAR COLUNAS O comando utilizado para modificar qualquer característica de uma coluna. Sintaxe: Exemplo: ALTER TABLE nome_da_tabela ALTER TABLE departamento ALTER COLUMN (coluna tipo_dado constraint) ALTER COLUMN VARCHAR(150); dep_nome TYPE ALTER TABLE dep_nome ALTER NULL; COLUMN dep_nome SET NOT Os comandos de alteração da estrutura/características de um banco de dados pode variar de um Sistema Gerenciador de Banco de Dados para outro. Portanto, é importante, em caso de dúvidas, consultar o manual da ferramenta. O manual do PostgreSql pode ser encontrado em: <http://www.postgresql.org/docs/9.1/static/sql.html> 1.3.6.4 EXCLUINDO ELEMENTOS Pelo padrão SQL, deveria ser possível excluir colunas ou constraints de uma tabela. Alguns bancos de dados não permitem a exclusão de colunas. Sintaxe: Exemplo: ALTER TABLE nome_da_tabela ALTER TABLE departamento DROP elemento DROP dep_ramal; ALTER TABLE departamento DELETE PRIMARY KEY; Abaixo um exemplo onde várias alterações estão sendo realizadas em um só comando: ALTER TABLE fornecedores ADD cgc NUMERIC(14), DROP tipofornecedor, 12 PROJETO FÍSICO DE BANCO DE DADOS ADD CONSTRAINT ck_email CHECK (e_mail CONTAINING ‘@’ OR e_mail IS NULL) 1.3.7 Eliminando uma Tabela Para eliminar uma tabela do banco de dados, utilizamos o comando DROP TABLE seguido do nome da tabela. Alguns bancos somente permitirão esta operação se esta não estiver relacionada à outra tabela. Sintaxe: Exemplo: DROP TABLE nome_da_tabela DROP TABLE departamento; 5. EXERCÍCIOS Usando a Linguagem SQL crie o Schema de banco de dados para o seguinte modelo lógico: 13 ÍNDICE 2 Parte íNDICE Q uando criamos a estrutura de um banco de dados temos que usar artifícios que melhorem a performance na recuperação dos dados que lá serão armazenados. Dentre várias técnicas existentes para melhorar a esta performance existe a criação de índices. O Índice é um arquivo auxiliar associado a uma Tabela. Sua função é acelerar o tempo de acesso às linhas de uma Tabela, criando ponteiros para os dados armazenados em colunas especificas. O Banco de dados usa o Índice de maneira semelhante ao índice remissivo de um livro, verifica um determinado assunto no índice e depois localiza a sua posição em uma determinada página. 1.1 CRIAÇÃO DE ÍNDICE O índice serve para prover um acesso rápido a linhas das tabelas. Por meio dele é possível unir uma ou mais colunas por onde o acesso é mais frequente. Exemplo: Temos uma tabela de pessoas e normalmente queremos fazer buscas em ordem alfabética. O nome, não é uma boa chave primária para a tabela por ser alfanumérica, o que deixa as pesquisas mais lentas e também pela repetição de nomes. Dessa forma, cria-se um índice para o nome da pessoa. Assim, garante-se um acesso mais rápido aos nomes, visto que os dados buscados estariam fora de sequência. Os valores indexados são armazenados em um objeto do banco de dados em ordem, o que permite ao Sistema Gerenciador de Banco de Dados pesquisar primeiro no índice, para depois buscar na tabela. É importante ressaltar que alguns bancos de dados necessitam de índices constantemente atualizados, pois eventualmente estes perdem a referência ao dado. O índice quando criado com critérios bem definidos traz uma enorme vantagem, mas por outro lado pode trazer enormes problemas, visto que todo trabalho de um Sistema Gerenciador de Banco de Dados envolve um custo. Não é conveniente sair criando índices para cada um dos campos da tabela sem que haja uma razão para isto. Quando um índice é criado uma das tarefas do Sistema Gerenciador de Banco de Dados é mantê-lo atualizado a cada nova inclusão, exclusão e alteração nos dados. Além do mais, o índice também ocupa espaço no banco de dados, pois é criado um novo objeto que manterá informações indexadas e armazenará a referência à linha física da tabela. 14 ÍNDICE NOTA: Se são criados muitos índices, pode-se prejudicar o desempenho do banco de dados Se são criados poucos índices, pode-se prejudicar o desempenho da aplicação. Quando criar índices? a) Chaves Primárias: se o banco de dados não criá-las automaticamente, é necessário fazê-lo manualmente. b) Chave Estrangeira: é sempre recomendável criar, principalmente, quando a tabela que faz referência é muito grande. c) Colunas utilizadas frequentemente na cláusula WHERE ou JOIN do comando SELECT. Quando não criar índices? a) Colunas com muitos valores iguais. b) Colunas com muito conteúdo NULO (não inicializados). c) Tabelas pequenas. NOTA: Sempre que uma consulta estiver muito lenta, verifique a possibilidade de criar índice para facilitar a busca. Para criar um índice, utilize a seguinte sintaxe: Sintaxe: Exemplo: CREATE INDEX nome_index ON nome_tabela (coluna [ASC|DESC]) CREATE INDEX idx_dep_nome ON departamento(dep_nome) CREATE INDEX idx_dep_varios ON departamento(dep_nome,dep_ramal) Para excluir um índice, utilize a seguinte sintaxe: Sintaxe: Exemplo: DROP INDEX nome_index DROP INDEX idx_dep_varios Não é possível modificar índices. Para alterar um índice, elimine-o e, depois, recrie-o. NOTA: Ao eliminar uma tabela, os índices e as constraints são eliminados automaticamente. 15 ÍNDICE 1.2 ACESSO A DADOS EM UM SISTEMA GERENCIADOR DE BANCO DE DADOS Para compreender melhor a utilização de índices é importante conhecer a estrutura de acesso à dados em um Sistema Gerenciador de Banco de Dados. Figura 2 - Organização de um SGBD Neste contexto, observamos que os bancos de dados são armazenados fisicamente como arquivos de registros, que são geralmente armazenados em discos magnéticos. Assim, os meios de armazenamento formam uma hierarquia de armazenamento que inclui duas categorias principais: - Armazenamento Principal: Operados diretamente pela CPU, como a Memória Principal e Memória Cache. - Armazenamento Secundário: Inclui os discos magnéticos, óticos e fitas. Geralmente possuem uma capacidade maior, porém são mais lentos e não podem ser acessados diretamente pela CPU. Figura 3 - Estrutura de armazenamento 16 ÍNDICE A maioria dos bancos de dados está armazenada sob maneira permanente (ou persistente) sob a forma de armazenamento secundário. Geralmente, o Sistema Gerenciador de Banco de Dados possui diversas opções disponíveis para organizar os dados, e o processo relativo ao projeto de banco de dados físico envolve escolher entre as opções, as técnicas de organização de dados. Os dados armazenados em disco são organizados na forma de arquivos de registros. Cada qual é uma coleção de valores de dados que podem ser interpretados como fatos sobre entidades, seus atributos e seus relacionamentos. Para acessar uma informação no banco de dados considera-se o tipo de requisição e a estrutura utilizada para organização e acesso às chamadas páginas de dados. 1.2.1 Organização dos dados em Memória Secundária - Arquivos Heap ou pile (aglomerado): Melhor opção quando o acesso típico é uma busca sequencial, sem ordem, recuperando todos os registros. Os registros são posicionados no arquivo pela ordem na qual foram incluídos. São utilizados pelos índices secundários e utilizados para coletar e armazenar registros de dados para utilização futura. Exemplo: um simples arquivo de registros de dados. - Arquivos Ordenados: Melhor opção se os registros devem ser recuperados em alguma ordem, ou quando somente uma porção contígua dos registros ordenados é necessária. Exemplo: Arquivo de dados, possivelmente ordenado, além de vários índices. - Arquivos Hashed: Melhor para seleções de igualdade. Uma função hash é aplicada ao valor do campo de um registro e retorna o endereço do bloco do disco no qual o registro está armazenado. Para a maioria dos registros, precisamos somente do acesso a um único bloco para recuperar aquele registro. Não é adequado para acesso sequencial ordenado. Existem outras maneiras ou possibilidades para organizar arquivos, como árvores-B e arquivos de registro mistos, que implementam relacionamentos entre registros de diferentes tipos fisicamente como parte da estrutura de armazenamento. 1.3 INDEXAÇÃO Como visto, os índices são estrutura de dados que recebem como entrada uma propriedade de registro (por exemplo, um valor de um ou mais campos) e os encontra com essa propriedade rapidamente. Um índice permite localizar um registro sem ter que examinar mais que uma pequena fração dos registros possíveis. Assim sendo, podemos concluir que os campos cujos valores o índice se baseia formam a chave de pesquisa. NOTA Índices são estruturas de dados auxiliares cujo único propósito é tornar mais rápido o acesso a registros baseado em certos campos, chamados campos de indexação. 17 ÍNDICE 1.3.1. Tipos de Índices - Índices primários - baseado na chave de ordenação; - Índices de agrupamentos (clustering) - baseado no campo de ordenação não-chave de um arquivo; - Índices secundários - baseado em qualquer campo não ordenado de um arquivo; - Índices multiníveis; - Árvores B e B+; - Tabelas Hash; Um dos mais antigos esquemas de índice utilizados em sistema de banco de dados é chamado de arquivo indexado sequencialmente, que são projetados para aplicações que requerem tanto o processamento sequencial de um arquivo inteiro quanto o acesso aleatório a registros individuais. Estes índices podem ser: - Densos: É quando o número de entradas do índice é igual ao número de registros no arquivo de dados. Cada registro no arquivo de índice contém o endereço de um registro no arquivo de dados. P o n teiro s Figura 4 - Índice denso sobre um arquivo de dados sequenciais - Esparços: Usa-se menos espaço de armazenamento que o índice denso ao custo de um tempo um pouco maior para localizar um registro dada a sua chave. É quando o número de entradas do índice é igual ao número de blocos do arquivo de dados. Apenas alguns registros de dados são representados no arquivo de índices. Em geral, aponta para o 1º registro de bloco. P o n teiro s Figura 5 - Índice esparso sobre um arquivo de dados sequenciais 18 ÍNDICE 1.3.1.1 ÍNDICE PRIMÁRIO É o índice associado a uma chave primária. Nada mais é do que um arquivo ordenado, cujos registros são de tamanho fixo com dois tampos. O primeiro, do mesmo tipo do campo chave de ordenação (chave primária) e o segundo é um ponteiro para um bloco do disco ou arquivo de dados. Utiliza índice esparço. Figura 6 - Índice Primário 1.3.1.2 ÍNDICE DE AGRUPAMENTO (CLUSTERING) Se os registros de um arquivo estiverem ordenados fisicamente por um campo que não seja chave, ou seja, que não possui um valor distinto, esse campo é chamado de campo clustering. Semelhante ao índice primário possui dois campos de ordenação, no entanto o primeiro campo é do mesmo tipo do campo clustering e o segundo é um ponteiro para o bloco. Neste sentido sempre há a reserva de um bloco inteiro para cada valor de campo clustering, melhorando operações de inclusão e exclusão. Em simples palavras é um índice associado a um campo não chave. Utiliza índice esparço. 19 ÍNDICE Figura 7 - Índice de Agrupamento 1.3.1.3 ÍNDICE SECUNDÁRIO No índice secundário o campo de indexação é um campo não ordenado do arquivo de dados. Pode haver muitos índices secundários para um mesmo arquivo. Dois casos: - O campo de indexação é um campo chave (às vezes chamado de chave secundária). - O campo de indexação não é chave. 20 ÍNDICE É possível que mais de um registro tenha o valor da chave. Isso ocorre, por exemplo, quando a chave de pesquisa não é a chave primária; Um índice secundário é um índice denso, normalmente com duplicatas. O índice consiste em pares de chaves-ponteiro. Neste caso, sempre que são permitidas chaves de pesquisas duplicadas. Figura 8 - Índice Secundário Outra possibilidade para índices secundários é a entrada de índice para um bloco de dados onde a menor chave de pesquisa é nova, isto é, a chave não apareceu em um bloco anterior; Se não há chave de pesquisa nova no bloco, então sua entrada de índice contém a única chave de pesquisa encontrada nesse bloco; Pode-se encontrar os registros de uma chave de pesquisa K examinando-se o índice para a primeira entrada cuja chave é: - Igual a K - Menor que K, mas a próxima chave é maior que K Segue-se o ponteiro da entrada. Se for encontrado pelo menos um registro com a chave de pesquisa K, então a busca continua em blocos adicionais, até encontrar todos os registros com a chave de pesquisa K. Figura 9 - Índice Secundário 21 ÍNDICE Usa o índice esparso indicando a nova chave de pesquisa mais baixa em cada bloco. ORGANIZANDO AS IDÉIAS: Campos ordenados Índice primário Índice de agrupamento Campo chave Campo não chave Tipo de Índice Primário Agrupamento Secundário (chave) Secundário (não chave) Campos não ordenados Índice Secundário (chave) Índice Secundário (não chave) Número de Entradas Nº de blocos no arquivo de dados Nº de valores distintos do campo de indexação Nº de registros no arquivo de dados Nº de registros no arquivo de dados Nº de valores distintos de indexação Denso ou Esparço Esparço Esparço Denso Denso Esparço 1.3.1.4 ÍNDICES MULTINÍVEIS Uma pesquisa binária é aplicada ao índice para localizar ponteiros para um bloco do disco ou para um registro no arquivo que possui um valor de campo índice específico. Se o arquivo de índices se torna muito grande para ser armazenado em bloco de disco, é interessante indexá-lo em mais de um nível. A vantagem é que um índice pequeno pode ser mantido em memória e o tempo de busca é mais baixo; por outro lado, muitos níveis de índices podem aumentar a complexidade do sistema, sendo mais recomendado a utilização de outra estrutura como a que veremos mais a frente: árvore-B. Figura 10 - Índices Multiníveis NOTA Um índice multinível é um “índice de índice”. 22 ÍNDICE - Primeiro nível: arquivo ordenado pela chave de indexação, valores distintos, entradas de tamanho fixo. - Demais níveis: índice primário sobre o índice do nível anterior e assim sucessivamente até que no último nível o índice ocupe apenas um bloco. - Número de acessos a bloco: um a cada nível de índice, mais um ao bloco do arquivo de dados. Figura 11 - Índice Primário de dois Níveis A figura acima exemplifica um índice primário de dois níveis que se parece com a organização ISAM (Indexed Sequential Acess Method - Método de Acesso Seqüencial Indexado). O problema dos índices multiníveis é que são arquivos fisicamente ordenados, portanto, ineficientes na inserção e remoção. Para solucionar este problema podem-se adotar as seguintes soluções: - Deixar algum espaço em cada um dos blocos para inserção de novas entradas. - Estruturas de dados: Árvores B e suas variações. a) Gerenciamento de Índices nas Modificações de Dados Os arquivos sequenciais apresentam problemas com o tempo, visto que um bloco não é mais suficiente para armazenar o conteúdo que era antes armazenado. Para contornar esse problema é possível adotar as seguintes soluções: - Criar blocos de estouro (overflow). Blocos de estouro não têm entradas em um índice esparso; 23 ÍNDICE - Inserir novos blocos na ordem sequencial; - Se não houver lugar para inserir informação em um bloco, alguma informação poderá ser transferida para outro bloco. De igual forma, blocos vazios poderão ser combinados. 1.3.1.5 ÁRVORE B Árvores B e B+ são casos especiais de estruturas de dados bastantes conhecidas do tipo árvore. Uma árvore é formada por nós, e cada nó na árvore, exceto um nó especial denominado raiz, possui um nó pai e diversos nós filhos. Um nó que não possui filhos é chamado de nó folha. Um modo de implementar uma árvore é possuir tantos ponteiros em cada nó quantos forem os nós filhos daquele nó. E para percorrer todos os nós, da árvore e da sub-árvore é necessário fazer uso da recursividade. Figura 12 - Árvore de Grau 3 a) Árvore de Pesquisa Uma árvore de pesquisa é um tipo especial de árvore que se utilizada para guiar a pesquisa por um registro, dado o valor de um dos campos do registro. A árvore de busca é projetada com dois objetivos: - Manter a árvore balanceada; e - Evitar o desperdício de espaço dentro de um nó, à custa de maior complexidade nos algoritmos de inserção e remoção. Regras: - Os nós da sub-árvore esquerda contém valores menores que a raiz; - Os nós da sub-árvore direita contém valores maiores ou iguais a raiz; - As sub-árvores são árvores binárias de pesquisa. 24 ÍNDICE Figura 13 - Distribuição nos nós b) Árvore B Em computação, Árvore B ou B-Tree é uma estrutura de dados árvores que são muito utilizadas em banco de dados e sistema de arquivos. Para inserir ou remover variáveis de um nó, o nó não poderá ultrapassar sua ordem e nem ser menor que sua ordem dividida por dois. Árvores B não precisam ser rebalanceadas como são frequentemente as árvores de busca binária com Árvore AVL. Árvores B têm vantagens substanciais em relação a outros tipos de implementações quanto ao tempo de acesso e pesquisa aos nós. Uma árvore B de ordem "m" (máximo de filhos para cada nó) é uma árvore que atende as seguintes propriedades: - Cada nó tem no máximo "m" filhos - Cada nó (exceto a raiz e as folhas) tem pelo menos "m/2" filhos - A raiz tem pelo menos dois filhos se a mesma não for uma folha - Todas as folhas aparecem no mesmo nível e não carregam informação - Um nó não-folha com "k" filhos deve ter k-1 chaves As vantagens da árvore B são: - Melhor desempenho por ter um número menor de nós do que uma árvore binária. Por exemplo: Menos nós significa menor altura que resulta em menos acessos ao disco. - Por garantir poucos ponteiros entre os nós, há uma economia de espaço. - Maior rapidez em buscas pela utilização de chaves primárias. - Sua estrutura é dinâmica, ajustando automaticamente o balanceamento da árvore, a cada inclusão/exclusão. - Permite um tempo de acesso de dados menor, em uma busca aleatória, por causa de suas ramificações. Algoritmos INSERÇÃO 1. Primeiro pesquise a chave, para ter a certeza de que esta não existe na árvore. 2. Busque a posição onde esta será inserida. Teste para ver se o nó está cheio. 3. Se nó estiver vazio, insira o valor dentro dele, senão execute uma subdivisão do nó da seguinte forma: 3.1. Verifique se o nó-pai está vazio, se sim execute 3.1.1. Passe o elemento do meio do nó para seu pai. 3.1.2. Divida o nó em dois nós iguais. 25 ÍNDICE 3.2. Se o nó pai estiver cheio, repita as duas linhas acima recursivamente. (Caso todos os nós-pai estiverem cheios, inclusive a raiz, deve ser criada uma nova raiz aumentando assim a altura da árvore. 3.3. Somente depois de satisfeita todas as divisões necessárias, insira nova chave. EXCLUSÃO 1. Primeiro pesquise a chave para ter a certeza de que esta existe na árvore. 2. Se existir, verifique se está em folha, e faça a exclusão. 3. Se existir e não estiver em folha, substitua esta chave pela menor chave do filho a direita. 3.1. Se o número de chave no nó, for maior do que (Ordem/2 - 1), então termine a rotina. 3.2. Senão redistribua as chaves entre os nós vizinhos. BUSCA 1. Indique a chave que será procurada. 2. Pesquise desde a raiz até encontrá-la, e então retorne o nó e a posição desta. 3. Se a chave não for encontrada, continue o laço até encontrar um nil das folhas. Exemplo: Figura 14 - Operações na árvore Para melhor compreensão do conceito de Árvore B, visite o link http://www.cse.ohiostate.edu/~bondhugu/acads/234-tree/index.shtml e simule a inserção, exclusão e busca de valores na árvore. 1.3.1.5 HASHING São particularmente adequados para pesquisas por igualdade. A idéia principal é usar uma função de hashing. Esta função mapeia um valor da chave de pesquisa (atributo de uma tabela) em um registro ou balde (bucket) de registros. 26 ÍNDICE Figura 15 - Hashing A balde é uma unidade de armazenamento que contém um ou mais registros (um balde é tipicamente um bloco do disco). Numa organização de ficheiro em hashing, obtemos o balde de um registro a partir do valor da sua chave de pesquisa. Mais concretamente, o balde é obtido através do cálculo da função de hash para aquela chave de pesquisa. A função de hash h é uma função do conjunto de todos os valores da chave K para o conjunto dos endereços de todos os baldes B. A função de hash é usada para localizar registros para as operações de acesso, inserção, bem como eliminação. Observe os exemplos de busca, inserção e exclusão. A desvantagem do hashing estático pode conduzir a longas cadeias de transbordo. Busca Inserção Exclusão 27 ÍNDICE Longas cadeias de transbordo podem desenvolver-se e degradar o desempenho. a) Hashing Estático O hashing estático escolhe a função hash com base no tamanho do arquivo atual, ou com base no tamanho antecipado do arquivo com base em um ponto no futuro. Esta técnica reorganiza periodicamente a estrutura de hash. b) Hashing Dinâmico Diferente do hashing estático a função hash é modificada dinamicamente para acomodar o crescimento ou encolhimento do banco de dados, usando o hashing extensível. c) Hashing Extensível Divide e une os buckets enquanto o banco de dados cresce e encurta. A Eficiência do espaço é mantida e a função hash gera valores por intervalos relativamente grandes. Outra característica é que os buckets são criados por demanda. 28 DATA MANIPULATION LANGUAGE - DML 3 Parte DATA MANIPULATION LANGUAGE - DML U ma vez criada a estrutura física do banco de dados é necessário popular as tabelas. Para isso utilizam-se os comandos da DML (Data Manipulation Language). Nesta seara encontramos comandos como Insert, Update, Delete, Commit e Rollback. DML(Linguagem de Manipulação de Dados): Permite a inclusão, remoção, seleção ou atualização de dados armazenados no banco de dados; Controle de acesso, permitindo proteção dos dados de manipulações não autorizadas; 3.1 INSERT Comando responsável por adicionar um ou mais registros na tabela de Banco de Dados. Os campos que forem omitidos recebem valores NULOS (“NULL”). Sintaxe: INSERT [TRANSACTION transaction] INTO <object> [(col [, col …])] {VALUES (<val> [, <val> …]) | <select_expr>}; <object> = tablename | viewname <val> = {:variable | <constant> | <expr> | <function> | udf ([<val> [, <val> …]]) | NULL | USER | RDB$DB_KEY | ? } Exemplo: INSERT INTO clientes (id, nome) VALUES (1, ‘Nome do Cliente’); NOTE BEM: A lista de colunas é opcional no comando Insert; Caso não seja definida, assume-se a sequência da criação da tabela; Para maior clareza do código e evitar erros em caso de reestruturação da tabela recomenda-se utilizar a lista de colunas. Valores alfanuméricos devem ser especificados entre aspas simples ‘exemplo’; Valores numéricos não devem conter esse separador. 29 DATA MANIPULATION LANGUAGE - DML Valores do tipo data devem ser tratados de acordo com as especificações do SGBD; Ao incluir uma linha no Banco de Dados, o gerenciador checará as restrições de integridade (Constraints). Assim chaves primárias, estrangeiras, domínios, etc são checadas no momento da inclusão. Caso alguma restrição for violada, a linha não será incluída e uma mensagem será emitida pelo SGBD. As colunas que não tiverem valores atribuídos terão conteúdo NULL ou default, caso não haja a restrição NOT NULL. Para inserir um valor nulo deve informar NULL no lugar correspondente a coluna. 3.1.1 Inserindo Várias Linhas Para incluir diversas linhas em uma tabela, utiliza-se o comando INSERT em conjunto com o comando SELECT. Isso irá copiar as linhas de uma tabela para outra; A lista de colunas do SELECT deve corresponder a totalidade de colunas da tabela. Exemplo: INSERT INTO CURRENT_DATE; vendas_old SELECT * FROM vendas WHERE data_venda = Outra forma é usando o comando INSERT por várias vezes consecutivas acompanhado do finalizador ponto-e-virgula . Ou também após o comando VALUES colocando os dados entre parênteses separados por vírgula. Exemplo: INSERT INTO vendas_old (prod_id, prov_valor) VALUES (1, 2.00), (2, 3.00), (3, 5.00), (4, 10.00); 3.1.2 Laboratório INSERT INTO autor (aut_id, aut_nome) VALUES (1, ‘Renato Russo’); INSERT INTO autor (aut_id, aut_nome) VALUES (2, ‘Ton Jobim’); INSERT INTO musica (mus_id, mus_nome, mus_duracao) VALUES (1, ‘sera’, ‘00:02:28’); INSERT INTO musica (mus_id, mus_nome, mus_duracao) VALUES (2, ‘Ainda é Cedo’, ‘00:03:55’); INSERT INTO gravadora (grav_id, grav_nome, grav_tel, grav_contato) VALUES (1, ‘EMI’, ‘1122334455’, ‘José Vitor’); INSERT INTO cd(cd_id, cd_nome, cd_preco, dt_lancamento, grav_id) VALUES (1, ‘Mais do Mesmo’, 20.50, ’21.12.1998’, 1); 3.1.3 Exercícios a) Popule as tabelas do banco de dados conforme representado abaixo: 30 DATA MANIPULATION LANGUAGE - DML ATENÇÃO: Observe a sequência de inserção dos dados. 3.2 UPDATE Comando responsável alteração de ou um mais registros na tabela de Banco de Dados. Sintaxe: UPDATE [TRANSACTION transaction] {table | view} SET col = <val> [, col = <val> …] [WHERE <search_condition> | WHERE CURRENT OF cursor]; Exemplo: UPDATE cliente SET data_inclusao = CURRENT_DATE; 3.2.1 Where Comando responsável por especificar qual registro da tabela será alterado quando usado em conjunto com a cláusula UPDATE. 31 DATA MANIPULATION LANGUAGE - DML NOTE BEM: O conteúdo a ser atualizado deve respeitar o tipo de dado da coluna. A condição expressa no comando servirá para definir quais linhas devem ser atualizadas. Caso a cláusula WHERE contenha a chave primária somente uma linha da será atualizada. Todas as restrições de integridade (Constraints) serão avaliadas pelo SGBD. 3.2.2 Laboratório Se quisermos alterar o preço de um CD, podemos utilizar o seguinte comando filtrando na cláusula WHERE a chave primária da tabela CD: Exemplo: UPDATE cd SET cd_preco = 21,00 WHERE cd_id = 1; Se o aumento for para todos os CDs de uma determinada gravadora, utilizaremos como filtro da cláusula WHERE grav_id: Exemplo: UPDATE cd SET cd_preco = 22,00 WHERE grav_id = 1; Imagine que ocorra um aumento de preço generalizado para todos os CDs em 5%. Assim multiplica-se o próprio preço do CD por 1,05. Exemplo: UPDATE cd SET cd_preco = cd_preco * 1,05; Outros Exemplos: UPDATE cd SET cd_preco = 15 WHERE cd_id = 1; UPDATE autor SET aut_nome = ‘B. Manilow’ WHERE aut_id = 2; UPDATE cd SET cd_preco = cd_preco * 1,10 WHERE grav_id = 3; UPDATE gravadora SET grav_nome = ‘E.M.I’, grav_tel = ‘2144332211’ WHERE grav_id = 1; 3.3 DELETE Comando responsável pela exclusão de ou um mais registros na tabela de Banco de Dados. Sintaxe: DELETE [TRANSACTION transaciona] FROM table {[WHERE <search_condition>] | WHERE CURRENT OF cursor}; 32 DATA MANIPULATION LANGUAGE - DML Exemplo: DELETE FROM VENDAS WHERE DATA_VENDA <= ‘01-01-1999’; 3.3.1 Where Comando responsável por especificar qual registro da tabela será excluído quando usado em conjunto com a cláusula DELETE. NOTE BEM: O comando DELETE pode, da mesma forma que o comando UPDATE, afetar uma ou mais linhas de uma tabela. Desta forma o escopo da exclusão será definido pela cláusula WHERE. Se for especificado na cláusula WHERE a chave primária, somente uma linha será excluída. Antes de uma exclusão, o gerenciador sempre fará uma checagem na integridade referencial. 3.4 CONTROLE BÁSICO DE TRANSAÇÕES Uma vez realizadas as operações de inclusão, alteração e exclusão, a modificação realizada não estará ainda no banco de dados. Ela é visível apenas na seção atual. Para que os comandos de Insert, Update e Delete sejam enviados para o banco é necessário utilizar os comandos COMMIT ou descartados pelo comando ROLLBACK. 3.5 LABORATÓRIO Neste primeiro exemplo, apenas o autor com código 1 será excluído (desde que não haja relacionamentos com outras tabelas): Exemplo: DELETE FROM autor WHERE aut_id = 1; Neste caso todos os CD’s da gravadora 2 serão excluídos: Exemplo: DELETE FROM cd WHERE grav_id = 2; 33 PESQUISA BÁSICA EM TABELAS 4 Parte PESQUISA BÁSICA EM TABELAS A gora que já sabemos criar, incluir, alterar e excluir informações nas tabelas, veja como podemos extrair informações do banco de dados. Para tanto o comando utilizado é o SELECT. Junto dele há uma imensa possibilidade de comandos como veremos a seguir. 4.1 SELECT É o comando utilizado para realizar buscas/pesquisas no banco de dados. Atrás dele há uma extensão de possibilidades que vão desde a simples extração do conteúdo de todas as linhas e colunas de uma tabela até a união de diversas tabelas, cálculos, agrupamentos, ordenações e filtragem de linhas e colunas. Sintaxe: SELECT [DISTINCT | ALL] {* | Coluna [, coluna, ...]} FROM tabela Onde: DISTINCT: Não mostra eventuais valores repetidos; ALL: Mostra todos os valores (Propriedade Default do comando select); *: Mostra todas as colunas da tabela; NOTE BEM: Para execução dos exemplos utilizaremos estrutura criada na parte 3 – DML. Exemplos: O exemplo mais simples de uma consulta é extrair todas as informações de uma tabela: SELECT * FROM cd; O exemplo a seguir demonstra como filtrar apenas algumas colunas da tabela: SELECT cd_id, cd_nome FROM cd; SELECT grav_id, grav_nome, grav_tel FROM gravadora; 34 PESQUISA BÁSICA EM TABELAS 4.1.1 Ordenando o resultado Em muitas situações a ordem mostrada nem sempre é a que esperamos. Para que os dados sejam mostrados da maneira que melhor nos atende usa-se a cláusula ORDER BY seguida pela coluna que desejamos que seja ordenada: Exemplo: SELECT aut_id, aut_nome FROM autor ORDER BY aut_nome; SELECT cd_id, cd_nome FROM cd ORDER BY cd_id; Se forem especificadas mais de uma coluna a serem ordenadas, o gerenciador primeiro ordenará pela primeira coluna e em seguida pelas demais: Exemplo: SELECT grav_id, cd_nome FROM cd ORDER BY grav_id, cd_nome; 4.1.2. Filtrando linhas Para filtrar linhas em uma pesquisa, utilizamos a cláusula WHERE. Assim, é definida uma expressão lógica (condição) que será validada e mostrará apenas as linhas que atenderem ao critério estabelecido. Sintaxe: SELECT [DISTINCT | ALL] {* | Coluna [, coluna, ...]} FROM tabela WHERE condição Para um resultado satisfatório devemos saber exatamente como construir condições que satisfaçam às nossas necessidades de busca para atingir nossos objetivos. Sempre que a condição especificada for verdadeira o resultado será mostrado. Para tanto é necessário utilizar-se de alguns operadores como veremos a seguir. 4.1.2.1 OPERADORES RELACIONAIS Estes operadores devem ser usados na definição das condições. Podemos testar igualdade, diferença, maior, menor, maior ou igual, menor ou igual. Os operadores devem ser colocados entre os argumentos que estão sendo comparados. OPERADOR SIGNIFICADO EXEMPLO = Igual aut_id = 2 < Menos que cd_preco < 10 <= Menor ou igual a cd_preco <= 10 35 PESQUISA BÁSICA EM TABELAS > Maior que cd_preco > 10 >= Maior ou igual a cd_preco <= 10 Diferente aut_id != 2 ou aut_id <> 2 != ou <> Exemplo: SELECT cd_nome, cd_preco FROM cd WHERE cd_preco > 12; NOTE BEM: Da mesma forma que podemos comparar uma coluna com um valor, podemos comparar com outra coluna. Sempre quando fazemos esse tipo de comparação, devemos obedecer ao tipo de dado que estamos comparando. 4.1.2.2 OPERADORES LÓGICOS Muitas vezes, apenas uma condição não é suficiente para determinarmos o critério de busca. Sempre que isso ocorrer, podemos utilizar operadores lógicos. OPERADOR SIGNIFICADO EXEMPLO AND e Condição-1 AND Condição-2 OR ou Condição-1 OR Condição-2 NOT ou ! não/negação NOT Condição AND Indica que as duas condições devem ser verdadeiras para que seja mostrada a linha. Exemplo: SELECT cd_nome, cd_preco, grav_id FROM cd WHERE cd_preco > 10 AND grav_id = 2; OR Utilizamos o operador OR sempre que quisermos que o resultado final seja verdadeiro. Exemplo: SELECT cd_nome, cd_preco, grav_id FROM cd WHERE cd_preco > 11 OR grav_id = 2 NOTE BEM Não há limitação no uso e na combinação de condições usando OR e AND. É conveniente utilizar parênteses para determinar o que se quer comparar. 36 PESQUISA BÁSICA EM TABELAS Exemplo: SELECT cd_nome, grav_id, cd_preco FROM cd WHERE (grav_id = 2 OR grav_id = 3) AND (cd_preco >= 17.50) NOT ou ! É utilizado para inverter o resultado de uma expressão lógica, negando o resultado da condição. Caso a condição seja verdadeira, será retornado falso e vice-versa. Exemplo: SELECT cd_nome, cd_preco FROM cd WHERE NOT (cd_preco > 15); 4.1.2.3 OPERADORES ESPECIAIS Existem alguns operadores que são utilizados para determinar melhor as linhas que queremos filtrar. São eles: IS NULL, IS NOT NULL, BETWEEN, LIKE e IN. IS NULL Sabemos que nem todas as colunas têm valores inicializados. Logo esse comando é utilizado para saber os campos que não foram inicializados: Exemplo: SELECT * FROM gravadora WHERE grav_tel IS NULL IS NOT NULL Compara a negação do comando anterior. Somente aqueles que tiverem conteúdo serão mostrados: Exemplo: SELECT * FROM gravadora WHERE grav_tel IS NOT NULL BETWEEN Esse operador serve para determinar um intervalo de busca. Quando desejarmos um intervalo entre números, datas, etc, utilizaremos o BETWEEN para simplificar a forma de escrevermos o comando. Normalmente é utilizado em conjunto com o AND. Exemplo: SELECT cd_nome, cd_dt_lancamento FROM cd WHERE cd.cd_dt_lancamento BETWEEN '1979-01-01' AND '2000-12-31'; 37 PESQUISA BÁSICA EM TABELAS LIKE Com esse operador podemos comparar cadeias de caracteres utilizando padrões de comparação para um ou mais caracteres. O caractere percentual (%) substitui zero, um ou mais caracteres e sublinha (_) substitui um caractere. EXPRESSÃO APLICAÇÃO LIKE ‘A%’ Todas as palavras que iniciem com a letra A LIKE ‘%A’ Todas as palavras que terminem com a letra A LIKE ‘%A%’ Todas as palavras que tenham a letra A em qualquer posição LIKE ‘A_’ String de dois caracteres que tenha a primeira letra A LIKE ‘_A’ String de dois caracteres que tenha o último caractere letra A LIKE ‘_A_’ String de três caracteres cuja segunda letra seja A LIKE ‘%A_’ Todas as palavras que tenham a letra A na penúltima posição LIKE ‘_A%’ Todas as palavras que tenha a letra A na segunda posição Exemplos: SELECT * FROM autor WHERE aut_nome LIKE 'R%'; SELECT * FROM gravadora WHERE grav_nome LIKE '_o%'; Um problema que pode surgir quando queremos fazer buscas utilizando os caracteres de substituição é tê-los na cadeia de caracteres que está sendo pesquisada. Neste caso devemos usar um caractere especial denominado ESCAPE. Exemplo: SELECT * FROM cd WHERE cd_nome LIKE '%\_%'ESCAPE '\'; IN Permite comparar o valor de uma coluna com um conjunto de valores. Utilizamos para substituir uma série de comparações seguidas da cláusula OR. Exemplo: SELECT * FROM autor WHERE aut_id IN (1, 3); Sua maior utilização é em subquerys (será visto posteriormente). 38 PESQUISA BÁSICA EM TABELAS 4.2. EXERCÍCIOS a) Liste todos os campos e linhas da tabela GRAVADORA; b) Liste todas as linhas dos campos CD_ID, CD_NOME, CD_PRECO da tabela CD; c) Liste todas as linhas dos campos AUT_ID, AUT_NOME da tabela AUTOR em ordem alfabética; d) Repita o comando anterior em ordem alfabética decrescente; e) Liste todos os CDs da gravadora 3; f) Liste as colunas CD_NOME, CD_PRECO dos CD’s cujos preços de venda sejam inferiores a 20,00 e sejam da GRAVADORA 3; g) Liste as colunas da tabela gravadora cujo GRAV_CONTATO seja nulo; h) Repita o comando anterior desta vez listando GRAV_CONTADO não nulo; i) Liste os CD’s cujos CD_PRECO esteja entre 15,00 e 30,00; j) Liste todos os CD’s cuja CD_DT_LANCAMENTO seja posterior ao ano 01/01/2000; k) Liste as MUSICAS cujo nome comece com A da tabela MUSICA; l) Liste os CD’s cuja segunda letra do CD_NOME seja a letra E; m) Liste os CD’s que possuam a letra O em qualquer posição do CD_NOME; n) Liste os CD’s que possuam CD_PRECO inferior a 30,00 em ordem decrescente de CD_DT_LANCAMENTO; o) Liste as músicas cuja MUS_ID seja 1, 3, 5; 39 CÁLCULOS E FUNÇÕES USUAIS 5 Parte CÁLCULOS E FUNÇÕES USUAIS U m recurso bastante útil e importante de SQL é a possibilidade de se realizar cálculos e totalizações de valores unitários. Podemos realizar cálculos quando realizamos buscas no banco de dados simplesmente aplicando um dos operadores aritméticos a coluna. Exemplo: Um aumento de 5% em todos os CD’s: SELECT cd_id, cd_nome, cd_preco, preco_venda * 1.05 FROM cd; Em alguns Sistemas Gerenciadores de Banco de Dados existem outras funções além das aritméticas, como a função POWER do Oracle e Sysbase que permite calcular exponenciação. 5.1 OPERADORES Estes são os operadores utilizados no SQL: OPERADOR SIGNIFICADO + Soma - Subtração * Multiplicação / Divisão A precedência é igual a da matemática, ou seja, a multiplicação e a divisão têm prioridade sobre a soma e subtração. Para alterar a prioridade deve-se utilizar parênteses (Operação). Exemplo: 15 / 5 * 3, Resultado = 9 15 / (5 * 3), Resultado = 1 SELECT cd_id, cd_nome, cd_preco - (cd_preco * 0.10) FROM cd 40 CÁLCULOS E FUNÇÕES USUAIS 5.1.1. CHARACTER_LENGTH / LENGTH Retorna o número de caracteres contidos em uma cadeia de caracteres. Esta função não existe em todos os SGBD, como acontece no Firebird. Exemplo: SELECT CHARACTER_LENGTH (‘Renato Russo’); SELECT CHARACTER_LENGTH (cd_nome) FROM cd WHERE cd_id = 10; 5.1.2. Alfanuméricos Em SQL é possível concatenar alfanuméricos e existem algumas funções para manipulá-los. Para concatenar caracteres, utiliza-se dois pipes ( || ). Exemplo: SELECT grav_id || ' - ' || grav_contato FROM gravadora; 5.1.3. UPPER e LOWER Ao realizar buscas alfanuméricas no banco de dados notamos que os parâmetros são case sensitive’s. Assim sendo, é possível usar os comandos UPPER E LOWER para transformar a cadeia de caracteres para maiúsculo e/ou minúsculo. Exemplo: SELECT * FROM autor WHERE UPPER(aut_nome) = ‘RENATO RUSSO’; SELECT UPPER(aut_nome) FROM autor; 5.1.4. SUBSTRING Essa função é requisito para SQL-92 ao nível intermediário. Retorna uma parte da cadeia de caracteres. Exemplo: SELECT SUBSTRING(aut_nome from 1 FOR 3), aut_nome FROM autor; 5.1.5. Manipulação de Datas Em colunas do tipo data podemos realizar uma série de cálculos e operações cronológicas, como calcular o número de dias entre duas datas, somar, subtrair dias, meses etc. O padrão especifica 04 (quatro) tipos de dados relacionados data e hora: 41 CÁLCULOS E FUNÇÕES USUAIS Tipo Date Time Timestamp Interval Descrição Apenas data Apenas Hora Data/Hora Intervalo entre dois tipos O padrão SQL definiu algumas funções acrescentando CURRENT_: - CURRENT_DATE - CURRENT_TIME - CURRENT_TIMESTAMP Exemplos: SELECT * FROM cd WHERE cd_dt_lancamento = CURRENT_DATE; Para saber a quantidade de dias que já se passaram depois do lançamento do CD usaríamos o comando: SELECT CURRENT_DATE, cd_dt_lancamento, (CURRENT_DATE – cd_dt_lancamento) FROM cd Adicionar dias em uma data: SELECT cd_dt_lancamento + 7 FROM cd; SELECT cd_dt_lancamento + INTERVAL ‘7 DAY’ FROM cd; 5.1.6. EXTRACT Essa função extrai e retorna um valor de um campo do tipo data. É possível extrair apenas o dia, o mês, o ano, a hora, etc. Exemplo: SELECT cd_dt_lancamento, EXTRACT(MONTH FROM cd_dt_lancamento) FROM cd; SELECT cd_dt_lancamento, EXTRACT(DAY FROM cd_dt_lancamento) FROM cd; Caso seja necessário realizar a concatenação de dados do tipo DATE é necessário utilizar o comando CAST para converter o tipo do dado para um tipo STRING. SELECT cd_dt_lancamento, CAST(EXTRACT(MONTH FROM cd_dt_lancamento) AS VARCHAR) ||'/'|| CAST(EXTRACT(DAY FROM cd_dt_lancamento) AS VARCHAR) FROM cd; 42 CÁLCULOS E FUNÇÕES USUAIS 5.2. EXERCÍCIOS a. Monte uma pesquisa que mostre a data atual. b. Escreva uma busca que mostre CD_NOME, CD_PRECO e CD_PRECO com 20% de aumento. c. Escreva uma busca igual à anterior, porém acrescente uma coluna mostrando a diferença entre o CD_PRECO e CD_PRECO com 20% de aumento. d. Escreva uma busca que mostre GRAV_NOME, GRAV_CONTATO em uma única coluna separados por um hífen. e. Escreva uma busca que mostre todos os autores que tenham a letra A no nome. f. Escreva uma busca que mostre a primeira letra da MUS_NOME e a MUS_DURACAO da tabela música. g. Escreva uma busca que mostre o CD_NOME e o número de dias entre a data atual e a data de lançamento. h. Escreva uma busca semelhante à anterior que mostre uma coluna com 15 dias após a CD_DT_LANCAMENTO. i. Escreva uma busca que retorne o ano de lançamento do CD_ID = 1, 2 e 3; j. Escreva uma consulta que retorne os 05 primeiro caracteres de MUS_NOME e outra coluna com MUS_NOME. 43 PESQUISA EM MÚLTIPLAS TABELAS 6 Parte PESQUISA EM MÚLTIPLAS TABELAS N os capítulos anteriores as pesquisas foram realizadas em apenas uma tabela. No entanto, observamos no modelo criado, assim como acontece sempre, que uma tabela está relacionada à outra, sendo necessário extrair informações de duas ou mais tabelas relacionadas ao mesmo tempo. Esta tarefa recebe o nome de união ou junção de tabelas. Isso se dá por meio das chaves primárias e chaves estrangeiras, que são no modelo físico as colunas que as tabelas têm em comum. Para os exemplos utilizados nesta aula é fundamental fazer uso do Modelo Lógico para melhor visualizar os relacionamentos. Figura 16 - Modelo Lógico de Dados 44 PESQUISA EM MÚLTIPLAS TABELAS 6.1. ALIAS Ao realizar pesquisa em diversas tabelas são referenciadas colunas pertencentes às tabelas da relação. Dessa forma, é necessário distinguir a qual tabela pertence à dada coluna. Podemos fazer isso de duas formas: a) Colocando o nome da tabela na frente do nome da coluna. Sintaxe: SELECT tabela.coluna FROM tabela; b) Utilizando um alias ou apelido para a tabela e colocando-o na frente da coluna. Sintaxe: SELECT t.coluna FROM tabela t; É ainda possível usar alias em nome de colunas, fazendo uso da cláusula AS; Sintaxe: SELECT coluna AS nome_desejado FROM tabela; 6.2. UNIÃO DE TABELAS Para realizar a união de tabelas basta acrescentar após a cláusula FROM do comando SELECT as tabelas que queremos unir utilizando após a cláusula WHERE a condição de união entre elas (Chave Primária e Chave Estrangeira). Sintaxe: SELECT [tabela1.]coluna [, [tabela2.]coluna, ...] FROM tabela1, tabela2 [, ...] WHERE tabela1.PK = tabela2.FK Uma prática recomendável é utilizar o mesmo nome nos campos de chave primária e chave estrangeira, pois dessa forma facilita a identificação dos campos ao realizar a união das tabelas. É possível colocar diversas tabelas na cláusula FROM. Não devemos esquecer, porém, que é necessário especificar as chaves primárias e chaves estrangeiras de cada relacionamento. Em caso de dúvidas, consulte o Modelo de Dados para estabelecer quais são as colunas comuns entre as tabelas. Daí a importância de se ter um bom projeto de Banco de Dados claro e preciso. A cláusula WHERE deve conter todo canal de relacionamento entre as tabelas listadas na cláusula FROM sob pena de ocorrer o Produto Cartesiano. 45 PESQUISA EM MÚLTIPLAS TABELAS 6.3. PRODUTO CARTESIANO Ocorrerá um produto cartesiano sempre que: - A condição de união entre as tabelas for omitida (não houver cláusula WHERE); - Condição de união entre as tabelas for inválida (cláusula WHERE incorreta); - Todas as linhas da primeira tabela estiverem unidas a todas as linhas da segunda tabela. - Nesta situação, as linhas da primeira tabela serão combinadas com as linhas da segunda, demonstrando um resultado na maior parte das vezes indesejado. - Esse procedimento é muito utilizado para efetuar testes de performance no banco devido a grande quantidade dedados. Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora, cd Ao invés de se mostrar todos os dados, deseja-se visualizar os CD’s e suas respectivas gravadoras. Emprega-se então, a União entre tabelas. Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora, cd WHERE cd.grav_id = gravadora.grav_id Usando alias: SELECT c.cd_id, c.cd_nome, g.grav_nome FROM gravadora g, cd c WHERE c.grav_id = g.grav_id 6.4. UNIÃO REGULAR (INNER JOIN OU EQUI-JOIN) Denomina-se união regular as uniões que têm a cláusula WHERE unindo chave primária e chave estrangeira como visto anteriormente. O padrão determina uma sintaxe alternativa para esse comando. Quando a chave primária e a chave estrangeira têm o mesmo nome em ambas as tabelas é possível simplificar o comando usando o comando NATURAL JOIN. Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM cd NATURAL JOIN gravadora; Outras duas maneiras definidas no padrão SQL é determinar qual(is) coluna(s) utilizar na união usando a cláusula USING e caso o nome das colunas não sejam iguais, determinar quais são as colunas com a cláusula ON. Ambas produzindo o mesmo efeito. 46 PESQUISA EM MÚLTIPLAS TABELAS Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM cd JOIN gravadora USING (grav_id); SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM cd JOIN gravadora ON gravadora.grav_id = cd.grav_id; 6.5. UNIÃO DE MAIS DE DUAS TABELAS Frequentemente é necessário unir mais de duas tabelas para obter uma informação consistente. Suponha que queira saber o nome da música, a faixa e o nome do CD em que está a música. No modelo de dados observamos a necessidade de se consultar três tabelas no Banco de Dados. Exemplo: SELECT c.cd_nome, f.fai_numero, m.mus_nome FROM cd c, faixa f, musica m WHERE c.cd_id = f.cd_id AND m.mus_id = f.mus_id O código abaixo produziria o mesmo efeito: SELECT c.cd_nome, f.fai_numero, m.mus_nome FROM faixa f INNER JOIN cd c ON c.cd_id = f.cd_id INNER JOIN musica m ON m.mus_id = f.mus_id Observe que é possível realizar a união de tantas tabelas quanto forem necessárias. SELECT a.aut_nome, g.grav_nome FROM autor a, cd c, gravadora g, faixa f, musica_autor ma, musica m WHERE a.aut_id = ma.aut_id AND m.mus_id = ma.mus_id AND m.mus_id = f.mus_id AND c.cd_id = f.mus_id AND g.grav_id = c.grav_id 6.6. UNIÕES EXTERNAS (OUTER-JOIN) Observa-se na união entre tabelas que quando uma linha não satisfaz a condição de união entre as tabelas, ela não será mostrada no resultado da busca. Isto acontece porque o banco de dados, não podendo estabelecer a relação entre as colunas que estão sendo unidas na busca, coloca NULL onde o dado não existe. 47 PESQUISA EM MÚLTIPLAS TABELAS Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora, cd WHERE cd.grav_id = gravadora.grav_id No resultado observamos que alguns dados de gravadora são omitidos. Define-se União Externa como aquela que inclui linhas no resultado da busca mesmo que não haja relação entre as duas tabelas. Para que os dados omitidos sejam mostrados utilizaremos o comando SELECT utilizando União Externa. 6.6.1. União Externa à Esquerda (Left Outer-Join) Como o nome diz, a união pela esquerda incluirá todas as linhas da primeira tabela na expressão, ou seja a tabela da esquerda será a tabela base para a união. Observe esse comando e depois inverta a posição das tabelas GRAVADORA e CD. Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora LEFT OUTER JOIN cd ON (gravadora.grav_id = cd.grav_id) Neste caso, todas as gravadoras são mostradas independente de haver ou não dados relacionados na tabela CD. 6.6.2. União Externa à Direita (Right Outer-join) Ao contrário da anterior, a união é realizada pela direita, pois incluirá todas as linhas da tabela da direita na expressão da união, ou seja a tabela da direita será a tabela base para a união. Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora RIGHT OUTER JOIN cd ON (gravadora.grav_id = cd.grav_id) NOTA: Se a tabela da referência (direita ou esquerda) for a que contém a chave estrangeira, ocorrerá uma união regular entre as tabelas. 6.6.3. União Externa Total (Full Outer-join) Realiza uma união independente da coluna opcional estar à direita ou à esquerda. Neste caso, são geradas linhas que não existem em uma ou em ambas as tabelas. 48 PESQUISA EM MÚLTIPLAS TABELAS Exemplo: SELECT cd.cd_id, cd.cd_nome, gravadora.grav_nome FROM gravadora FULL OUTER JOIN cd ON (gravadora.grav_id = cd.grav_id) Neste caso a consulta age como uma união externa a direita ou a esquerda por não haver campos nulos na coluna de chave estrangeira de CD. 6.7. EXERCÍCIOS a. Faça uma busca que mostre todos os CD’s e o nome de suas respectivas gravadoras. b. Faça uma pesquisa que mostre o produto cartesiano das tabelas MUSICA e CD. c. Realize uma consulta se utilizando da sintaxe JOIN que mostre todas MUSICAS e o nome de seus respectivos CD’s. d. Crie uma pesquisa que retorne como resultado todas as gravadoras e seus respectivos CD’s mesmo que estes não possuam CD’s associados. e. Crie uma pesquisa que mostre o AUTOR e suas MUSICAS ordenadas por AUTOR. f. Acrescente a pesquisa anterior o nome do CD que foi gravado a música. g. Crie uma pesquisa que mostre em quais gravadoras os autores tem realizado suas gravações. 49 FUNÇÕES DE GRUPO E AGRUPAMENTO 7 Parte FUNÇÕES DE GRUPO E AGRUPAMENTO A té agora trabalhamos com funções que tratavam apenas de uma linha. Contudo, em diversas situações é necessário trabalhar com várias linhas da consulta. Dessa forma, é necessário conhecer funções especiais denominadas funções de grupo e agrupamento. 7.1. FUNÇÕES DE GRUPO Funções de grupo operam conjuntos de linhas visando a fornecer um resultado para o grupo. Estes grupos podem ser constituídos desde toda a tabela até subgrupos da tabela. Existem diversas funções de grupo que são implementadas pelo padrão SQL. As principais funções são: FUNÇÃO COUNT SUM AVG MIN MAX STDDEV VARIANCE DESCRIÇÃO Retorna número de linhas afetadas pelo comando. Retorna o somatório do valor das colunas especificadas Retorna a média aritmética dos valores das colunas Retorna o menor valor da coluna de um grupo de linhas Retorna o maior valor da coluna de um grupo de linhas Retorna o desvio-padrão da coluna Retorna a variância da coluna 7.1.1. COUNT Diferente das outras funções de grupo, o COUNT retorna o número de linhas que atende a uma determinada condição. Podemos utilizá-lo com um asterisco entre parênteses, para indicar que queremos saber a quantidade total de linhas independente de haver linhas com colunas nulas ou não. Exemplos: SELECT COUNT(*) FROM gravadora; SELECT COUNT(grav_contato) FROM gravadora; Efeito semelhante pode ser conseguido com o seguinte comando: 50 FUNÇÕES DE GRUPO E AGRUPAMENTO Exemplos: SELECT COUNT(grav_contato) FROM gravadora WHERE grav_contato IS NOT NULL; Diferente de: SELECT COUNT(*) FROM gravadora WHERE grav_contato IS NULL; Outra maneira é utilizando o comando DISTINCT. Observe a diferença entre os dois comandos: Exemplos: SELECT COUNT(DISTINCT aut_id) FROM musica_autor; SELECT COUNT(aut_id) FROM musica_autor; 7.1.2. SUM Retorna o valor total de uma determinada coluna em um determinado grupo de linhas. Assim se quisermos saber o total do preço de venda dos CD’s, utilizamos o comando: Exemplo: SELECT SUM(cd_preco) FROM cd; É possível realizar cálculos baseados na somatória ou mesmo incluir outras colunas e operações no comando. Exemplo: SELECT SUM(cd_preco) * 1.2 FROM cd; 7.1.3. AVG Extrai a média aritmética de um determinado grupo de linhas. Para saber o preço médio dos CD’s da loja execute o seguinte comando: Exemplo: SELECT AVG(cd_preco) FROM cd; 51 FUNÇÕES DE GRUPO E AGRUPAMENTO 7.1.4. MIN Retorna o menor valor de uma coluna em um grupo de linhas. Podemos utilizá-la em colunas do tipo data ou alfanuméricas. Para saber o preço de venda do CD mais barato execute o seguinte comando: Exemplos: SELECT MIN(cd_preco) FROM cd; SELECT MIN(cd_dt_lancamento) FROM cd; SELECT MIN(cd_nome) FROM cd; 7.1.5. MAX Retorna o maior valor de uma coluna em um grupo de linhas. Podemos utilizá-la em colunas do tipo data ou alfanuméricas. Para saber o preço de venda do CD mais caro execute o seguinte comando: Exemplos: SELECT MAX(cd_preco) FROM cd; SELECT MAX(cd_dt_lancamento) FROM cd; SELECT MAX(cd_nome) FROM cd; 7.1.6. STDDEV (Não funciona em alguns bancos de dados.) Retorna o desvio padrão de uma determinada coluna. Para saber o desvio padrão dos preços de venda dos CD’s da loja execute o seguinte comando: Exemplo: SELECT STDDEV(cd_preco) FROM cd; 7.1.7. VARIANCE (Não funciona em alguns bancos de dados.) Retorna a variância de uma determinada coluna. 52 FUNÇÕES DE GRUPO E AGRUPAMENTO Para saber a variância do preço de venda dos CD’s da loja execute o seguinte comando: Exemplo: SELECT VARIANCE(cd_preco) FROM cd; 7.2. AGRUPANDO RESULTADOS É possível agrupar valores com base em determinadas colunas. Desta forma, estaremos trabalhando um pequeno grupo de dados. Para isso usamos a cláusula GROUP BY. 7.2.1. GROUP BY O comando GROUP BY deve vir antes da cláusula ORDER BY e depois do WHERE (se houver necessidade de utilizá-lo). É utilizado com uma função de grupo (COUNT, SUM, AVG, MIN ou MAX). Para saber quantas músicas há em cada CD: Exemplo: SELECT cd_id, COUNT(*) FROM faixa GROUP BY cd_id; Para o preço médio de venda de cada CD agrupado por gravadora: Exemplo: SELECT grav_id, AVG(cd_preco) FROM cd GROUP BY grav_id; É possível realizar mais de uma função de grupo dentro de um mesmo SELECT. Neste exemplo, além do preço de venda, temos a quantidade de CD’s de cada gravadora: Exemplo: SELECT grav_id, AVG(cd_preco), COUNT(*) FROM cd GROUP BY grav_id; 7.3. AGRUPAMENTOS COM MAIS DE UMA TABELA Podemos unir mais de uma tabela com as regras já citadas. 53 FUNÇÕES DE GRUPO E AGRUPAMENTO Exemplo: SELECT c.grav_id, g.grav_nome, AVG(c.cd_preco) FROM cd c, gravadora g WHERE c. grav_id = g.grav_id GROUP BY c.grav_id, g.grav_nome NOTA É necessário colocar todas as colunas que fazem parte do comando SELECT na cláusula GROUP BY. Caso se deseje ordenar o resultado, basta acrescentar o comando ORDER BY no final do comando após o comando GROUP BY: Exemplo: SELECT c.grav_id, g.grav_nome, AVG(c.cd_preco) FROM cd c, gravadora g WHERE c. grav_id = g.grav_id GROUP BY c.grav_id, g.grav_nome ORDER BY g.grav_nome desc 7.4. RESTRINGINDO O RESULTADO Em algumas situações os dados agrupados podem ser restringidos ou filtrados. Até agora vimos que toda tabela era afetada pelo comando GROUP BY. Contudo, nem sempre isso é desejável. Para isso podemos utilizar a cláusula WHERE em conjunto com GROUP BY ou utilizar a cláusula HAVING. Retornar o total de autores agrupados por MUS_ID sendo este menor que 3: Exemplo: SELECT mus_id, COUNT(*) FROM musica_autor WHERE mus_id < 3 GROUP BY mus_id; ou SELECT mus_id, COUNT(*) FROM musica_autor GROUP BY mus_id HAVING mus_id < 3; NOTE BEM Usando o filtro da cláusula WHERE as linhas são filtradas antes do agrupamento; Usando HAVING, as linhas são filtradas depois do agrupamento. A única restrição é que a cláusula HAVING só pode usar as colunas que fazem parte do GROUP BY. Para o WHERE isso não acontece. Nunca use na cláusula WHERE uma função de grupo para filtrar os grupos. Dessa forma, o comando abaixo é inválido: 54 FUNÇÕES DE GRUPO E AGRUPAMENTO Exemplos: SELECT GRAV_ID, AVG(cd_preco) FROM cd WHERE AVG(cd_preco) > 12 GROUP BY grav_id A forma correta seria: SELECT grav_id, AVG(cd_preco) FROM cd GROUP BY grav_id HAVING AVG(cd_preco) > 12 7.5. EXERCÍCIOS a. Verifique o maior e menor valor entre os preços de venda dos CD’s. b. Verifique a diferença entre o maior e menor valor dos preços de venda dos CD’s. c. Verifique a quantidade de dias compreendidos entre a data mais atual e a data mais antiga das datas de lançamento dos CD’s; d. Mostre através de uma consulta o tempo total do CD 1 (cd_id = 1); e. Mostre através de uma consulta a média de duração das músicas cujo nome comece com a letra A; f. Faça uma consulta que retorne a quantidade de CD’s cadastrados. g. Faça uma consulta que retorne a o nome do CD e a média de duração das músicas em cada CD, desde que esta média não seja superior a 4. h. Faça uma consulta que retorne os autores que são autores de mais de uma música. 55 SUBCONSULTAS 8 Parte SUBCONSULTAS – SUBQUERIES N as sessões anteriores exploramos largamente o comando SELECT utilizando-o em conjunto com uma gama de funções. Ainda neste contexto, é possível incluir subconsultas dentro das consultas principais. 8.1. SUBQUERY As subqueries fazem parte do padrão SQL-86 logo, todos os bancos de dados relacionais que utilizam SQL devem permitir essa utilização. Sintaxe: SELECT colunas FROM tabela WHERE expressão operador (SELECT colunas FROM tabela WHERE ...) Há três tipos de subquery: a) Subquery de uma linha: o retorno do SELECT interno será uma única linha. b) Subquery de múltiplas linhas: o retorno do SELECT interno será mais de uma linha. c) Subquery de múltiplas colunas: o retorno do SELECT interno será conterá mais de uma linha e coluna. 8.1.1. Subquery de uma linha Neste caso, primeiro é realizada a busca em uma tabela e, com base no resultado, pesquisada a outra tabela. Exemplo: SELECT cd_nome, cd_preco FROM cd WHERE cd_preco > (SELECT AVG(cd_preco) FROM cd) Acima é utilizada uma subquery para verificar a média de preço dos CD’s e, com base nesse resultado, é extraído o resultado da busca do primeiro SELECT. 56 SUBCONSULTAS Exemplo: SELECT grav_id, cd_nome, cd_preco FROM cd c WHERE cd_preco > (SELECT AVG(cd_preco) FROM cd WHERE grav_id = c.grav_id) Acima são buscados apenas os CD’s que tenham preço de venda superior a média da própria gravadora. Muito cuidado ao usar essa construção, porque ele tende a consumir muito recurso do banco de dados, pois a cada linha do primeiro SELECT será extraído a média no segundo SELECT. Assim é necessário saber qual a gravadora do primeiro SELECT, para poder calcular o segundo SELECT. NOTA - Coloque as subqueries entre parênteses. - Coloque a subquery à direita do operador. - Não use a cláusula ORDER BY em uma subquery. Se for necessário ordenar faça-o no SELECT principal. - Use operadores de grupo apenas em buscas que potencialmente retornem mais de uma linha. - Use operadores de linha apenas em buscas que retornem uma única linha. Exemplo: SELECT grav_id, cd_nome, cd_preco FROM cd WHERE grav_id = (SELECT grav_id FROM cd WHERE cd_id = 2) AND cd_preco > (SELECT cd_preco FROM cd WHERE cd_id = 5); 8.1.1.1. SUBQUERY EM CLÁUSULA HAVING A utilização é semelhante a cláusula WHERE. A subquery será executada primeiro e o resultado da busca servirá de base para filtrar as linhas do GROUP BY. Exemplos: SELECT grav_id, MIN(cd_preco) FROM cd GROUP BY grav_id HAVING MIN(cd_preco) > (SELECT cd_preco FROM cd WHERE cd_id = 2) SELECT grav_id, MAX(cd_preco) FROM cd c GROUP BY grav_id HAVING MAX(cd_preco) > (SELECT AVG(cd_preco) FROM cd WHERE grav_id = c.grav_id) 57 SUBCONSULTAS 8.1.1.2. COMANDO EXISTS Verifica o número de linhas retornadas pela subquery. Caso contenha uma ou mais linhas, então o resultado será mostrado. Exemplo: SELECT grav_id, grav_nome FROM gravadora WHERE EXISTS (SELECT * FROM cd WHERE cd.grav_id = gravadora.grav_id) Note que quando se está utilizando esse operador, não importa o que o comando SELECT interno irá buscar. Interessa apenas se ele retorna ou não linhas, Por esse motivo é usado o * em vez de uma coluna em especial. 8.1.2. Subquery de múltiplas linhas Neste caso o SELECT interno retorna mais de uma linha. Não é possível utilizar operadores simples como igualdade, diferença, maior ou menor. Utilizam-se operadores de grupo para realizar a comparação, como: - ANY - ALL - IN 8.1.2.1. IN Imagine que queremos saber quais CD’s têm o preço igual ao menor preço de cada gravadora. Inicialmente devemos saber qual o menor preço de cada gravadora. SELECT MIN(cd_preco) FROM cd GROUP BY grav_id Para saber quais são os CD’s escreveríamos o seguinte comando: SELECT cd_id, cd_nome, cd_preco FROM cd WHERE cd_preco IN (19, 23, 11) Para fazer isso em um único comando, escreveríamos o comando da seguinte forma: SELECT cd_id, cd_nome, cd_preco FROM cd WHERE cd_preco IN (SELECT MIN(cd_preco) FROM cd GROUP BY grav_id) 58 SUBCONSULTAS 8.1.2.2. ANY (Compara com qualquer valor) Esse operador permite comparar operadores simples (=,<,>,!=) com um grupo de linhas. Imagine que desejamos saber quais CD’s têm preço inferior a qualquer outro da gravadora com código 2, mas que não sejam da gravadora 2. Inicialmente é preciso saber qual é o preço de venda de cada CD que não seja da gravadora 2: SELECT cd_id, cd_nome, cd_preco FROM cd WHERE grav_id != 2 Depois precisamos saber quais os preços dos CD’s da gravadora 2 com o comando da seguinte forma: SELECT cd_preco FROM cd WHERE grav_id = 2 Agora realizamos a pesquisa na tabela de CD para comparar os preços inferiores a esses: SELECT cd_id, cd_nome, cd_preco FROM cd WHERE cd_preco < ANY (SELECT cd_preco FROM cd WHERE grav_id = 2) AND grav_id != 2 8.1.2.3. ALL Esse operador é usado em combinação com operadores simples (<,>) para que os valores retornados de todas as linhas do SELECT interno sejam comparados com o SELECT externo. Imagine que queiramos saber quais CD’s têm o preço de venda menor que a média de preço de venda de todas as gravadoras. Inicialmente é necessário saber a média de preços por gravadora: SELECT AVG(cd_preco) FROM cd GROUP BY grav_id; Depois verificamos quais são os CD’s com preço inferior a todos anteriores. SELECT cd_id, cd_nome, cd_preco FROM cd WHERE cd_preco < ALL (SELECT AVG(cd_preco) FROM cd GROUP BY grav_id) 59 SUBCONSULTAS 8.1.3. Subquery de múltiplas colunas A técnica consiste em colocar as colunas unidas na cláusula WHERE do SELECT externo e realizar a busca no SELECT interno dessas colunas unidas da mesma forma. Essa técnica tende a ser muito lenta, mas pode ser feita em qualquer banco de dados. Temos vários CD’s indicados no cadastro e queremos conhecer os dados do CD dentre os menores CD’s indicados de cada gravadora. Para isso temos que saber o menor CD indicado de cada gravadora. Utilizamos o || para unir as colunas. Exemplo: SELECT grav_id || cast(MIN(cd_indicado) as varchar) FROM cd GROUP BY grav_id Agora realizamos a busca dos dados que atendem essas características: SELECT cd_id, cd_nome, grav_id, cd_indicado FROM cd WHERE (grav_id || cast (cd_indicado as varchar)) IN (SELECT grav_id || cast(MIN(cd_indicado) as varchar) FROM cd GROUP BY grav_id) Outra técnica consiste em colocar as colunas de busca entre parênteses no SELECT externo, entretanto não está disponível em alguns bancos de dados. Exemplo: SELECT cd_id, cd_nome, grav_id, cd_indicado FROM cd WHERE (grav_id, cd_indicado) IN (SELECT grav_id, MIN(cd_indicado) FROM cd GROUP BY grav_id) NOTA: Quando um SELECT interno contiver valores nulos, não deve ser utilizado NOT IN como operador de comparação. Isso porque qualquer comparação com nulo retorna nulo. 8.1.4. Subquery na Cláusula FROM Podemos utilizar uma subquery na cláusula FROM de um comando SELECT. Essa estrutura não está disponível para todos os bancos de dados, mas faz parte do padrão SQL. Sua utilização é muito parecida com as visões (Views). Imagine que queremos extrair o nome do CD, seu preço e o preço médio da gravadora, e o preço médio da gravadora é extraído por uma subquery na própria cláusula FROM: 60 SUBCONSULTAS Exemplo: SELECT a.cd_nome, a.cd_preco, b.preco_medio FROM cd a, (SELECT grav_id, AVG(cd_preco) as preco_medio FROM cd GROUP BY grav_id) b WHERE a.grav_id = b.grav_id AND a.cd_preco > b.preco_medio Veja que foi substituída a segunda tabela por um comando SELECT e que a tabela recebeu o apelido de b. 8.2. EXERCÍCIOS a) Faça uma lista de CD’s usando subquery que foram gravados pela gravadora 2. b) Faça uma lista que mostre os CD’s que custam mais que a média de preço dos CD’s gravados pela gravadora 1. c) Faça uma consulta usando a cláusula HAVING que retorne o CD mais barato que a média de preço dos CD’s. d) Faça uma lista que mostre os CD’s cujas músicas tem duração maior que 00:02:00. e) Faça uma lista que mostre quais CD’s tem preços superiores a qualquer CD da gravadora 1 excluindo os da própria gravadora 1. 61 VISÕES/VIEW 9 Parte VISÕES/VIEW A nteriormente aprendemos a criar consultas em bancos de dados com a linguagem SQL usando uma série de funções que permitem a extração dos dados de diversas formas. Nesta sessão veremos a utilização de visões (Views), também chamadas de tabelas virtuais em sistemas de bancos de dados. As views são um modo especial de enxergar dados de uma ou várias tabelas. É um objeto de banco de dados criado a partir de um comando SELECT. É importante saber que ela é armazenada no dicionário de dados e possui a mesma estrutura de uma tabela. As views permitem restringir o acesso aos dados, por exemplo, utilizando o comando SELECT podemos filtrar linhas e coluna que não devam ser mostradas a todos os usuários. As buscas complexas tornam-se simples, pois ao DBA é possível criar consultas complexas em forma de visões e ao usuário realizar as buscas necessárias nessa visão. Outro aspecto é a independência de dados: as visões podem ser criadas visando atender necessidades genéricas e não apenas às relacionadas a pessoa ou programas. Podemos buscar dados em diversas tabelas. A eliminação de códigos é outra razão: normalmente os códigos não devem ser mostrados aos usuários, pois são utilizados apenas para estabelecer relacionamento entre as tabelas. Dessa forma facilita a compreensão dos dados pelo usuário. 9.1. CLASSIFICAÇÃO As visões podem ser classificadas de duas formas: a) Simples: - Os dados são extraídos de uma única tabela; - Não contém funções. - Não possuem dados agrupados. - Podem utilizar comandos DML para manipular os dados. b) Complexas: - Os dados são extraídos de várias tabelas. - Podem conter funções. - Podem conter dados agrupados. - Não podem utilizar comandos DML para manipulação de dados, exceto no Oracle, pois possui recursos específicos para isso. 62 VISÕES/VIEW 9.2. CRIAÇÃO DA VISÃO Para criar uma visão, utilizamos o comando CREATE VIEW: Sintaxe: CREATE VIEW nome AS query/subquery Exemplo: CREATE VIEW vCD AS SELECT cd_id, cd_nome, cd_preco FROM cd; Como visto anteriormente esta é uma visão simples. Como uma visão é sempre baseada em um comando SELECT, podemos criar visões extremamente complexas. Exemplos: CREATE VIEW vPRECO_CD AS SELECT grav_id, cd_nome, cd_preco FROM cd WHERE grav_id = (SELECT grav_id FROM cd WHERE cd_preco > 10.00) CREATE VIEW vCD_FAIXA AS SELECT cd_id, item_faixa FROM item_cd WHERE mus_id IN (SELECT mus_id FROM musica WHERE UPPER(mus_nome) LIKE ‘%A’) CREATE VIEW VGRAV_MAX AS SELECT grav_id, cd_preco FROM cd a WHERE cd_preco > (SELECT AVG(cd_preco) FROM cd WHERE grav_id = a.grav_id) 9.3. BUSCANDO CONTEÚDO DE VISÕES Utilizamos o comando SELECT para buscar o conteúdo de visões exatamente como fazemos com qualquer tabela. Exemplo: SELECT * FROM vCD; SELECT * FROM vCD_FAIXA; Podemos, ainda, incluir qualquer outra cláusula do comando SELECT, mesmo em pesquisa realizadas em visões. 63 VISÕES/VIEW Exemplo: SELECT * FROM VPRECO_CD WHERE cd_preco > 8; 9.4. UTILIZAÇÃO DE COMANDOS DML EM VISÕES Somente em visões simples é possível utilizar comandos DDL. O padrão SQL determina as condições em que uma visão pode ser atualizada: - Deve ser criada em uma única tabela; - Deve conter apenas um SELECT; - Se foi criada com base em outra visão, a primeira visão dever ser passível de atualização; - O comando SELECT não pode conter colunas calculadas; - Não deve utilizar GROUP BY; - Não deve conter a cláusula DISTINCT; - Pode conter uma subquery, desde que o SELECT interno tenha como base a mesma tabela do SELECT externo; - Inclusões só podem ser feitas caso a visão contenha a chave primária da tabela base. - Exclusões e alterações em visões sem a chave primária da tabela base são permitidas, porém desaconselháveis, pois não se sabe ao certo o resultado que pode aparecer. Isso é chamado de Materialização de Views. 9.5. EXCLUINDO UMA VISÃO Para excluir uma visão, utilizamos o comando DROP VIEW; Ao excluir uma visão não estamos excluindo os dados (linhas e colunas), visto que é apenas uma tabela virtual. Sintaxe: DROP VIEW nome; Exemplo: DROP VIEW vCD; 9.6. EXERCÍCIOS a) Crie uma visão que mostre uma lista de CD’s que foram gravados pela gravadora 2. b) Crie uma visão que mostre os CD’s e suas respectivas gravadoras. c) Crie uma visão que mostre as músicas e seus respectivos autores. d) Construa uma visão que mostre as músicas sua duração e a que CD pertence. e) Construa uma visão que mostre os autores e em quais gravadoras possuem CD gravado. 64 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Parte PROCEDIMENTOS ARMAZENADOS 10 m muitas situações será necessário armazenar procedimentos escritos com a finalidade de se utilizar recursos como loop, estruturas de decisão, etc. Para isso, utiliza-se os procedimentos armazenados o qual definimos como um programa escrito em uma linguagem própria que é armazenado como parte do banco de dados. Em outras palavras Procedimento Armazenado ou Stored Procedure é uma coleção de comandos em SQL disponíveis em Banco de dados. Encapsula tarefas repetitivas, aceita parâmetros de entrada e retorna um valor de status (para indicar aceitação ou falha na execução). O procedimento armazenado pode reduzir o tráfego na rede, melhorar a performance, criar mecanismos de segurança, etc. (Wikipédia, a enciclopédia livre, 2009). E 10.1. VANTAGENS DO USO DE STORED PROCEDURES - Redução de tráfico de rede. - Aumenta a performance da aplicação, particularmente em uma WAN ou em uma conexão de baixa velocidade. - Utilizando stored procedure, elimina-se o processo de “parsed”, ou seja, da query ter que ser analisada gramaticalmente e submetido ao otimizador para formulação de um plano de execução. - São analisadas, otimizadas e armazenadas em uma forma executável no momento em que são adicionadas ao banco de dados. - É possível executar operações muito mais complexas que uma simples query. - Pode ser usada por aplicações distintas. - Facilita a manutenção, pois é possível alterá-la sem ter que recompilar a aplicação. - Proporciona mais segurança ao banco de dados, acessando tabelas que o usuário não tem acesso. 10.2. QUANDO USAR STORED PROCEDURES - Sempre que a metodologia de desenvolvimento do software permitir. - Não existem desvantagens em usar procedimentos armazenados, entretanto existem duas limitações: - Deve ser possível passar qualquer informação variável para a stored procedure (parâmetros). - Operações complexas podem ser limitadas. 65 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES 10.3. STORED PROCEDURES NO POSTGRESQL Como visto, Stored Procedures são programas desenvolvidos em determinada linguagem de script e armazenados no servidor, local onde serão processados. Também são conhecidos como funções, este o motivo pelo qual as Stored Procedures no PostgreSQL são referenciadas como Function. O PostgreSQL conta com três formas diferentes de criar funções: a) Funções em Linguagem SQL: São funções que utilizam a sintaxe SQL e se caracterizam por não possuírem estruturas de condição (if, else, case), estruturas de repetição (while, do while, for), não permitirem a criação de variáveis e utilizam sempre algum dos seguintes comandos SQL: SELECT, INSERT, DELETE ou UPDATE. b) Funções de Linguagens Procedurais: Ao contrário das funções SQL, aqui é permitido o uso de estruturas de condição e repetição e o uso de variáveis. As funções em linguagens procedurais caracterizam-se também por não possuírem apenas uma possibilidade de linguagem, mas várias. Normalmente a mais utilizada é conhecida como PL/PgSQL, linguagem semelhante ao conhecido PL/SQL da Oracle. Existem outras linguagens como, por exemplo, o PL/Perl, PL/Python e PL/Tcl, que possuem sintaxe igual ou semelhante às linguagens que lhes deram origem. c) Funções em Linguagens Externas ou de Rotinas Complexas: São funções normalmente escritas em C++ que trazem consigo a vantagem de utilizarem uma linguagem com diversos recursos, na qual pode-se implementar algoritmos com grande complexidade. Tais funções são empacotadas e registradas no Sistema Gerenciador de Banco de Dados para seu uso futuro. Existem ainda outras linguagens como PL/Ruby, PL/sh e PL/Java, no entanto, estas são definidas por projetos independentes. Dentre elas destacam-se as funções criadas com PL/Java devido a sua crescente demanda entre usuários do PostgreSQL. Para criar uma função utilizando SQL no PostgreSQL utiliza-se o comando CREATE FUNCTION: Sintaxe: CREATE [OR REPLACE] FUNCTION nome ([tipo_do_parametro1],[...]) RETURNS tipo_retornado AS ' Implementação_da_função; ' LANGUAGE 'SQL'; - CREATE FUNCTION é o comando que define a criação de uma função, [OR REPLACE] informa que se acaso existir uma função com este nome, a atual função deverá sobrescrever a antiga. - RETURNS tipo_retornado informa o tipo de dado que será retornado ao término da função. Tais tipos de retornos são os convencionais como o INTEGER, FLOAT, VARCHAR, etc. As funções em SQL também permitem o retorno de múltiplos valores e para isso informase como retorno SETOF. Na implementação haverá as linhas de programação para a implementação da stored procedure. - LANGUAGE indica para a função em qual linguagem ela está sendo implementada. 66 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Na passagem de parâmetros à função não se utiliza o nome nas variáveis declaradas nos parênteses da assinatura da função. Utiliza-se o tipo da variável de parâmetro separado por vírgula. Para acessar o valor dos parâmetros, utiliza-se o $ mais o número da posição que ocupa nos parâmetros, seguindo a ordem da esquerda para a direita. Exemplo: CREATE FUNCTION soma(INTEGER, INTEGER) RETURNS INTEGER AS ' SELECT $1 + $2; ' LANGUAGE 'SQL'; O fato de que as funções utilizando SQL sempre retornam valor faz com que seja sempre necessário que a última linha de comando da função utilize o comando SELECT. Exemplo: CREATE FUNCTION cubo(INTEGER) RETURNS FLOAT AS ' SELECT $1 ^ 3; ' LANGUAGE 'SQL'; Também é possível criar funções que fazem interação entre uma determinada consulta e parâmetros utilizados na função. Na função abaixo se obtém o total de CD’s gravados por determinada gravadora, passando como parâmetro o id da gravadora (grav_id): Exemplos: CREATE FUNCTION cdGravados(INTEGER) RETURNS INTEGER AS ' SELECT COUNT(cd_id) FROM cd WHERE grav_id = $1; ' LANGUAGE 'SQL'; CREATE OR REPLACE FUNCTION cdGravados2(in INT, out f1 INT, out f2 TEXT, out f3 NUMERIC) AS $$ SELECT CAST(cd_id as integer), CAST(cd_nome AS text), CAST(cd_preco AS numeric) FROM cd WHERE GRAV_id = $1 $$ LANGUAGE ‘SQL’; NOTA: Dependendo da configuração escolhida na instalação do PostgreSQL será necessário ajustar o tipo do retorno fazendo uso da função CAST. Exemplo: CAST(COUNT(cd_id) as INTEGER) 67 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Para usar a função utiliza-se a cláusula SELECT. Exemplo: SELECT cdGravados(4); Como mencionado, também é possível retornar várias linhas de uma consulta em uma função. Para isso, utilizamos o tipo de retorno SETOF. No próximo exemplo é criada uma função em que retorna todos os CD’s mais caros que determinado valor passado por parâmetro. Exemplo: CREATE OR REPLACE FUNCTION precoMaior(NUMERIC(10,2)) RETURNS SETOF cd AS' SELECT * FROM cd WHERE cd_preco > $1; ' LANGUAGE 'SQL'; Quando as funções possuem o seu retorno referenciado em uma tabela ou uma View, ou seja, quando a função retorna um resultset, devemos utilizar a função da seguinte maneira: Exemplo: SELECT * FROM precoMaior(5.2); A exclusão de uma função se faz através do comando DROP, como visto na DDL. Sintaxe: DROP FUNCTION nome_da_funcao(); Quando a função, na sua assinatura contiver parâmetros não será possível sua exclusão através do comando DROP FUNCTION nome_da_funcao(), ou seja, para excluir uma função é necessário passar toda a sua assinatura. Exemplo: DROP FUNCTION precoMaior(NUMERIC(10,2)); Ainda existe o fato que no momento da exclusão poder excluir a função passando mais um parâmetro, como no exemplo a seguir: Exemplo: DROP FUNCTION cd_grav (psql INTEGER) RESTRICT; DROP FUNCTION cd_grav (psql INTEGER) CASCADE; Passando o RESTRICT como parâmetro, a exclusão da função será recusada caso existam dependências de objetos em torno da função (como por exemplo, Triggers e operadores). Com o CASCADE esses objetos serão excluídos juntamente com a função. 68 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Nota: Se a função for removida e recriada, a nova função não é mais a mesma entidade que era antes. Ficarão inválidas as regras, visões, gatilhos, etc. existentes que fazem referência à antiga função. Use o comando CREATE OR REPLACE FUNCTION para mudar a definição de uma função, sem invalidar os objetos que fazem referência à função. 10.4.1. PL/pgSQL A PL/pgSQL é uma linguagem estrutural estendida da SQL que tem por objetivo auxiliar as tarefas de programação no PostgreSQL. Ela incorpora à SQL características procedurais, como os benefícios e facilidades de controle de fluxo de programas que as melhores linguagens possuem como loops estruturados (for, while) e controle de decisão (if, then, else). Dessa forma, programar em PL/pgSQL significa ter a disposição um ambiente procedural totalmente desenvolvido para aplicações de bancos de dados, beneficiando-se do controle transacional inerente das aplicações deste tipo. 10.4.2. Elementos da Linguagem A criação de Stored Procedures inclui todas as construções de uma linguagem de programação estruturada, como: - Comentários: -- Este é um comentário - Bloco de comandos (BEGIN/END): É possível usar construções como IF-THEN-ELSE e loops WHILE que somente podem conter um comando. - Comandos de atribuição: var1 := var2 * var3 - Operador de concatenação: || (duas barras verticais) Além desses elementos é possível usar as expressões condicionais: - BETWEEN - LIKE IN - EXISTS - ANY - ALL - IS NULL - IS NOT NULL 10.4.3. Estrutura da Linguagem A PL/pgSQL é estruturada em blocos, assim, todo o texto de definição de uma função precisa estar em um bloco. Sintaxe: [<<label>>] [DECLARE declarações] BEGIN conteúdo 1; conteúdo 2; conteúdo n; 69 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES END; - DECLARE é a área para declaração de variáveis. As variáveis declaradas nessa sessão são inicializadas recebendo seus valores default sempre que existe uma entrada nesse bloco. Como na maior parte das linguagens existentes, na PL/pgSQL, variáveis com o mesmo nome possuem o seu escopo definido para o bloco imediatamente posterior à sua declaração. Exemplo: CREATE OR REPLACE FUNCTION mostra_valor(valor INT) RETURNS INTEGER AS $$ DECLARE valor INTEGER := $1; BEGIN RAISE NOTICE 'O valor da variável aqui é %', Valor; RETURN valor; END; $$ LANGUAGE 'PLPGSQL'; Executando a função: SELECT mostra_valor(12); Como visto é possível usar as mesmas estruturas de controle disponíveis nas linguagens de programação: Exemplo: CREATE OR REPLACE FUNCTION formatarCPF(cpf VARCHAR(11)) RETURNS VARCHAR(14) AS $$ BEGIN IF char_length(cpf) != 11 THEN RAISE NOTICE 'Formato inválido: %', $1; RETURN 'ERRO'; END IF; RETURN SUBSTRING(cpf FROM 1 FOR 3) || '.' || SUBSTRING(cpf FROM 4 FOR 3) || '.' || SUBSTRING(cpf FROM 7 FOR 3) || '-' || SUBSTRING(cpf FROM 10 FOR 2); END; $$ LANGUAGE PLPGSQL; SELECT formatarCPF('11111111111'); Além de todos os recursos advindos da estrutura de programação é permitido fazer interação com a estrutura do banco de dados nas operações da DML como no exemplo abaixo: 70 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Exemplo: CREATE OR REPLACE FUNCTION alteraPreco(id INTEGER, preco NUMERIC(10,4)) RETURNS VARCHAR(50) AS $$ DECLARE valorMax NUMERIC(10,4) := 100; valorMin NUMERIC(10,4) := 1; BEGIN IF $2 >= valorMin and $2 <= valorMax THEN UPDATE CD SET cd_preco = $2 WHERE cd_id = $1; RETURN 'Preço alterado com sucesso'; ELSE RETURN 'O valor inserido está fora da faixa delimitada para preços de CDs'; END IF; END; $$ LANGUAGE PLPGSQL; SELECT alteraPreco(1, 16.5); SELECT alteraPreco(2, 120); As versões 8.3 e posteriores do PostgreSQL permitem o retorno de conjuntos de linhas e colunas de uma função através da funcionalidade RETURN QUERY. O RETURN QUERY aceita o tipo RECORD como valor de retorno tornando fácil retornar resultados de consultas genéricas. Exemplo: CREATE OR REPLACE FUNCTION cd_grav (psql integer) RETURNS SETOF RECORD AS $$ BEGIN RETURN QUERY SELECT cd_nome, grav_nome FROM cd, gravadora WHERE cd.grav_id = gravadora.grav_id AND gravadora.grav_id = $1; -- Consulta RETURN; -- Retorna as linhas END; $$ LANGUAGE plpgsql; A diferença desta forma de programar está na chamada da função. Ao utilizar valor de retorno do tipo RECORD, deve ser indicada a lista de campos a serem retornados e seus tipos. Esta pode ser vista como uma limitação, embora na prática também seja uma forma de se garantir a confiabilidade dos resultados retornados na execução de comandos SQL dentro de funções pela validação dos valores retornados. Exemplo: SELECT * FROM cd_grav(2) AS ( c1 VARCHAR(50), c2 VARCHAR(50) ); 71 PROCEDIMENTOS ARMAZENADOS/STORED PROCEDURES Esses são alguns recursos possíveis com Stored Procedures/Functions no PostgreSQL. Contudo, é importante saber que existe um gama de outros recursos possíveis fazendo uso do PgSQL, assim como acontece com outros bancos de dados, como o Oracle. 10.4. EXERCÍCIOS a) Crie uma Stored Procedure no PostgreSQL que faça inserções na tabela de música. b) Crie uma Stored Procedure no PostgreSQL que retorne quais música foram gravadas em determinado CD passando como parâmetro o id do CD (cd_id). 72 TRIGGERS (GATILHOS OU AUTOMATISMOS) Parte 11 TRIGGERS (GATILHOS OU AUTOMATISMOS) D esenvolver uma aplicação para gerenciar os dados significa criar uma aplicação que faça o controle sobre todo ambiente desde a interface, passando pela manutenção dos dados e as regras de negócios do sistema. Assim, é o sistema que deve controlar e tomar as decisões sobre o que fazer em determinadas situações. Em um programa para controlar os produtos de uma empresa, quando a quantidade de um produto atingir certa quantidade o sistema deverá avisar o operador/usuário para providenciar a reposição do mesmo. Ao trabalhar com base de dados Cliente/Servidor como SQL Server, Oracle, Informix, PostgreSQL dentre outras, é possível usar um recurso muito poderoso chamado Trigger. Triggers são rotinas armazenadas no banco de dados e utilizadas quando um comando Insert, Update ou Delete é executado em uma tabela ou até mesmo uma visão (view). Trigger significa gatilho e é disparada quando os comandos da DML são disparados. São executadas automaticamente sem a interferência do usuário, ou seja, são procedimentos armazenados que são acionados por algum evento e em determinado momento. As principais aplicações das triggers estão relacionadas às validações, restrições de acesso, rotinas de segurança e consistência de dados. Triggers são iguais a stored procedures com as seguintes exceções: - São chamadas automaticamente; - Não tem parâmetros; - Não retornam valores. A criação de uma trigger envolve duas etapas: - Um comando SQL que vai disparar o Trigger (INSERT, DELETE, UPDATE) - A ação que a trigger vai executar (Geralmente um bloco de códigos SQL) O gatilho fica associado com uma tabela e executa uma função especifica. Na maioria dos bancos de dados estes eventos podem ser inserções (INSERT), atualizações (UPDATE) e exclusões (DELETE), e podem ser executadas em dois momentos: - Antes da execução do evento (BEFORE); - Depois da execução do evento (AFTER). Uma trigger é executada automaticamente mediante seis eventos da DML: - BEFORE UPDATE (Antes da atualização); - AFTER UPDATE (Após a atualização); - BEFORE INSERT (Antes da inserção); - AFTER INSERT (Após a inserção); 73 TRIGGERS (GATILHOS OU AUTOMATISMOS) - BEFORE DELETE (Antes da exclusão); - AFTER DELETE (Após a exclusão); 11.1. BEFORE OU UPDATE Uma trigger deve ser disparada antes de o registro ser atualizado caso queira alterar o valor de uma ou mais colunas antes que a linha seja atualizada, ou queira bloquear a alteração da linha gerando uma exceção, como por exemplo: usar uma trigger BEFORE UPDATE para evitar que o usuário apague o registro de um cliente que tenha comprado nos últimos dois anos. Triggers do tipo AFTER quando se deseja garantir que a atualização que disparou a trigger seja completada com sucesso antes de executar outras ações, como por exemplo: inserir uma linha em uma tabela de salario_historico sempre que o salário de um funcionário for alterado. 11.2. USANDO OLD E NEW No corpo de uma trigger é possível usar tanto os valores antigos ou novos de qualquer registro. Para isso basta utilizar os comandos OLD e NEW. Exemplo: old.aut_nome new.aut_nome Com esses comandos é possível criar registros de histórico, calcular percentual de alteração de valores, etc. 11.3. TRIGGERS NO POSTGRESQL Um diferencial das triggers no PostgreSQL é que elas são sempre associadas a funções de triggers e em outros Bancos de Dados elas são criadas no corpo da trigger. 11.3.1. Tipos de Triggers O PostgreSQL possui dois tipos de triggers: - Triggers-por-linha - Triggers-por-instrução A trigger-por-linha é disparada uma vez para cada registro afetado pela instrução que disparou a trigger. A trigger-por-instrução é disparada somente uma vez quando a instrução é executada. 11.3.2. Funções de Trigger e Linguagens Procedurais 74 TRIGGERS (GATILHOS OU AUTOMATISMOS) Funções de triggers são funções que não recebem nenhum parâmetro e retornam o tipo trigger. Essas funções recebem uma estrutura chamada TriggerData, e esta é passada internamente para a função pelo PostgreSQL. O PostgreSQL disponibiliza duas variáveis importantes para serem usadas em conjunto com as triggers-por-linha: NEW e OLD. A variável NEW, no caso do INSERT, armazena o registro que está sendo inserido. No caso do UPDATE, armazena a nova versão do registro depois da atualização. A variável OLD, no caso do DELETE, armazena o registro que está sendo excluído. No caso do UPDATE, armazena a antiga versão do registro depois da atualização. As funções de triggers devem ser escritas em alguma linguagem procedural disponível no banco de dados1. Cada linguagem, que suporta triggers, possui o seu próprio método para tornar os dados de entrada da trigger disponíveis para a função. Estes dados de entrada incluem o tipo de evento da trigger, assim como as opções informadas na criação da trigger. Para uma trigger no nível de linha, os dados de entrada também incluem as linhas NEW para as triggers de INSERT e UPDATE, e a linha OLD para os triggers de UPDATE e DELETE. Sintaxe: CREATE TRIGGER nome { BEFORE | AFTER } { evento [OR ...] } ON tabela FOR EACH { ROW | STATEMENT } EXECUTE PROCEDURE função ( argumentos ) Onde: Nome: O nome da Trigger. Tabela: O nome da tabela na qual a trigger estará vinculada. Evento: INSERT, DELETE ou UPDATE. Função: Pode-se usar, neste caso, uma stored procedure para execução de determinada tarefa. row|statement: especifica se a trigger deve ser disparada uma vez para cada linha afetada pelo evento ou apenas uma vez por comando SQL. Se não for especificado nenhum dos dois, o padrão é FOR EACH STATEMENT. Exemplo: Para este primeiro exemplo é necessário alterar nosso banco de dados: ALTER TABLE autor ADD aut_cpf CHAR(14); Próximo passo é criar a função que faz a inserção de determinado valor nesta tabela: 1 Essas linguagens podem ser várias, como Ruby, Perl, Python, entre outras. Atualmente existem quatro linguagens procedurais disponíveis na distribuição padrão do PostgreSQL: PL/pgSQL, PL/Tcl, PL/Perl e PL/Python. Mas é possível que o usuário defina outras linguagens. Para instalar novas linguagens no PostgreSQL, consulte a documentação oficial do PostgreSQL, que pode ser obtida no seguinte endereço: http://pgdocptbr.sourceforge.net/. 75 TRIGGERS (GATILHOS OU AUTOMATISMOS) Exemplo: CREATE OR REPLACE FUNCTION ftFormataCPF() RETURNS trigger AS $$ BEGIN IF char_length(new.aut_cpf) = 11 THEN new.aut_cpf := SUBSTRING(new.aut_cpf FROM 1 FOR 3) || '.' || SUBSTRING(new.aut_cpf FROM 4 FOR 3) || '.' || SUBSTRING(new.aut_cpf FROM 7 FOR 3) || '-' || SUBSTRING(new.aut_cpf FROM 10 FOR 2); RETURN new; END IF; END; $$ LANGUAGE plpgsql; Por fim, criando a trigger: Exemplo: CREATE TRIGGER tFormataCpf BEFORE INSERT ON autor FOR EACH ROW EXECUTE PROCEDURE ftFormataCPF (); Verificando seu funcionamento: INSERT INTO autor (aut_id, aut_nome, aut_cpf) VALUES (102, 'teste function2', '00000000002'); 11.3.3. Parâmetros Especiais Dentre as funções que foram apresentadas para triggers é possível utilizar uma grande quantidade de argumentos com a finalidade de criar controles e métodos de auditoria nos bancos de dados PostgreSQL. Todas elas são facilmente encontradas no manual do PostgreSQL. Desses, um dos argumentos mais importantes é o TG_OP que permite identificar qual operação está sendo realizada: INSERT, UPDATE e DELETE. Muito útil para criar mecanismos de auditoria nos dados contidos em determinada tabela. Acompanhe atentamente o exemplo abaixo: Exemplo: Primeiramente vamos criar uma tabela de histórico: CREATE TABLE historico_cd ( hcd_id INTEGER, hcd_preco_novo NUMERIC(14,2), hcd_preco_antigo NUMERIC(14,2), 76 TRIGGERS (GATILHOS OU AUTOMATISMOS) hcd_data_hora TIMESTAMP, hcd_operacao VARCHAR(50) ); Depois criamos a função: CREATE OR REPLACE FUNCTION auditaCD() RETURNS trigger AS $$ BEGIN IF (TG_OP = 'INSERT') THEN INSERT INTO historico_cd (hcd_id, hcd_preco_novo, hcd_preco_antigo, hcd_data_hora, hcd_operacao) VALUES (NEW.cd_id, NEW.cd_preco, NULL, CURRENT_TIMESTAMP, ‘INSERT’); RETURN NEW; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO historico_cd (hcd_id, hcd_preco_novo, hcd_preco_antigo, hcd_data_hora, hcd_operacao) VALUES (OLD.cd_id, NEW.cd_preco, OLD.cd_preco, CURRENT_TIMESTAMP, ‘UPDATE’); RETURN NEW; ELSIF (TG_OP = 'DELETE') THEN INSERT INTO historico_cd (hcd_id, hcd_preco_novo, hcd_preco_antigo, hcd_data_hora, hcd_operacao) VALUES (OLD.cd_id, NULL, OLD.cd_preco, CURRENT_TIMESTAMP, ‘DELETE’); RETURN NEW; END IF; END; $$ LANGUAGE plpgsql; Por fim, criamos a Trigger: CREATE TRIGGER tg_auditaCD AFTER INSERT OR UPDATE OR DELETE ON cd FOR EACH ROW EXECUTE PROCEDURE auditaCD(); Verificando seu funcionamento: INSERT INTO cd (cd_id, grav_id, cd_nome, cd_preco, cd_dt_lancamento) VALUES (100, 1, 'teste trigger1', 10.00, '2009-1115'); UPDATE cd SET cd_preco = 20.00 WHERE cd_id = 100; DELETE FROM cd WHERE cd_id = 100; 11.3.4. Triggers Recursivas 77 TRIGGERS (GATILHOS OU AUTOMATISMOS) Se uma função de trigger executar comandos SQL, estes comandos podem disparar triggers novamente. Isto é conhecido como cascatear triggers. Não existe limitação direta do número de níveis de cascateamento. É possível que o cascateamento cause chamadas recursivas da mesma trigger; por exemplo, um trigger para INSERT pode executar um comando que insere uma linha adicional na mesma tabela, fazendo com que a trigger para INSERT seja disparada novamente. É responsabilidade de o programador evitar recursões infinitas nestes casos. 11.3.5. Alterando uma Trigger É possível alterar o nome de trigger usando a sintaxe abaixo: Sintaxe: ALTER TRIGGER nome ON tabela RENAME TO novo_nome; 11.3.6. Excluindo uma Trigger Para excluir uma trigiger basta executar o comando abaixo: Sintaxe: DROP TRIGGER nome ON tabela [ CASCADE | RESTRICT ] Onde: Nome: é o nome da trigger a ser removida. Tabela é o nome da tabela para a qual a trigger está definida. [ CASCADE | RESTRICT ]: indica que ao remover a trigger vamos remover também todos os objetos que dependem dela (CASCADE) ou recusaremos sua exclusão (RESTRICT). 11.3.7. Desabilitando/Habilitando uma Trigger Para desabilitar/habilitar uma trigger execute o comando abaixo: Sintaxe: ALTER TABLE nome_tabela [DISABLE|ENABLE] TRIGGER nome_trigger; Para desabilitar todas as triggers da tabela, execute o seguinte comando: Sintaxe: ALTER TABLE nome_tabela [DISABLE|ENABLE] TRIGGER ALL; 11.4. EXERCÍCIO a) Crie uma trigger responsável por verificar, no momento da inserção na tabela de CD, se o preço do CD é maior de 1,00 e inferior a 100,00. 78 SEGURANÇA DE BANCO DE DADOS Parte 12 SEGURANÇA DE BANCO DE DADOS P odemos dizer que segurança em banco de dados garante que os usuários tenham permissão para fazer o que realmente precisam fazer. Para tanto, os Sistemas Gerenciadores de Banco de Dados precisam de certas limitações, na qual os usuários não poderão violar. Estas especificações são criadas pelo DBA (Administrador de Banco de Dados). Todas as decisões acerca dos direitos que devem ser concedidos a determinado usuário são decisões políticas e não técnicas. Assim sendo, tais competências fogem a alçada dos Sistemas Gerenciadores de Banco de Dados. 12.1. VISÃO DE SEGURANÇA O Sistema Gerenciador de Banco de Dados deve fornecer ao usuário uma representação conceitual dos dados (visão), sem fornecer muitos detalhes de como as informações estão armazenadas. Muitas verificações de autorização podem aplicar-se ao tempo de execução, entretanto a abordagem de segurança de que se baseia uma visão é um tanto inábil, pois um usuário pode precisar de direitos diferentes sobre subconjuntos diferentes da mesma tabela. Existem muitos aspectos relativos aos problemas de segurança, dentre eles os aspectos legais, sociais e éticos. O que deve ser analisado é se quem faz a solicitação da informação tem direito legal sobre ela. Outro aspecto a se analisar são os controles físicos. Considera-se a sala a sala do servidor um lugar inviolável, pois lá estão todas as informações. Para que o sistema seja capaz de decidir que limitações se aplicam a determinada solicitação é necessário reconhecer a fonte daquela solicitação, isto é, deve ser capaz de reconhecer de que usuário específico partiu aquela demanda. Neste sentido é necessário atribuir mecanismos de autenticação para os responsáveis pela solicitação sejam identificados, como um meio de garantir a auditoria sobre os feitos daquele usuário nos dados armazenados. 12.2. INSTRUÇÕES GRANT E REVOKE O mecanismo de visão possibilita dividir o banco de dados em partes conceituais de modo que a informação sensível fique oculta aos usuários não autorizados. Por meio das instruções GRANT e REVOKE é possível atribuir restrições aos usuários de acordo com um perfil previamente estabelecido. Esses representam a principal interface que 79 SEGURANÇA DE BANCO DE DADOS concede ao usuário capacidade de desempenhar qualquer operação através de comandos SQL. 12.3. OUTROS ASPECTOS DE SEGURANÇA As visões e o mecanismo de GRANT e REVOKE não são os únicos meios de se implementar segurança em banco de dados. Na verdade, não existe nenhum ponto no Sistema Gerenciador de Banco de Dados que proporcione um conjunto amplo de controles de segurança. Dessa forma, um suposto infiltrador suficientemente determinado é capaz de passar por todos os controles já mencionados. Para situações em que os dados sejam sensíveis, torna-se necessário um arquivo ou banco de dados especial, onde o sistema registra automaticamente todas as operações desempenhadas por determinado usuário. A isso chamamos de Log. Outro nível de segurança pode ser proporcionado pela codificação de dados. A idéia básica consiste no armazenamento físico dos dados no disco e antes de serem transmitidos são codificados (Criptografados). 12.4. CRIAÇÃO DE USUÁRIOS NO POSTGRESQL Para possibilitar a utilização da base de dados por usuários distintos, cada um trabalhando em uma sessão diferente é necessário realizar a criação de um usuário no Sistema Gerenciador de Banco de Dados atribuindo-lhe um nome para autenticação e senha. A este usuário criado poderão ser concedidas permissões diversas assegurando-lhe acesso aos dados, bem como concedendo e restringindo operações sobre as informações. O comando usando no PostgreSQL para criar usuário é o CREATE USER. No entanto, nas versões mais modernas é possível fazê-lo diretamente através do comando CREATE ROLE, possibilitando a utilização de mais recursos. Sintaxe: CREATE USER|ROLE nome [ [ WITH ] opção [ ... ] ] Onde, opção pode ser: SYSID id_do_usuário | CREATEDB | NOCREATEDB | CREATEUSER | NOCREATEUSER | IN GROUP nome_do_grupo [, ...] | [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'senha' | VALID UNTIL 'data_e_hora' Parâmetros: - Nome: O nome do usuário. - id_do_usuário: A cláusula SYSID pode ser utilizada para escolher o identificador de usuário do PostgreSQL do novo usuário. Normalmente não é necessário, mas pode ser útil se for necessário recriar o dono de um objeto que ficou órfão. Se não for especificado, será utilizado por padrão o maior identificador de usuário atribuído acrescido de um (com mínimo de 100). 80 SEGURANÇA DE BANCO DE DADOS - CREATEDB NOCREATEDB: Estas cláusulas definem a permissão para o usuário criar banco de dados. Se for especificado CREATEDB, o usuário sendo definido terá permissão para criar seus próprios bancos de dados. Se for especificado NOCREATEDB, nega-se ao usuário a permissão para criar banco de dados. Se nenhuma destas cláusulas for especificada, o padrão é NOCREATEDB. - CREATEUSER NOCREATEUSER : Estas cláusulas determinam se o usuário pode ou não criar novos usuários. CREATEUSER também torna o usuário um superusuário, o qual pode passar por cima de todas as restrições de acesso. Se nenhuma destas cláusulas for especificada, o padrão é NOCREATEUSER. - nome_do_grupo: O nome de um grupo existente onde o usuário será incluído como um novo membro. Podem ser especificados nomes de vários grupos. - Senha: Define a senha do usuário. Se não se pretende utilizar autenticação por senha esta opção pode ser omitida, mas o usuário não poderá mais se conectar se for decidido mudar para autenticação por senha. A senha poderá ser definida ou mudada posteriormente através do comando ALTER USER. - ENCRYPTED UNENCRYPTED: Estas cláusulas controlam se a senha será armazenada criptografada, ou não, nos catálogos do sistema; Se não for especificada nenhuma destas cláusulas, o comportamento padrão será determinado pelo parâmetro de configuração password_encryption. Se a cadeia de caracteres da senha já estiver criptografada no formato MD5, então a cadeia de caracteres será armazenada como está, independentemente de ser especificado ENCRYPTED ou UNENCRYPTED (porque o sistema não pode descriptografar a cadeia de caracteres criptografada contendo a senha). Esta funcionalidade permite a restauração de senhas criptografadas efetuadas por uma operação de dump/restore. Deve ser observado que os clientes antigos podem não possuir suporte para o mecanismo de autenticação MD5, necessário para trabalhar com as senhas que são armazenadas criptografadas. - data_e_hora: A cláusula VALID UNTIL define uma data e hora após a qual a senha do usuário não é mais válida. Se esta cláusula for omitida, a conta será válida para sempre. A cláusula VALID UNTIL define uma data de expiração para a senha apenas, e não para a conta do usuário per se. Em particular, a obediência à data de expiração não é imposta ao se conectar utilizando um método de autenticação não baseado em senha. Deve ser usado o comando ALTER USER para mudar os atributos de um usuário, e DROP USER para remover um usuário. Deve se usado ALTER GROUP para adicionar ou remover usuários de grupos de forma semelhante como acontece com a criação do usuário. O PostgreSQL inclui o programa createuser que possui a mesma funcionalidade do CREATE USER (na verdade, chama este comando), mas pode ser executado a partir da linha de comando. 81 SEGURANÇA DE BANCO DE DADOS Exemplos: Criar um usuário sem senha: CREATE USER Jonas; CREATE ROLE Jonas; Criar um usuário com senha: CREATE ROLE manuel WITH PASSWORD 'jw8s0F4'; Criar um usuário com uma senha válida até o fim de 2009. Após o primeiro segundo de 2010 a senha não será mais válida: CREATE ROLE miriam WITH PASSWORD 'jw8s0F4' VALID UNTIL '2010-0101'; Criar uma conta onde o usuário pode criar bancos de dados: CREATE ROLE manuel WITH PASSWORD 'jw8s0F4' CREATEDB; Criar um usuário com poderes de superusuário: CREATE ROLE administrador WITH SUPERUSER ‘xxxxx’; ENCRYPTED PASSWORD Para alterar determinado usuário, basta utilizar o comando ALTER ROLE de maneira semelhante como ocorre com o comando CREATE ROLE: Exemplo: Conceder permissão para o usuário criar outro usuários e bancos dedados: ALTER ROLE miriam CREATEROLE CREATEDB; Para excluir usuário do banco de dados utiliza-se o comando DROP: DROP ROLE Manoel; Para saber quais usuário estão cadastrados no PostgreSQL basta fazer uma consulta na tabela do sistema pg_user: SELECT username FROM pg_user; 12.5. CRIANDO GRUPOS DE USUÁRIOS No PostgreSQL é possível criar grupos de usuários. Lembrando que todas as permissões atribuídas ao grupo serão repassadas aos usuários pertencentes a ele. Sintaxe: CREATE GROUP nomedogrupo; Exemplos: CREATE GROUP adm; 82 SEGURANÇA DE BANCO DE DADOS Adicionar e remover usuário de um grupo: Exemplo: ALTER GROUP adm ADD USER miriam, administrador; ALTER GROUP adm DROP USER miriam; Para excluir um grupo, basta usar o comando padrão para exclusão de objetos no banco de dados, DROP. Lembrando que o comando DROP remove apenas o grupo e não os usuários do grupo. Exemplo: DROP GROUP adm; Para listar todos os grupos criados é necessário consultar a tabelas do sistema pg_group: SELECT gronome FROM pg_group; 12.6. CONCEDENDO PRIVILÉGIOS AOS USUÁRIOS Nesta etapa é importante lembrar que o superusuário tem o direito de fazer o que bem entender nos bancos de dados existentes. Outro aspecto é relativo ao proprietário de um objeto no banco de dados. Sendo ele o dono do objeto ele também tem todas as permissões para fazer o que bem entender com esse objeto. São vários os privilégios que um usuário pode receber, dentre eles: - SELECT - INSERT - UPDATE - DELETE - RULE - REFERENCES - TRIGGER - CREATE - TEMPORARY - EXECUTE - USAGE - LOGIN - CONNECT A concessão de privilégios se dá através do comando GRANT e sua revogação através do comando REVOKE. Sintaxe: GRANT privilégio ON objeto TO usuário; REVOKE privilégio ON objeto FROM usuário; Exemplos: GRANT SELECT, INSERT, UPDATE ON cliente TO miriam; Para atribuir e retirar permissão ao grupo: GRANT SELECT, DELETE ON cliente TO adm; REVOKE UPDATE ON cliente FROM miriam; Para remover os privilégios de todos os usuários: REVOKE ALL ON cliente FROM PUBLIC; 83 SEGURANÇA DE BANCO DE DADOS Os privilégios especiais do dono da tabela são sempre inerentes à condição de ser o dono, não podendo ser concedidos ou revogados. No entanto, o dono do objeto pode revogar seus próprios privilégios, tornando-se um usuário comum como os demais no que se refere ao objeto na qual é proprietário. Em geral, somente o dono e o superusuário podem conceder ou revogar privilégios para um objeto. 12.7. OBSERVAÇÕES IMPORTANTES O usuário do sistema (superusuário) deve ser um usuário criado exclusivamente para o PostgreSQL. Nunca devemos torná-lo dono de um executável. Os nomes de usuário são globais para todos os bancos de dados. 84 TRANSAÇÕES EM BANCO DE DADOS Parte 13 TRANSAÇÕES EM BANCO DE DADOS O termo transação refere-se a uma coleção de operações que formam uma única unidade de trabalho lógica. Por exemplo, a transferência de dinheiro de uma conta para outra é uma transação consistindo de duas atualizações, uma para cada conta. Uma transação é uma unidade de execução do programa que acessa e possivelmente atualiza vários itens de dados. Para garantir a integridade dos dados, é necessário que o SGBD mantenha as seguintes propriedades das transações: atomicidade, consistência, isolamento e durabilidade. - Atomicidade: uma transação é uma unidade atômica de processamento; ou ela será executada em sua totalidade ou não será de modo nenhum. - Consistência: uma transação deve ser preservadora de consistência se sua execução completa fizer o banco de dados passar de um estado consistente para outro também consistente. - Isolamento: uma transação deve ser executada como se estivesse isolada das demais. Isto é, a execução de uma transação não deve sofrer interferência de quaisquer outras transações concorrentes. - Durabilidade: as mudanças aplicadas ao banco de dados por uma transação efetivada devem persistir no banco de dados. Essas mudanças não devem ser perdidas em razão de uma falha. Essas propriedades normalmente são conhecidas como propriedades ACID. Esse acrônimo é derivado da primeira letra de cada uma das quatro propriedades. Quando se trabalham com transações, é necessário que se faça pelo menos duas ressalvas. A primeira é que em certas situações é interessante se agregar vários comandos como sendo integrantes de uma mesma transação, como, por exemplo, em uma transferência bancária que envolve a retirada de dinheiro de uma conta e o acréscimo em outra como se fosse apenas uma única operação lógica. A segunda ressalva é que em outras situações se faz necessário sacrificar ou flexibilizar as características ACID em virtude da necessidade de maior desempenho. 13.1. ESTADOS DE UMA TRANSAÇÃO Na ausência de falhas, todas as transações são completadas com sucesso. Porém, uma transação nem sempre pode completar sua execução com sucesso. Caso isso ocorra, essa transação é considerada abortada. 85 TRANSAÇÕES EM BANCO DE DADOS Se houver necessidade de garantir a propriedade de atomicidade, uma transação abortada não pode ter efeito sobre o estado do banco de dados. Assim, qualquer mudança que a transação abortada tenha feito no banco de dados deve ser desfeita. Quando as mudanças causadas por uma transação abortada tiverem sido desfeitas, dizemos que a transação foi revertida (rolled back). Se uma transação foi executada com sucesso, diz-se que foi confirmada (committed). Assim, é possível estabelecer vários estados para as transações: - Active: corresponde ao estado inicial. A transação fica neste estado enquanto está sendo executada. - Partially Committed: Após a última instrução ter sido executada. - Failed: Depois de se descobrir que a execução normal não pode continuar. - Aborted: quando a transação foi revertida e a base de dados voltou ao estado anterior a transação - Committed: como dito antes, após uma conclusão da transação com êxito. Figure 17 – Estado das Transações 13.2. CONTROLE DE CONCORRÊNCIA Após uma abordagem inicial sobre transações, é fácil verificar que estas fazem sentido num âmbito concorrente. O controle de concorrência é uma das coisas mais importantes em bases de dados grandes. Tomemos novamente como exemplo o caso de um banco, em que várias pessoas podem acessar aos mesmos dados simultaneamente, fazendo várias operações iguais ou diferentes. Caso não haja um controlo de concorrência forte, poderiam existir problemas sérios. 86 TRANSAÇÕES EM BANCO DE DADOS Para lidar com este problema, foram desenvolvidas técnicas específicas para lidar com esta concorrência: - Protocolos Baseados em Locks: Estes protocolos baseiam-se, tal como o nome indica, na utilização de locks. Através destas propriedades, podemos garantir que o acesso a determinados dados são feitos de acordo com estes locks, mas basicamente a idéia é que se uma transação está acessando determinados dados, mais nenhuma outra transação pode acessar estes (modo exclusivo). Através de propriedades de locks e unlocks conseguimos estabelecer estas regras. Note que estes locks podem ser do tipo exclusivo, em que se uma transação obtém um exclusive-mode lock mais nenhuma transação poderá ler ou escrever sobre esses dados, ou também podem ser do tipo shared, em que se uma transação obtém o shared-mode lock qualquer outra transação poderá ler os mesmos dados, mas nunca escrever neles. Um protocolo deste gênero muito conhecido é o Two-Phase Locking Protocol, que consiste em duas fases distintas, Growing Phase, onde são obtidos os locks sem nunca os libertar, e a shrinking phase, onde uma transação liberta os locks, não os podendo obter mais. - Protocolos Baseados em Time-Stamps: Para cada transação no sistema, é atribuído um time-stamp, antes de esta começar a execução. Há então duas formas de funcionamento: a atribuição do time-stamp pode ser com a utilização do clock do sistema ou utilizando um contador lógico, que é incrementado sempre que uma nova transação entra no sistema. Através destes time-stamps conseguem-se fazer um controle de concorrência. - Protocolos Multi-Versões: De forma a maximizar ainda mais a concorrência, este tipo de protocolo cria várias cópias do mesmo item. Assim, cada write (Q) cria uma nova versão de Q e quando Q é chamado para leitura, o sistema elege a versão do Q mais apropriada, garantida a serialização. É neste âmbito que surgem então os protocolos Multiversion Timestamp Ordering e o conhecido Two-Phase Locking. Existem muitos outros protocolos, no entanto esses se destacam como mais utilizados. 13.3. GESTÃO DE CONCORRÊNCIA NO POSTGRESQL O PostGreSQL implementa uma política de auto-commit. Desta forma, cada instrução é tratada como uma transação. Um read é uma transação, um write é outra, etc… Há então duas formas de contornar esta situação: uma é simplesmente desligar o auto-commit, através da instrução \SET AUTOCOMMIT OFF. Assim, é tudo considerado uma transação até que a instrução commit seja executada. A outra solução passa por indicar explicitamente o início e o fim de uma transação usando o comando BEGIN no início da transação e o comando COMMIT; no final desta. Caso se pretenda anular a transação, ao invés do comando COMMIT é possível introduzir o comando ROLLBACK. 13.3.1. Garantia de Isolamento O PostgreSQL, diferente de muitos outros Sistemas Gerenciadores de Banco de Dados, mantém os dados coerentes através da utilização de modelos multiversão (Multiversion Concurrency Control, MVCC). Assim, cada transação vê uma versão da base de dados (snapshot) tal como era em algum tempo atrás, independentemente do estado atual dos dados, evitando assim o problema de uma transação poder ver os dados incoerentes. Protocolos em 87 TRANSAÇÕES EM BANCO DE DADOS nível de locks de tabela e tupla também são possíveis no PostgreSQL para aplicações que não se adaptem bem ao funcionamento do modelo MVCC, sendo que é necessário estabelecer manualmente o nível de granularidade desejado. Contudo, um uso cuidado no MVCC é melhor que a utilização de locks. 13.3.2. Isolamento nas Transações O SQL standard define quatro níveis de isolamento de transações de acordo com três fenômenos que não devem acontecer entre transações concorrentes, sendo os fenômenos os seguintes: dirty reading, nonrepeatable read e phantom read. - Dirty reading: uma transação lê dados que foram modificados por uma outra transação concorrente que ainda não realizou o comando commit; - Nonrepeatable read: uma transação re-lê dados e descobre que os estes foram modificados por uma outra transação; - Phantom read: uma transação executa novamente uma pergunta e descobre que os valores que satisfazem a pergunta são diferentes da anterior, devido a um commit de uma outra transação. Os quatro níveis de isolamento das transações SQL são as seguintes: Tabela 1- Nível de isolamento das transações SQL Isolation level Read uncommitted Read committed Repeatable read Serializable Dirty Read Possible Not Possible Not Possible Not Possible Nonrepeatable Read Possible Possible Not Possible Not Possible Phantom Read Possible Possible Possible Not Possible O PostgreSQL suporta o Read Commited e o Serializable. Como o padrão SQL tem mais dois elementos, o Read Uncommitted é tratado como um Repetable Read que é tratado como Serializable. O Read Commited é o grau de isolamento padrão do sistema em questão. 13.3.3. Níveis de Granularidade (Locks) O PostgreSQL permite a utilização de locks com vários níveis de granularidade (explicit locking), em nível de tabela, em nível de tuplas e, em versões mais recentes, os chamados advisory locks. Para criar locks em nível de tabela, a instrução utilizada é a seguinte: LOCK [ TABLE ] [ ONLY ] name [, ...] [ IN lockmode MODE ] [ NOWAIT ] Sendo que lock mode é um dos seguintes: ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE 88 TRANSAÇÕES EM BANCO DE DADOS 13.3.4. Consistência de Dados no PostgreSql Como no sistema PostgreSQL um leitor nunca realiza locks sobre os dados, dados lido numa transação podem ser escritos por cima por uma outra corrente transação, ou seja, os dados lidos podem não ser sempre os mais atuais. Para resolver este problema surge um pouco à necessidade de se utilizarem os locks, SELECT FOR UPDATE, SELECT FOR SHARE, ou fazendo locks sobre toda a tabela, como forma de impossibilitar a modificação dos dados em questão. Porém, a utilização excessiva de locks pode limitar, e muito, a concorrência de uma base de dados, que, é bastante proveitosa em inúmeras situações. 13.3.5. Atomicidade e Durabilidade no PostgreSql De acordo com as propriedades anteriores, é justo dizer que as transações funcionam como um todo. Ou a transação é executada na sua totalidade, ou nada dela é executada. Caso haja falha, a base de dados é restaurada, através de mecanismos de rollback, que serão abordados no seguinte tópico. O mesmo acontece com a durabilidade. Caso a transação se concretize os dados são guardados em memória estável e o utilizador é notificado do sucesso da operação. Em caso de falha, os dados não são guardados e o utilizador é notificado da falha que ocorreu. 13.3.6. Mecanismos de Rollback e Recuperação no PostgreSql Por vezes, em transações complexas e grandes, há necessidade de estabelecer save points. Estas são pontos de restauração do sistema, considerado um ponto especial na transação que permite a execução de todos os comandos após ter existido um rollback, restaurando o estado da transação para o estado atual no save point. Para criar um save point, basta introduzir o seguinte comando: SAVEPOINT savepoint_name Desta forma é então possível estabelecer um save point. Para se retornar ao estado atual num save point, é necessário invocar comandos de rollback, que podem seguir as seguintes definições: ROLLBACK [ WORK | TRANSACTION ] ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] savepoint_name A primeira opção restaura o sistema todo, ou seja, volta para o início da transação, anulando todas as operações efetuadas. A segunda efetua um rollback para um determinado save point. A seguir um exemplo concreto bem como os resultados de operações com save points e rollbacks: 89 TRANSAÇÕES EM BANCO DE DADOS Exemplo: CREATE TABLE teste (id INTEGER, PRIMARY KEY (id)); COMMIT; INSERT INTO teste VALUES (1); COMMIT; INSERT INTO teste VALUES (2); SAVEPOINT y; DELETE FROM teste; SELECT * FROM teste; //nada é mostrado ROLLBACK TO y; SELECT * FROM teste; //são mostrados os valores 1 e 2 ROLLBACK; SELECT * FROM teste; // é mostrado o valor 1 90 REFERÊNCIAS REFERÊNCIAS ELMASRI, Ramez; Sistema de Banco de Dados - Fundamentos e Aplicações - 4ª Edição, Pearson Education, 2005. OLIVEIRA, Celso Henrique Poderoso; SQL – Curso Prático; Novatec, 2002. MOMJIAN, Bruce; PostgreSQL: Introduction and Concepts. E-Book; Addison-Wesley, 2001. PostgreSQL – Manual (With user comments) do PostgreSQL 8.3. Disponível em <http://www.postgresql.org/files/documentation/pdf/8.3/postgresql-8.3-A4.pdf> PostgreSQL – Manual (The SQL Language) do PostgreSQL 9.1. Disponível em <http://www.postgresql.org/docs/9.1/static/sql.html> 91