Parte I
Revisado em 09 de dezembro de 10 01:00 EST
Olhei para o seu DDL. OK. Precisamos dar um passo atrás e organizar seu banco de dados primeiro. Isso resolverá metade dos seus problemas (seu SQL será direto; e rápido; menos índices; não são necessárias tabelas temporárias). Por um tempo eu pensei, aha, você tem suas colunas, deve ser estável, mas não há chance. De cima para baixo do zero, ok. Dê uma olhada neste Diagrama de Relação de Entidade (não adianta trabalhar no Modelo de Dados, que é Entidades, Relações e Atributos , até acertarmos os ERs) e verificar se está correto.
-
A maneira de fazer isso é responder às seguintes perguntas (respostas curtas são boas). Essas perguntas estão esclarecendo as Entidades e Regras de Negócios . Como você entende os bancos de dados em geral e seus dados em particular é crucial. Você percorreu um longo caminho, por conta própria, para que possamos continuar a partir daí.
-
Acho que ▶esta postagem◀ pode ser útil para você, para entender as etapas formais que devem ser seguidas; que estamos curto-circuitando aqui.
-
Mais importante, total e completamente, esqueça a função e quaisquer requisitos de codificação. Os dados devem ser modelados independentemente da aplicação, simplesmente como dados. Modelagem de funções é uma ciência diferente. Primeiro acerte um; então acerte o outro; e os dois juntos tocam músicas lindas. Tente juntá-los; fazendo as duas tarefas ao mesmo tempo, e eles não vão nem fazer uma banda de garagem suburbana.
Por brevidade, e para o bem de quem estiver lendo isto, eu uso uma Seção Fechada e Aberta; quando um item Aberto (discussão) é fechado, vou torná-lo conciso e movê-lo para a seção Fechado. Mantenha a numeração, porque as coisas às vezes voltam para nos assombrar. Você pode querer fazer o mesmo, ou até mesmo excluir a discussão do seu lado.
Os links para as belas fotos estão no final.
Desculpas:a edição não funciona; a subnumeração é inconsistente
Problemas encerrados
- users.bb_locations_csv é uma relação muitos-para-muitos entre usuários e locais:
- Cada um desses elementos deve ser uma entrada em uma coluna discreta, em uma linha discreta
- Um usuário pode ter vários locais e 1 local pode ter muitos usuários é muitos-para-muitos
- Leia ▶esta postagem◀ para uma discussão sobre como isso é tratado e em que estágio é tratado
- Neste Estágio Lógico, que é apenas uma relação n::n, como eu desenhei, você pode esquecê-la por enquanto, ela será fornecida, simplesmente, quando chegarmos ao Estágio físico.
- Confie em mim, fornecerei um código que não seja mais complexo do que
...WHERE IN ()
para seu propósito declarado. - Pensando bem, se eu quebrar seus dedos, você digitará ainda mais devagar, então é melhor não
- Ok, seu aplicativo é baseado em navegador e a página é dinâmica (meu conselho foi para páginas estáticas que precisam ser retocadas); vá em frente com as caixas de seleção.
.
- users.bb_categories_csv é uma relação muitos-para-muitos entre usuários e categorias
- Idem.
.
- Idem.
-
Confirmado:não existe boletim (bbs) sem usuário; um usuário emite um boletim, e isso inicia todo o ciclo; em seguida, convida respostas e classificações.
3.1 Confirmado:Existe realmente apenas um quadro de avisos e ele não existe como Coisa no banco de dados.
3.2 Confirmado:que a organização nunca terá mais de um quadro de avisos, e as classificações e categorizações são todas tratadas adequadamente pela tabela/função Categoria
-
Excluído.
-
Confirmado:A diferença entre boletins e respostas é que as respostas dependem de um boletim para existir, não têm título e não são categorizadas por local ou categoria porque dependem do próprio boletim para existir.
-
Excluído.
-
Comentários anotados. Resolvido.
7.1. Para cada boletim enviado por outro usuário, cada usuário pode postar mais de uma resposta.
7.2. Para cada boletim enviado por um usuário, esse usuário pode postar uma ou mais de uma resposta.
7.3. Excluído.
7.4. Excluído.
.
8. Confirmado:cada usuário pode postar no máximo uma avaliação em um boletim (que pode ser revogada/alterada)
.
9. Confirmado:cada usuário pode postar no máximo uma classificação para uma resposta (idem)
10.1. Dado:nome de usuário vem da organização e é o nome exclusivo que identifica os funcionários. Por exemplo, e-mails são [email protected] - a autenticação é feita com ldap e isso é necessário para conectar e recuperar outras informações sobre os funcionários
- Confirmado:UserName é um excelente identificador
10.2. Confirmado:FirstName, LastName ... BirthPlace, etc permanecem como colunas (as tradicionais) para garantir
People
não são duplicados..
11. Dado:No momento podemos identificar nossos escritórios por nomes casuais que geralmente são conhecidos dentro da organização, já que temos apenas cerca de 3 escritórios principais e muitos escritórios de campo. Assim, exemplos seriam Washington DC ou o escritório de campo da Virgínia. No total, acho que tentaremos manter o total abaixo de 20. Também quero registrar o endereço exato de cada local, porque isso pode ser usado para identificar exclusivamente os escritórios para os usuários.
- Fornecido:
StateCode+Town
como PK;IsMainOffice
como booleano.
.
12. Confirmado:
Description
e Name
para Category
são obrigatórios..
13. Dado:Os usuários não poderão postar em algumas categorias. Somente usuários com direitos suficientemente altos terão o direito de postar em determinadas categorias.
- Fornecido:
Permission
emUser, Location, Category
é um método de avaliação de tais direitos.
.
14. Confirmado:
Location.Administrator
é UserId
de admin para o Location
..
15. Dado:Só haverá necessidade de gostar ou não gostar. Eu não acho que precisa haver uma posição neutra porque isso é o mesmo que não votar? Curtir parece mais relevante para respostas de boletins do que postagens para ser honesto. Ou seja, eu vejo sua resposta e, em vez de escrever a minha, apenas concordo com você - o quadro de avisos existente é um aspecto social da organização e acho que gostar e não gostar / concordar e discordar cria um nível de controvérsia que incentiva a participação . No entanto, gostar ou não gostar de um boletim nem sempre é totalmente apropriado.
15.1 Fornecido:
Like
como booleano em BulletinRating
e ResponseRating
. Isso exigirá interpretação em cada acesso.15.2. Quando não é mais um booleano, pode ser alterado para um
RatingCode
, e implementado como uma tabela de pesquisa. Os nomes são então determinados por Joins e a interpretação é eliminada. Eu desenhei isso no First Data Model, para que você possa ver o que eu quis dizer15.3. Removido no Segundo Modelo de Dados..
16. Confirmado:cada usuário tem uma casa
Location
(além da lista de Locations
que eles estão interessados)..
17. Confirmado:
Permission
conforme (13)..
18. Confirmado:Permissões adicionais podem ser necessárias, conforme Modelo de Dados.
18.1. Se você fizer isso agora, não precisará se preocupar quando a organização decidir impedir que uma determinada
Person
de postar Responses
ou Bulletins
, ou Classificá-los; e quer que esse recurso seja implementado ontem. 18.2. Mesmo se você não implementá-lo, deixe lacunas entre os valores que você implementa.
.
19 Confirmado:um
Bulletin
é sobre um Location
. 19.1. Confirmado:não há
Bulletins
sem um Location
19.2. Confirmado:não há
Bulletins
sem um Location
. 19.3 Confirmado:Não há
Bulletins
sem um User
(declarativo). Mas até agora não temos como restringir esse User
; portanto, qualquer User
pode inserir um Bulletin
para qualquer Location
(você pode restringi-lo no código, por exemplo, para Locations
cada User Is Interested In
. 19.4 Confirmado:Não há
BulletinRatings
sem um Bulletin
e uma classificação de User
. 19.5 Confirmado:Não há
Responses
sem um Bulletin
. 19.4 Confirmado:Não há
ResponseRatings
sem uma Response
e uma classificação de User
. 19.7. Mas, pode haver
Users
, Locais, and
Categorias`, independentemente. .
20. Se você não se importar, fornecerei convenções de nomenclatura, etc. Elas devem ser autoexplicativas, e o valor aparecerá apenas quando você começar a codificar SQL. Por favor, pergunte, se alguma coisa não for. Para começar, todos os nomes são singulares. Mixed Case é mais fácil de ler (você deve usar maiúsculas para a linguagem SQL).
20.1. Minha experiência é table_name em oposição a tableName são realmente formulários técnicos, e os usuários não gostam deles; O caso misto consistente é apreciado por todos. É uma daquelas coisas que é impossível mudar, então escolha com cuidado.
.
21. Para sua necessidade de agrupar tabelas, o que é bom, lembre-se de que isso é um problema físico. No nível do Modelo de Dados Lógicos, as tabelas têm nomes normais, organizados por problemas físicos. Imagine que as tabelas físicas sejam prefixadas com algo como (e use maiúsculas para isso):
-
REF_
para referência (como Usuário) e tabelas de pesquisa-
BUL_
para o sistema Bulletin.
Não consigo nomear tabelas com letras maiúsculas? Não tenho certeza por quê. Não sei por que não posso ter nomes de tabelas em maiúsculas. Tem a ver com o uso de tabelas de banco de dados MyIsam?
.
22.
rank
(todos) podem ser derivados diretamente do banco de dados (lembre-se, não se preocupe com o código durante a modelagem de dados). Se você armazená-lo, é um erro de normalização; uma coluna duplicada; que deve ser mantido atualizado; que pode sair de sincronia com o valor derivado; que é chamado de Anomalia de Atualização. A quinta forma normal elimina as anomalias de atualização. Esse é o meu nível mínimo de Normalização, então é isso que você receberá de mim. 22.1. Não estou interferindo na ordem de classificação ou no problema de popularidade; na verdade, pelo que parece, você não fechou essa funcionalidade. Estou pegando apenas dados redundantes, a classificação coluna , out, como parte do processo de Normalização.
22.2. Aqui está um ▶Tutorial rápido◀ no operador RANK() (como é comumente conhecido). Não é ANSI SQL; é uma extensão Oracle e MS. No entanto, não é necessário se você entender Subconsultas, razão pela qual o Sybase não o possui. Eu duvido que o MySQL o tenha, então você precisa entender isso. Entender as subconsultas escalares é um pré-requisito. Sintaxe Sybase, então coloque seu ponto e vírgula, etc. Sinta-se à vontade para fazer perguntas específicas.
.
Eu nunca vi essa abordagem de escrever Rank =(SELECT.... o mesmo que (SELECT ...) como Rank?
.
22.3. Precisar entender o porquê, não é problema algum. Somente as crianças seguem cegamente regras simples, e você certamente não é uma delas.
.
23. Confirmado:
users.total_bulletins
é redundante; pode ser derivado. Removido..
24. Todos os seus PKs são Ids. Você ainda não se cansou de se perder no código? Esqueça de colar
Id
iot PKs em tudo o que se move, vamos descobrir como seus usuários Identificar as suas Entidades; quais Entidades são verdadeiramente Independentes, e outras que dependem de Entidades Independentes. 24.1. Nunca use
Id
ou qualquer forma. Onde for um PK, use o formulário completo. 24.2. Chame location_id, location_id, onde quer que esteja, incluindo a tabela PK. A exceção é quando você precisa mostrar a função. Isso ficará claro no Modelo de Dados.
.
25. Você não tem Integridade Referencial Declarativa, não Definido Chaves estrangeiras. Isso é uma má notícia por muitas razões diferentes. Uma vez que essas questões sejam esclarecidas, por favor, adicione-as. DRI significa que, tanto quanto possível, se não tudo, a Integridade é Declarada em SQL. O padrão ISO/IEC/ANSI SQL permite isso, mas o mercado freeware não fornece o padrão e está lentamente alcançando. Isso significa que o servidor não permitirá que uma linha na tabela FK seja adicionada, a menos que o PK exista na tabela pai. O MySQL recentemente forneceu DRI para Chaves Estrangeiras. Para FKs, consulte ▶ este artigo◀ .
25.1. Para restrições CHECK e RULES, você terá que implementá-las no código.
minhas chaves estrangeiras são como, users-id(fk) =users.id(pk) Não tenho certeza de como adicioná-las além do que fiz, mas certamente farei isso assim que souber.
Vinte e cinco. Comentários anotados. Eu não sou um especialista em MySQL. Sim, esses são os problemas que você tem que descobrir por si mesmo. Em geral, pela minha leitura, o MySQL não tem pernas; para qualquer coisa do tipo SQL, você precisa do InnoDB.
.
27. Dado:Repensei os requisitos de classificação para o boletim. Os usuários podem classificar cronologicamente com facilidade, faz sentido. Os usuários podem classificar os boletins pela data da última resposta ao boletim. Então podemos esquecer a classificação e deve ser realmente fácil classificar os boletins cronologicamente pela hora de sua última resposta? Quais são seus pensamentos.
Problemas em aberto
(Nada)
Modelo de dados
Ok, supondo que você não tenha problemas com o ERD e implementando todos os problemas encerrados, modelei os dados e preparei um quinto modelo de dados 09 dez 10 para sua revisão. Eu definitivamente preciso de muito mais feedback, perguntas, etc, sobre isso. Estou tendo dificuldade em aceitar que está feito. Provavelmente é melhor começar a escrever código real para suas áreas problemáticas.
Links
▶Link para IDEF1X Notation◀ Você realmente precisa ler e entender isso antes de ler o Modelo de Dados.
▶Link para dados do quinto boletim Modelo◀ O Diagrama de Relação de Entidade está na primeira página, seguido pelo Modelo de dados .
-
As Keys são praticamente IDEF1X diretas (exceto para UserId que eu forneci como contraponto); o que significa chaves relacionais de bolsa. Não aprimorado e não otimizado para considerações físicas. Antes de se esquivar deles, primeiro observe-os, registre-os e avalie-os. Claro que podemos adicionarId
iot keys, mas antes de fazermos isso, vamos nos certificar de que entendemos o que vamos perder.
-
Observe os identificadores (linhas sólidas) conforme o documento Notação. A coluna vertebral, as vértebras do sistema sãoLocation ... Bulletin ... Response
.
-
Observe que as Chaves realmente implementam muitas Regras de Negócios.
-
Observe a Hierarquia Natural que eu renderizei. Veja se há algum significado nisso para você.
-
As Frases Verbais são realmente importantes; ver se eles significam alguma coisa.
Comentários sobre o primeiro modelo de dados e respostas
Uma pergunta que tenho é que a chave primária do local será usada para formar a chave primária filha? (elas são unidas por uma linha sólida) Eu realmente não entendo esse conceito
- O que é um bom identificador para boletim? , o que seus usuários usam naturalmente para identificar um boletim...
- "você viu o boletim de Virginia FO ontem?",
- "Sally, de Washington, com certeza escreve bons boletins", etc.
ou por que essa relação não existe entre o usuário e o boletim?
-
Conforme a intenção declarada mais acima, já que agora mostrei o Rating como uma tabela e qual seria a renderização, uma vez, vou removê-la
-
Eu acho que a permissão deve ser uma entidade.
-
Bulletin
PK agora é(StateCode, Town, UserId, SequenceNo)
. Para ser claro,SequenceNo
está dentro deStateCode, Town, UserId
:serão 5 para o 5º boletim de Sally sobre MO/Billngs FO.
-
Observe que as configurações do usuárioBulletinsPerPage
,etc, são 1::1 comUser
, então eles estão emUser
; tabela filho estaria incorreta.
-
Erros tipográficos corrigidos.
Comentários sobre o segundo modelo de dados e respostas
- Os PKs para ambos os
Bulletin
eResponse
foram alterados para refletir (7).BulletinNo
eResponseNo
foram substituídos porBulletinDate
eResponseDate
(que costumava serCreatedDate
), para permitir várias respostas porUser
porBulletin
.
Comentários sobre o Terceiro Modelo de Dados e Respostas
Confie que você teve uma boa pausa.
-
Há pelo menos 30 anos (que eu saiba), os gigantes do setor tiveram esse debate. Os nomes são sempre singulares. Tabelas são substantivos. Frases verbais são verbos. Isso não se limita a convenções de nomenclatura de banco de dados, aplica-se a documentos, teses, dissertações, etc. Você pode ter 5 conclusões no final do documento, mas o título da seção ou capítulo, tanto no ToC quanto no topo da página é "Conclusão".
Depois de lutar contra eles durante toda a Uni, assim que comecei meu primeiro trabalho remunerado de programação e vi a importância das regras no mundo real, em oposição aos argumentos teóricos que tínhamos na faculdade, desisti disso como um desperdício de tempo. Todo o tempo e energia que desperdicei foram liberados para fazer um trabalho produtivo. Desde então, não questiono os gigantes; Eu apenas aceito. Que suas mentes são maiores que as minhas. É como aceitar Padrões, ou se comportar dentro da lei, ou de Deus. Não tenho razões muito, muito boas para fazer algo ilegal.
De qualquer forma, a facilidade de linguagem (discussão, SQL, documentação) que é suportada por tais regras não pode ser explicada adequadamente; à medida que você escreve mais e mais código SQL, isso ficará claro.
Você está sempre livre para usar o que quiser. Eu entrego apenas singular.
-
Tudo bem por mim.
Mas você precisa ter em mente que esses dois elementos, na sequência identificada (além do Índice Único não PK, ou Chave Alternativa) são universalmente necessários para estabelecer a Exclusividade de uma Pessoa. Removê-los resultará em duas coisas. Primeiro, você não poderá mais identificar a exclusividade entreUsers
(e, portanto, você pode ter linhas duplicadas). Em segundo lugar, o AK torna-se não exclusivo, uma entrada de inversão.
-
O ponto é (ao contrário de uma das postagens), qualquer coluna que seja 1::1 com oUser
PK, deve residir emUser
. Todas as configurações de preferência. Como limpamos osInterestedLocations
eInterestedCategories
, conheço apenasBulletinsPerPage
remanescente; mas tenho certeza que existem outros.IsPreference2
é um ex. de um booleano;NumPreference3
é um ex. de um inteiro. Etc. Você pode me dizer quais são as preferências reais.
(Vamos tentar isso no plural:... qualquer coluna que seja 1::1 com oUsers
PK, deve residir emUsers
. Só não faz isso por mim, fico preso ao inglês quebrado e sou um pouco precioso sobre minha língua materna.)
Modelo de dados atualizado.
-
Excelente. Deixe-me saber quando você estiver confortável com isso, e eu lhe darei o Modelo Físico.
Como sobre as frases verbais?
Comentários de 06 de dezembro de 10 20:38 EST (pequenas atualizações)
.
28. Onde houver apenas uma ocorrência de PK como FK, é claro, o nome da coluna FK é o mesmo que o nome da coluna PK. No entanto, quando houver mais de um occ do FK (dê uma olhada em
ResponseRating
), existem três UserIds
), precisamos diferenciá-los. Na terminologia IDEF1X, isso é chamado de Funções. A função do User
quem emitiu o Bulletin
é Issuer
, e assim por diante. Obviamente, é melhor usar esse nome e mantê-lo consistente em toda a hierarquia (não UserId
em Bulletin
e quando chegarmos a Response
, onde há dois e uma diferenciação é exigida, altere-a para IssuerId
. Achei que você poderia ter um problema com isso; nos estágios iniciais, o uso é Issuer.UserId
para que fique absolutamente claro que é UserId
como um FK, e a função é Issuer
; quando chegamos ao modelo físico, ele é simplificado para IssuerId
. Da mesma forma, temos muitas colunas DateTime (Date para abreviar, se preferir; caso contrário, Dtm), que precisam ser diferenciadas.
.
29. A notação IDEF1X não fazia sentido?
- O PK de cada tabela está acima da linha, na ordem especificada.
- Lembre-se de que estamos carregando os PKs das tabelas pai de qualquer maneira e, se houver significado, usando esses FKs para formar o PK filho.
-
ParaBulletin
:
- O local FK
(StateCode, Town)
para o qual é emitido - O
UserId
do Emissor - e DateTime em que foi emitido, para torná-lo único.
- portanto (StateCode, Town, IssuerId, BulletinDate)`
- O local FK
-
Para excluir todos osResponseRatings
para esteBulletin
, useWHERE =
nesses quatroBulletin
colunas.
.
30. Porque
(State, Town)
é o PK de Location
, levando para qualquer lugar. E faz parte do Bulletin
PK, então quaisquer tabelas dependentes carregam essas colunas porque elas carregam o Bulletin
PK. Identificamos anteriormente que
(State, Town)
é o PK, .
33. Vale a discussão. Sim, se você for exibi-lo quando (por exemplo) exibir
Responses
, e os usuários entendem UserName
. Não, se for de 30 bytes e também houver um UserId
exclusivo de 4 bytes . A ideia é fazer essas escolhas conscientemente, ciente do que você está abrindo mão, quando você finalmente decidir que uma chave de 6 colunas e 30 bytes é muito complicada para migrar para os filhos. - Afirmei no início que usaria
UserId
como umId
típico Pk, porque é transportado/migrado para várias tabelas filhas. - Podemos deixar como isso é criado para mais tarde. Mas é um PK substituto puro.
.
34. Sem problemas.
Category
já tem. Vou alterar Order
para ListOrder
. .
35. Certo. Com base no que li e ouvi, estou muito feliz com isso. Mas eu gostaria de mais idas e vindas para obter alguma confiança, antes de escrever o código. Como alternativa, veja-o como uma experiência de aprendizado e aceite que o modelo e o código podem ser alterados posteriormente. Você gostaria que eu produzisse o Físico agora? Se você me der todas e quaisquer correções, publicarei a próxima versão. Estou esperando preferências em
User
. Além disso, execute rapidamente as funções e verifique se você tem todas as colunas necessárias. Olhe para algumas das outras respostas, para fins de aprendizado e interesse.
.
36. Juntas. Você acabou de entrar em
Modelo de dados atualizado.
.
Comentários sobre 08 dez 10 20:49, Quarto modelo de dados e respostas
.
Verifique a seção anterior imediatamente acima, existem pequenas atualizações.
IDEF1X:Sua velocidade está boa.
Observe a criança sempre "herda" o PK Pai, como um FK (linha sólida ou quebrada), caso contrário não há Relação entre eles. Ao usar essas colunas que existem no filho de qualquer maneira, para formar o PK filho, carregamos o significado (e essa é a diferença entre sólido e quebrado). E assim não precisamos procurar um Identificador independente para a criança. O poder relacional neste método ficará claro mais tarde, quando você estiver codificando.
A seção com a qual estamos lidando é sobre Identificadores :natural vs não natural; significativo vs sem sentido. Mais tarde, você verá como podemos usar a capacidade relacional do mecanismo, quando o PK filho é formado a partir do PK pai. (O seu sobrenome não é o mesmo do seu pai?)
Também é importante entender os bancos de dados relacionais e sua capacidade. Isso se perde quando abordamos o banco de dados (por exemplo) de uma perspectiva OO e o tratamos como um local para tornar nossas classes "persistentes". Portanto, tentaremos aprender e usar termos relacionais. Fica difícil quando você vai à França e espera que eles falem americano e usem a mesma moeda; aprenda a falar 10 palavras em francês, e eles te recebem de braços abertos, e você terá uma experiência bem diferente com os locais.
De qualquer forma, vá em frente com a implementação do modelo. Apenas perceba que provavelmente faremos uma mudança em algum momento. Salve todo o seu DDL. Salve todos os seus dados de teste como instruções de inserção ou como um backup de tabela ou exportação de formato de caractere (não faço ideia do que o MySQL pode/não pode fazer nesta área).
37.1. Manipulada, a Relação n::n com
Office
&Category
. Você só vai "ver" isso quando chegarmos ao Modelo Físico. 37.2. Feito.
37.3 Concluído.
.
38. Excelente. Mais curto também. Observe que eles nunca poderão ter dois
Offices
no mesmo CEP. NUMERIC(5,0) é bom, mas achei que os EUA estavam caminhando para 7 dígitos. Não importa, você pode descobrir; é um excelente PK para Office
. Agora esta coluna, que fazia parte de Address
, provavelmente ZipCode
, foi elevado a um propósito maior, sem duplicação; já que estamos carregando em 5 tabelas filhas, e queremos que o nome do PK seja claro, conforme as convenções explicadas anteriormente, vamos chamá-lo de OfficeCode
; OfficeZipCode
pode ser bobo. Precisamos de um índice exclusivo em
Name
para garantir que eles não adicionem dois Offices
com o mesmo nome. Observe que, para fins de explicação, esta é na verdade a chave lógica do Office
, substituindo (StateCode, Town)
, e continua assim. Ainda acho que você pode precisar de
StateCode
e Town
como uma referência rápida (além de estar em algum lugar em Address
) Modelo de dados atualizado, quinto agora disponível para revisão. Você não informou sua preferência, para
...Date
vs ...Dtm
. Estou indo com o último, por ser mais específico, identificando também o componente do tempo. Fácil de mudar. Esta resposta atingiu o tamanho máximo. Continua na "Parte II"