Minha formação é mais em programação da Web do que em administração de banco de dados; portanto, corrija-me se estiver usando a terminologia errada aqui. Estou tentando descobrir a melhor maneira de projetar o banco de dados para um aplicativo que codificarei.
A situação: eu tenho relatórios em uma tabela e recomendações em outra tabela. Cada relatório pode ter muitas recomendações. Eu também tenho uma tabela separada para palavras-chave (para implementar a marcação). No entanto, quero ter apenas um conjunto de palavras-chave que sejam aplicadas aos Relatórios e às Recomendações, para que a pesquisa em palavras-chave forneça Relatórios e Recomendações como resultados.
Aqui está a estrutura com a qual comecei:
Reports
----------
ReportID
ReportName
Recommendations
----------
RecommendationID
RecommendationName
ReportID (foreign key)
Keywords
----------
KeywordID
KeywordName
ObjectKeywords
----------
KeywordID (foreign key)
ReportID (foreign key)
RecommendationID (foreign key)
Instintivamente, acho que isso não é o ideal e que meus objetos taggable sejam herdados de um pai comum e que esse pai de comentário seja marcado, o que daria a seguinte estrutura:
BaseObjects
----------
ObjectID (primary key)
ObjectType
Reports
----------
ObjectID_Report (foreign key)
ReportName
Recommendations
----------
ObjectID_Recommendation (foreign key)
RecommendationName
ObjectID_Report (foreign key)
Keywords
----------
KeywordID (primary key)
KeywordName
ObjectKeywords
----------
ObjectID (foreign key)
KeywordID (foreign key)
Devo ir com esta segunda estrutura? Estou perdendo alguma preocupação importante aqui? Além disso, se eu for com o segundo, o que devo usar como um nome não genérico para substituir "Objeto"?
Atualizar:
Estou usando o SQL Server para este projeto. É um aplicativo interno com um pequeno número de usuários não concorrentes, por isso não prevejo uma carga alta. Em termos de uso, as palavras-chave provavelmente serão usadas com moderação. É basicamente apenas para fins de relatórios estatísticos. Nesse sentido, qualquer solução que eu escolher provavelmente afetará apenas os desenvolvedores que precisarão manter esse sistema abaixo da linha ... mas achei que seria bom implementar boas práticas sempre que possível. Obrigado por toda a visão!
fonte
Respostas:
O problema com o seu primeiro exemplo é a tabela de três links. Isso exigirá que uma das chaves estrangeiras no relatório ou nas recomendações seja sempre NULL, para que as palavras-chave vinculem apenas uma maneira ou de outra?
No caso do seu segundo exemplo, a união da base às tabelas derivadas agora pode exigir o uso do seletor de tipo ou LEFT JOINs, dependendo de como você o faz.
Dado isso, por que não explicitar e eliminar todos os NULLs e LEFT JOINs?
Nesse cenário, quando você adiciona outra coisa que precisa ser marcada, basta adicionar a tabela de entidades e a tabela de ligação.
Em seguida, seus resultados de pesquisa terão a seguinte aparência (veja se ainda existe uma seleção de tipos e os transforma em genéricos no nível de resultados do objeto, se você deseja uma única lista de resultados):
Não importa o que aconteça, em algum lugar haverá seleção de tipo e algum tipo de ramificação acontecendo.
Se você observar como faria isso em sua opção 1, é semelhante, mas com uma instrução CASE ou LEFT JOINs e um COALESCE. À medida que você expande sua opção 2 com mais itens vinculados, é necessário continuar adicionando mais LEFT JOINs onde normalmente não são encontrados itens (um objeto vinculado pode ter apenas uma tabela derivada válida).
Não acho que exista algo fundamentalmente errado com a sua opção 2, e você pode fazer com que pareça com esta proposta com o uso de visualizações.
Na sua opção 1, tenho algumas dificuldades para entender por que você optou pela tabela de três links.
fonte
Primeiro, observe que a solução ideal depende, em certa medida, de qual RDBMS você usa. Vou dar então a resposta padrão e a específica do PostgreSQL.
Resposta normalizada e padrão
A resposta padrão é ter duas tabelas de junção.
Suponha que tenhamos nossas tabelas:
Essa abordagem segue todas as regras de normalização padrão e não quebra os princípios tradicionais de normalização de banco de dados. Deve funcionar em qualquer RDBMS.
Resposta específica do PostgreSQL, design N1NF
Primeiro, uma palavra sobre por que o PostgreSQL é diferente. O PostgreSQL suporta várias maneiras muito úteis de usar índices sobre matrizes, principalmente usando o que é conhecido como índices GIN. Isso pode beneficiar bastante o desempenho se usado corretamente aqui. Como o PostgreSQL pode "acessar" os tipos de dados dessa maneira, a suposição básica de atomicidade e normalização é um tanto problemática para aplicar rigidamente aqui. Portanto, por esse motivo, minha recomendação seria quebrar a primeira regra de atomicidade da forma normal e confiar nos índices GIN para obter melhor desempenho.
Uma segunda observação aqui é que, embora isso ofereça melhor desempenho, ele adiciona algumas dores de cabeça, porque você terá algum trabalho manual para fazer para que a integridade referencial funcione corretamente. Portanto, a desvantagem aqui é o desempenho do trabalho manual.
Agora temos que adicionar gatilhos para garantir que as palavras-chave sejam gerenciadas corretamente.
Em segundo lugar, temos que decidir o que fazer quando uma palavra-chave é removida. Como está agora, uma palavra-chave removida da tabela de palavras-chave não entrará em cascata nos campos de palavras-chave. Talvez isso seja desejável e talvez não. A coisa mais simples a fazer é restringir sempre a exclusão e esperar que você lide manualmente com esse caso, se ele aparecer (use um gatilho para segurança aqui). Outra opção pode ser reescrever todos os valores de palavras-chave onde a palavra-chave existe para removê-la. Novamente, um gatilho seria a maneira de fazer isso também.
A grande vantagem desta solução é que você pode indexar pesquisas muito rápidas por palavra-chave e pode puxar todas as tags sem uma associação. A desvantagem é que remover uma palavra-chave é uma dor e não terá um bom desempenho, mesmo em um bom dia. Isso pode ser aceitável porque é um evento raro e pode ser enviado para um processo em segundo plano, mas é uma troca que vale a pena entender.
Criticando sua primeira solução
O verdadeiro problema com sua primeira solução é que você não possui uma chave possível no ObjectKeywords. Consequentemente, você tem um problema no qual não pode garantir que cada palavra-chave seja aplicada a cada objeto apenas uma vez.
Sua segunda solução é um pouco melhor. Se você não gostar das outras soluções oferecidas, sugiro que siga com ela. No entanto, eu sugeriria se livrar do keyword_id e apenas se juntar ao texto da palavra-chave. Isso elimina uma junção sem desnormalização.
fonte
Eu sugeriria duas estruturas separadas:
Dessa forma, você não possui todos os IDs de entidade possíveis na mesma tabela (o que não é muito escalável e pode ser confuso) e não possui uma tabela com um "id de objeto" genérico que você precisa desambiguar em outro lugar usando a
base_object
tabela, que funcionará, mas acho que complica demais o design.fonte
BaseObjects
tabela na minha primeira leitura e pensei que estava vendo uma descrição para uma tabela ondeobject_id
pode apontar para um ID em qualquer tabela.Na minha experiência, é isso que você pode fazer.
E para a relação entre palavras-chave, relatórios e recomendações, você pode executar uma das duas opções: Opção A:
Isso permite uma relação direta entre Relatórios e Recomendações, Palavras-chave e, finalmente, Palavras-chave. Opção B:
A opção A é mais fácil de aplicar e gerenciar, uma vez que terá as constratints do banco de dados para lidar com a integridade dos dados e não permitirá a inserção de dados inválidos.
A opção B ainda requer um pouco mais de trabalho, pois você precisará codificar a identificação do relacionamento. É mais flexível a longo prazo, se por acaso, em algum momento no futuro, você precisar adicionar palavras-chave a outro item que não seja o relatório ou a recomendação, basta adicionar a identificação e usar diretamente a tabela.
fonte