mrocha98 / projeto-modelagem-de-dados

Projeto final da disciplina de Arquitetura e Modelagem de Dados - BD FATEC 2020

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Projeto de Modelagem de Dados - Clínica de Fisioterapia

GitHub GitHub repo size

Projeto final da disciplina de Arquitetura e Modelagem de Dados - BD FATEC 2020

Aluno: Matheus Rocha da Silva - RA: 1460281823034

Introdução

Dr. Hans Chucrute

O famoso Dr. Hans Chucrute contratou uma empresa de desenvolvimento de software para automatizar os processos administrativos da sua clínica de fisioterapia.

O cliente gostaria de que fosse construído um banco de dados, pois atualmente tudo é organizado de forma manual, com uma agenda física, que é facilmente suscetível a erros.

Levantamento de Requisitos

Depois de conversar com o cliente, foram levantadas as seguintes informações:

  • É necessário guardar informações básicas, que são comuns tanto aos pacientes, quanto aos fisioterapeutas. São elas:
    • Nome, obrigatório;
    • E-mail, obrigatório e único, será utilizado para logar no futuro sistema web;
    • Senha, obrigatória;
    • Data de nascimento, obrigatória;
    • Sexo, obrigatório;
    • Telefones para contato, pelo menos um;
    • Endereço, opcional;
      • Não foram especificados os campos do endereço;
  • Todo paciente possui um diagnóstico inicial, mas o doutor prefere usar o termo anamnese, pois soa mais profissional.
  • Para os fisioterapeutas, é preciso guardar:
    • Data de admissão, obrigatória;
    • Data de demissão, opcional;
    • Salário, obrigatório;
    • Datas dos pagamentos e quanto foi pago;
  • Atualmente a clínica só trabalha com três tipos de planos, terapia manual, pilates e RPG (não é o de mesa). Os planos possuem:
    • Nome, obrigatório;
    • Frequência semanal, obrigatória;
    • Uma descrição, opcional;
    • Um valor sugerido de mensalidade, obrigatório;
  • Um paciente pode se matricular em quantos planos quiser, assim como um fisioterapeuta pode atender em qualquer plano;
  • É necessário guardar a data da matrícula e se ela está ativa ou não;
  • Para cada matrícula, será gerada uma mensalidade, que deve conter:
    • O valor solicitado, obrigatório;
    • O valor pago, obrigatório;
    • A data da mensalidade, obrigatória;
    • Um comentário opcional;
  • Cada sessão de terapia precisa conter:
    • Qual paciente foi atendido, obrigatório;
    • Qual fisioterapeuta atendeu, obrigatório;
    • A data em que a sessão ocorreu, obrigatória;
    • Se o paciente compareceu, obrigatório;
    • Um comentário opcional;
  • O doutor também mencionou que, para fins estatísticos, seria interessante anotar quais exercícios foram feitos em cada sessão, porém é importante ter em mente que nem toda sessão tem exercícios, como por exemplo a primeira, onde só é feita uma entrevista com o paciente.
  • Quanto ao exercício:
    • Possui um nome, obrigatório;
    • Pode utilizar um equipamento, que também possui um nome, obrigatoriamente;

Modelo Conceitual

Modelo Conceitual https://github.com/mrocha98/projeto-modelagem-de-dados/blob/master/Assets/modelo-conceitual.jpg

Modelo Lógico (teórico)

Modelo Lógico Teórico https://github.com/mrocha98/projeto-modelagem-de-dados/blob/master/Assets/modelo-logico.jpg

Modelo Lógico (prático)

Durante o desenvolvimento optei por transformar todos identificadores em Unique Keys e criei colunas ID como primary key.

Segue modelo lógico gerado pelo software DBeaver Community após a execução do Script DLL.sql.

Modelo Lógico Prático https://github.com/mrocha98/projeto-modelagem-de-dados/blob/master/Assets/dbeaver-diagram.png

SQL

Postgres Logo

Para o banco de dados, optei pelo Postgres, por ser muito fácil de utilizar com o Docker.

Todos os códigos estão disponíveis nesse repositório do Github:

https://github.com/mrocha98/projeto-modelagem-de-dados.

DDL

create table if not exists UF (
	id uuid not null,
	sigla char(2) not null,
	constraint PK_UF primary key (id),
	constraint UK_UF unique (sigla)
);


create table if not exists Cidade (
	id uuid not null,
	UF_id uuid not null,
	nome varchar(255) not null,
	constraint PK_Cidade primary key (id),
	constraint FK_Cidade_UF foreign key (UF_id) references UF(id),
	constraint UK_Cidade unique (UF_id, nome)
);


