Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Quadro de avisos - Otimização de banco de dados

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

  1. 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.
      .
  2. users.bb_categories_csv é uma relação muitos-para-muitos entre usuários e categorias
    • Idem.
      .

  3. 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

  4. Excluído.

  5. 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.

  6. Excluído.

  7. 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 em User, 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 adicionar Id 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ão Location ... 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 de StateCode, Town, UserId :serão 5 para o 5º boletim de Sally sobre MO/Billngs FO.

  • Observe que as configurações do usuário BulletinsPerPage ,etc, são 1::1 com User , então eles estão em User; tabela filho estaria incorreta.

  • Erros tipográficos corrigidos.

Comentários sobre o segundo modelo de dados e respostas

  • Os PKs para ambos os Bulletin e Response foram alterados para refletir (7). BulletinNo e ResponseNo foram substituídos por BulletinDate e ResponseDate (que costumava ser CreatedDate ), para permitir várias respostas por User por Bulletin .

Comentários sobre o Terceiro Modelo de Dados e Respostas


Confie que você teve uma boa pausa.

  1. 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.

  2. 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 entre Users (e, portanto, você pode ter linhas duplicadas). Em segundo lugar, o AK torna-se não exclusivo, uma entrada de inversão.

  3. O ponto é (ao contrário de uma das postagens), qualquer coluna que seja 1::1 com o User PK, deve residir em User . Todas as configurações de preferência. Como limpamos os InterestedLocations e InterestedCategories , conheço apenas BulletinsPerPage 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 o Users PK, deve residir em Users . 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.

  4. 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.

  • Para Bulletin :
    • 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)`

  • Para excluir todos os ResponseRatings para este Bulletin , use WHERE = nesses quatro Bulletin 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, vou deixar como está Consulte (38) para alterar.

.
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 um Id 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 quatro três colunas em vez de uma. SQL é complicado com junções, e a nova sintaxe que deveria facilitar, na verdade é mais complicada. Meus codificadores nunca escrevem joins:economizamos tempo e erros de digitação. Tenho um proc que dado duas ou mais tabelas, irá gerar o código com todas as colunas e joins. Eu não conheço o suficiente do MySQL para converter isso para você.

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"