create table if not exists Endereco (
	id uuid not null,
	cidade_id uuid not null,
	bairro varchar(255) not null,
	rua text not null,
	numero smallint not null,
	ponto_de_referencia text null,
	constraint PK_Endereco primary key (id),
	constraint FK_Endereco_Cidade foreign key (cidade_id) references cidade (id),
	constraint UK_Endereco unique (cidade_id, bairro, rua, numero)
);

create table if not exists Telefone (
	id uuid not null,
	numero varchar(11) not null,
	constraint PK_Telefone primary key (id),
	constraint UK_Telefone unique(numero)
);


create table if not exists Usuario (
	id uuid not null,
	email varchar(255) not null,
	senha bytea not null,
	nome varchar(255) not null,
	sexo char(1) not null,
	data_nasc timestamptz not null,
	endereco_id uuid null,
	constraint PK_Usuario primary key (id),
	constraint FK_Usuario_Endereco foreign key (endereco_id) references Endereco (id),
	constraint UK_Usuario unique (email),
	constraint CK_Usuario_sexo check (sexo = 'M' or sexo = 'F')
);

create table if not exists Telefone_Por_Usuario (
	id uuid not null,
	telefone_id uuid not null,
	usuario_id uuid not null,
	constraint PK_Telefone_Por_Usuario primary key (id),
	constraint FK_Telefone_Por_Usuario_Telefone foreign key (telefone_id) references Telefone (id),
	constraint FK_Telefone_Por_Usuario_Usuario foreign key (usuario_id) references Usuario (id),
	constraint UK_Telefone_Por_Usuario unique (telefone_id, usuario_id)
);

create table if not exists Fisioterapeuta (
	id uuid not null,
	id_usuario uuid not null,
	data_admissao timestamptz not null,
	data_demissao timestamptz null,
	salario numeric(7, 2) not null,
	constraint PK_Fisioterapeuta primary key (id),
	constraint FK_Fisioterapeuta_Usuario foreign key (id_usuario) references Usuario (id),
	constraint UK_Fisioterapeuta unique (id_usuario),
	constraint CK_Fisioterapeuta_salario check (salario > 0)
);

create table if not exists Paciente (
	id uuid not null,
	id_usuario uuid not null,
	anamnese text null,
	constraint PK_Paciente primary key (id),
	constraint FK_Paciente_Usuario foreign key (id_usuario) references Usuario (id),
	constraint UK_Paciente unique (id_usuario)
);

create table if not exists Pagamento (
	id uuid not null,
	id_fisioterapeuta uuid not null,
	data timestamptz not null,
	valor numeric(7, 2) not null,
	constraint PK_Pagamento primary key (id),
	constraint FK_Pagamento_Fisioterapeuta foreign key (id_fisioterapeuta) references Fisioterapeuta(id),
	constraint UK_Pagamento unique (id_fisioterapeuta, data)
);


create table if not exists Plano (
	id uuid not null,
	nome varchar(255) not null,
	frequencia_semanal smallint not null,
	valor_sugerido numeric (5, 2) not null,
	descricao text null,
	constraint PK_Plano primary key (id),
	constraint UK_Plano unique (nome, frequencia_semanal),
	constraint CK_Plano_valor_sugerido check (valor_sugerido >= 0)
);

create table if not exists Matricula (
	id uuid not null,
	id_paciente uuid not null,
	id_plano uuid not null,
	data timestamptz not null,
	status bool not null,
	constraint PK_Matricula primary key (id),
	constraint FK_Matricula_Paciente foreign key (id_paciente) references Paciente (id),
	constraint FK_Matricula_Plano foreign key (id_plano) references Plano (id),
	constraint UK_Matricula unique (id_paciente, id_plano)
);

create table if not exists Mensalidade (
	id uuid not null,
	id_matricula uuid not null,
	data timestamptz not null,
	valor_solicitado numeric (5, 2) not null,
	paciente_pagou bool not null,
	constraint PK_Mensalidade primary key (id),
	constraint FK_Mensalidade_Matricula foreign key (id_matricula) references Matricula (id),
	constraint UK_Mensalidade unique (id_matricula, data)
);


create table if not exists Equipamento (
	id uuid not null,
	nome varchar(255) not null,
	constraint PK_Equipamento primary key (id),
	constraint UK_Equipamento unique (nome)
);

create table if not exists Exercicio (
	id uuid not null,
	nome varchar(255) not null,
	id_equipamento uuid null,
	constraint PK_Exercicio primary key (id),
	constraint RK_Exercicio_Equipamento foreign key (id_equipamento) references Equipamento (id),
	constraint UK_Exercicio unique (nome)
);

create table if not exists Terapia (
	id uuid not null,
	id_matricula uuid not null,
	id_fisioterapeuta uuid not null,
	data timestamptz not null,
	paciente_compareceu bool not null,
	observacao text null,
	constraint PK_Terapia primary key (id),
	constraint FK_Terapia_Matricula foreign key (id_matricula) references Matricula (id),
	constraint FK_Terapia_Fisioterapeuta foreign key (id_fisioterapeuta) references Fisioterapeuta (id),
	constraint UK_Terapia unique (id_matricula, id_fisioterapeuta, data)
);

create table if not exists Exercicio_Por_Terapia (
	id uuid not null,
	id_exercicio uuid not null,
	id_terapia uuid not null,
	constraint PK_Exercicio_Por_Terapia primary key (id),
	constraint FK_Exercicio_Por_Terapia_Exercicio foreign key (id_exercicio) references Exercicio (id),
	constraint FK_Exercicio_Por_Terapia_Terapia foreign key (id_terapia) references Terapia (id),
	constraint UK_Exercicio_Por_Terapia unique (id_exercicio, id_terapia)
);

DML

Os dados inseridos estão no script DML.sql. Não criei um preview com eles pois o arquivo contêm mais de 700 linhas.

DQL

-- Dados de todos os usuários
select usr.nome, usr.sexo, date(usr.data_nasc) as data_nasc, tel.numero as telefone, concat(endr.rua, ', ', endr.numero, ' - ', endr.bairro) as endereco, cid.nome as cidade, uf.sigla as UF
from usuario usr
inner join telefone_por_usuario tel_p_usr on usr.id = tel_p_usr.usuario_id
left join telefone tel on tel.id = tel_p_usr.telefone_id
left join endereco endr on usr.endereco_id = endr.id
inner join cidade cid on endr.cidade_id = cid.id
inner join uf on cid.uf_id = uf.id
order by usr.nome ASC;

-- Total de usuários por sexo
select sexo, count(sexo) as total
from usuario
group by sexo;

-- Montante total de pagamentos recebidos e qtd de vezes em que recebeu
select usr.nome, sum(pag.valor) as montante, count(pag.id) as qtd
from usuario usr
inner join fisioterapeuta fisio on usr.id = fisio.id_usuario
inner join pagamento pag on pag.id_fisioterapeuta = fisio.id
group by usr.nome
order by usr.nome ASC;

-- Pacientes e seus diagnósticos
select usr.nome, pac.anamnese
from usuario usr
inner join paciente pac on usr.id = pac.id_usuario
order by usr.nome asc;


-- Matrículas
select usr.nome as paciente, plan.nome as plano, plan.frequencia_semanal as freq_sem, mat.status, mat.data
from usuario usr
inner join paciente pac on usr.id = pac.id_usuario
inner join matricula mat on pac.id = mat.id_paciente
inner join plano plan on plan.id= mat.id_plano
order by usr.nome asc;


-- Quantidade de matrículas ativas por plano
select plan.nome as plano, count(mat.id) as qtd
from usuario usr
inner join paciente pac on usr.id = pac.id_usuario
inner join matricula mat on pac.id = mat.id_paciente
inner join plano plan on plan.id= mat.id_plano
group by plan.nome, mat.status
having mat.status = true
order by plan.nome ASC;


-- Pacientes que moram na Av. Andrômeda, Residencial Jardim Satélite - São José dos Campos, SP
-- e com matrícula ativa em Pilates
select usr.nome as paciente
from usuario usr
inner join paciente pac on usr.id = pac.id_usuario
inner join matricula mat on pac.id = mat.id_paciente
inner join plano plan on plan.id = mat.id_plano and plan.nome = 'Pilates' and mat.status = true
inner join endereco endr on usr.endereco_id = endr.id and endr.bairro = 'Residencial Jardim Satélite'
inner join cidade cid on endr.cidade_id = cid.id and cid.nome = 'São José dos Campos'
inner join uf on cid.uf_id = uf.id and uf.sigla = 'SP';


-- Mensalidades que não foram pagas
select usr.nome as paciente, plan.nome as plano, mens.valor_solicitado, date(mens.data) as data
from usuario usr
inner join paciente pac on usr.id = pac.id_usuario
inner join matricula mat on pac.id = mat.id_paciente
inner join plano plan on plan.id= mat.id_plano
inner join mensalidade mens on mens.id_matricula = mat.id and mens.paciente_pagou = false;


-- Mensalidades da paciente Lorena Braga
select date(mens.data) as data, mens.valor_solicitado, mens.paciente_pagou
from mensalidade mens
inner join matricula mat on mens.id_matricula = mat.id
inner join paciente pac on mat.id_paciente = pac.id
inner join usuario usr on usr.id = pac.id_usuario and usr.nome = 'Lorena Braga';


-- Todos exercícios
select exr.nome as exercicio, eqp.nome as equipamento
from exercicio exr
left join equipamento eqp on exr.id_equipamento = eqp.id
order by exr.nome ASC;


-- Quantidade de exercícios por equipamento
select eqp.nome as equipamento, count(exr.id) as qtd
from exercicio exr
inner join equipamento eqp on exr.id_equipamento = eqp.id
group by eqp.nome
order by eqp.nome ASC;


-- Exercícios que não utilizam um equipamento
select exr.nome as exercicio
from exercicio exr
left join equipamento eqp on exr.id_equipamento = eqp.id
where exr.id_equipamento is null;


-- Terapias
select ter.data, pla.nome as plano, usrPAC.nome as paciente, ter.paciente_compareceu as compareceu, usrFIS.nome as fisioterapeuta
from matricula mat
inner join paciente pac on pac.id = mat.id_paciente
inner join plano pla on pla.id = mat.id_plano
inner join terapia ter on ter.id_matricula = mat.id
inner join fisioterapeuta fis on fis.id = ter.id_fisioterapeuta
inner join usuario usrPAC on usrPAC.id = pac.id_usuario
inner join usuario usrFIS on usrFIS.id = fis.id_usuario
order by ter.data DESC;

Normalização

Levando em conta o modelo lógico teórico.

Critérios (lógica OU):

  • 1FN
    • Não possui dependências funcionais
    • Não possui dependências multivaloradas
    • Não possui campos multivalorados
    • Não possui campos compostos
  • 2FN
    • PK com uma única coluna
    • Não possui colunas que não fazem parte da PK
  • 3FN
    • Não possui colunas que não fazem parte da PK
    • Possui apenas uma única coluna que não faz parte da PK
Tabela 1FR 2FR 3FR Comentários
UF Possui apenas 1 campo e é PK
Cidade Todos os campos fazem parte da PK
Endereço Possui apenas uma única coluna que não faz parte da PK (Ponto_de_referência)
Telefone Possui apenas 1 campo e é PK
Telefone_Por_Usuário Todos os campos fazem parte da PK
Usuário
Fisioterapeuta
Pagamento Possui apenas uma única coluna que não faz parte da PK (Valor)
Paciente Possui apenas uma única coluna que não faz parte da PK (Anamnese)
Plano
Matrícula
Terapia
Equipamento Possui apenas 1 campo e é PK
Exercício Possui apenas uma única coluna que não faz parte da PK (Nome_Equipamento)
Terapia
Exercício_Por_Terapia Todos os campos fazem parte da PK

Se em algum momento surgir a necessidade de cadastrar um novo tipo de funcionário, secretário por exemplo, teríamos que repetir os campos Data_Admissão, Data_Demissão, e Salário, além de que seria necessário criar uma tabela Pagamento exclusiva para esse novo tipo de funcionário.

não normalizado

Para resolver esse problema, primeiro criamos a tabela Funcionário, que será responsável por armazenar as informações em comum.

1FN

Funcionário(E-Mail_Usuário, Data_Admissão, Data_Demissão, Cargo, Salário)

Fisioterapeuta(E-Mail_Funcionário)

Secretário(E-Mail_Funcionário)

Essa nova tabela atende aos requisitos para estar na 1FN, todavia, não está na 2FN, pois Salário sempre repete para o mesmo cargo.

2FN

Cargo(Nome, Salário)

Funcionário(E-Mail_Usuário, Nome_Cargo, Data_Admissão, Data_Demissão)

Fisioterapeuta(E-Mail_Funcionário, Nome_Cargo)

Secretário(E-Mail_Funcionário, Nome_Cargo)

3FN

Cargo já está na 3FN pois só possui uma coluna que não faz parte da PK.

Fisioterapeuta e Secretário já estão na 3FN pois todos seus campos fazem parte da PK.

Funcionário já está na 3FN pois os campos que não fazem parte da PK (Data_Admissão, Data_Demissão) não são referências transitivas (não dependem de campos não chave).

normalizado

About

Projeto final da disciplina de Arquitetura e Modelagem de Dados - BD FATEC 2020

License:BSD 2-Clause "Simplified" License


Languages

Language:TSQL 100.0%