Como armazenar dados históricos


162

Eu e alguns colegas de trabalho entramos em um debate sobre a melhor maneira de armazenar dados históricos. Atualmente, para alguns sistemas, uso uma tabela separada para armazenar dados históricos e mantenho uma tabela original para o registro ativo atual. Então, digamos que eu tenho a tabela FOO. No meu sistema, todos os registros ativos entrarão no FOO e todos os registros históricos entrarão no FOO_Hist. Muitos campos diferentes no FOO podem ser atualizados pelo usuário, por isso quero manter uma conta precisa de tudo atualizado. FOO_Hist contém exatamente os mesmos campos que FOO, com exceção de um HIST_ID de incremento automático. Cada FOO tempo é atualizado, eu executar uma instrução de inserção em FOO_Hist semelhante a: insert into FOO_HIST select * from FOO where id = @id.

Meu colega de trabalho diz que esse design é ruim porque não devo ter uma cópia exata de uma tabela por motivos históricos e apenas inserir outro registro na tabela ativa com um sinalizador indicando que é para fins históricos.

Existe um padrão para lidar com o armazenamento de dados históricos? Parece-me que não quero bagunçar meus registros ativos com todos os meus registros históricos na mesma tabela, considerando que pode haver mais de um milhão de registros (estou pensando a longo prazo).

Como você ou sua empresa lida com isso?

Estou usando o MS SQL Server 2008, mas gostaria de manter a resposta genérica e arbitrária de qualquer DBMS.

Respostas:


80

O suporte a dados históricos diretamente em um sistema operacional tornará seu aplicativo muito mais complexo do que seria. Geralmente, eu não recomendaria fazê-lo, a menos que você tenha um requisito difícil de manipular versões históricas de um registro dentro do sistema.

Se você observar atentamente, a maioria dos requisitos para dados históricos se enquadra em uma de duas categorias:

  • Log de auditoria: é melhor fazer isso com tabelas de auditoria. É bastante fácil escrever uma ferramenta que gera scripts para criar tabelas e gatilhos de log de auditoria lendo os metadados do dicionário de dados do sistema. Esse tipo de ferramenta pode ser usado para modernizar o log de auditoria na maioria dos sistemas. Você também pode usar esse subsistema para captura de dados alterados se desejar implementar um armazém de dados (veja abaixo).

  • Relatórios históricos: relatórios sobre o estado histórico, posições 'as-at' ou relatórios analíticos ao longo do tempo. Pode ser possível atender a requisitos simples de relatórios históricos consultando as tabelas de log de auditoria do tipo descrito acima. Se você tiver requisitos mais complexos, pode ser mais econômico implementar um data mart para os relatórios do que tentar integrar o histórico diretamente no sistema operacional.

    As dimensões que mudam lentamente são de longe o mecanismo mais simples para rastrear e consultar o estado histórico e grande parte do rastreamento pode ser automatizada. Manipuladores genéricos não são tão difíceis de escrever. Geralmente, os relatórios históricos não precisam usar dados atualizados, assim um mecanismo de atualização em lote normalmente é bom. Isso mantém a arquitetura do sistema principal e de relatórios relativamente simples.

Se seus requisitos se enquadram em uma dessas duas categorias, provavelmente é melhor você não armazenar dados históricos em seu sistema operacional. Separar a funcionalidade histórica em outro subsistema provavelmente exigirá menos esforço geral e produzirá bancos de dados transacionais e de auditoria / relatório que funcionam muito melhor para a finalidade pretendida.


Eu acho que vejo o que você está dizendo. Então, o que eu fiz com minha tabela FOO_Hist foi realmente criar uma tabela de auditoria. Em vez de usar um gatilho para inserir na tabela de auditoria na atualização, apenas executei uma instrução no programa. Isso está correto?
Aaron

6
Bastante. É melhor fazer esse tipo de log de auditoria com gatilhos; os gatilhos garantem que quaisquer alterações (incluindo correções manuais de dados) sejam registradas nos logs de auditoria. Se você tiver mais de 10 a 20 tabelas para auditar, provavelmente será mais rápido criar uma ferramenta geradora de gatilhos. Se o tráfego de disco para os logs de auditoria for um problema, você poderá colocar as tabelas de log de auditoria em um conjunto separado de discos.
ConcernedOfTunbridgeWells

Sim, eu concordo 100% com isso. Obrigado.
Aaron

40

Não acho que exista uma maneira padrão específica de fazê-lo, mas pensei em lançar um método possível. Trabalho no Oracle e em nossa estrutura interna de aplicativos da web que utiliza XML para armazenar dados de aplicativos.

Usamos algo chamado modelo Master - Detail que, no mais simples, consiste em:

Tabela mestre, por exemplo, chamada Widgetsfrequentemente apenas contendo um ID. Geralmente contém dados que não mudam com o tempo / não são históricos.

Tabela Detalhe / Histórico, por exemplo, chamada Widget_Detailscontendo pelo menos:

  • ID - chave primária. ID do detalhe / histórico
  • MASTER_ID - por exemplo, neste caso chamado 'WIDGET_ID', este é o FK do registro mestre
  • START_DATETIME - registro de data e hora indicando o início dessa linha do banco de dados
  • END_DATETIME - registro de data e hora indicando o final dessa linha do banco de dados
  • STATUS_CONTROL - coluna de caractere único indica o status da linha. 'C' indica atual, NULL ou 'A' seria histórico / arquivado. Só usamos isso porque não podemos indexar se END_DATETIME é NULL
  • CREATED_BY_WUA_ID - armazena o ID da conta que causou a criação da linha
  • XMLDATA - armazena os dados reais

Então, essencialmente, uma entidade começa com 1 linha no mestre e 1 linha nos detalhes. Os detalhes com uma data de término NULL e STATUS_CONTROL de 'C'. Quando uma atualização ocorre, a linha atual é atualizada para ter END_DATETIME do horário atual e status_control é definido como NULL (ou 'A', se preferir). Uma nova linha é criada na tabela de detalhes, ainda vinculada ao mesmo mestre, com status_control 'C', o ID da pessoa que está fazendo a atualização e os novos dados armazenados na coluna XMLDATA.

Essa é a base do nosso modelo histórico. A lógica Criar / Atualizar é tratada em um pacote Oracle PL / SQL, para que você simplesmente transmita a função o ID atual, seu ID de usuário e os novos dados XML e, internamente, faça toda a atualização / inserção de linhas para representar isso no modelo histórico . Os horários de início e de término indicam quando essa linha da tabela está ativa.

O armazenamento é barato, geralmente não EXCLUEM dados e preferimos manter uma trilha de auditoria. Isso nos permite ver como eram os nossos dados a qualquer momento. Ao indexar status_control = 'C' ou usar uma View, a desordem não é exatamente um problema. Obviamente, suas consultas precisam levar em consideração que você sempre deve usar a versão atual (NULL end_datetime e status_control = 'C') de um registro.


Oi Chris, se você fizer isso, o ID (chave primária) deve ser alterado, certo? que tal o relacional com outra tabela se for usado por outras?
projo

@projo, o ID na sua tabela mestre é o PK e, conceitualmente, o "PK" para qualquer conceito com o qual você esteja lidando. O ID na tabela de detalhes é o PK para identificar uma versão histórica para o mestre (que é outra coluna nos detalhes). Ao formar relacionamentos, você costuma referenciar a verdadeira PK do seu conceito (ou seja, o ID na sua tabela mestre ou a coluna MASTER_ID nos seus detalhes) e usar STATUS_CONTROL = 'C' para garantir que está obtendo a versão atual. Como alternativa, você pode fazer referência ao ID de detalhe para relacionar algo a um momento específico.
Chris Cameron-Mills

+1 Eu implementei esse padrão com grande sucesso em vários projetos grandes.
Three Value Logic

Estamos usando a mesma abordagem. Mas agora estou pensando se é melhor armazenar apenas START_DATETIME e não armazenar END_DATETIME
bat_ventzi

Algumas variações na minha experiência. Se sua entidade for "finalizada", ou seja, arquivada ou excluída, você poderá efetivamente não ter registros detalhados com controle de status 'C', ou seja, nenhuma linha atual, embora você não saiba quando isso aconteceu. Como alternativa, você pode definir um end_datetime na linha final e a presença de uma linha 'final' 'C' pode indicar que a entidade está agora excluída / arquivada. Por fim, você pode representá-lo por meio de outra coluna, STATUS, que você provavelmente já possui.
Chris Cameron-Mills

15

Eu acho que você se aproxima está correto. A tabela histórica deve ser uma cópia da tabela principal sem índices. Verifique também se você possui um registro de data e hora de atualização na tabela.

Se você tentar a outra abordagem em breve, enfrentará problemas:

  • despesas gerais de manutenção
  • mais sinalizadores em seleciona
  • desaceleração de consultas
  • crescimento de tabelas, índices

7

No SQL Server 2016 e acima , há um novo recurso chamado Tabelas Temporais que visa solucionar esse desafio com o mínimo esforço do desenvolvedor . O conceito de tabela temporal é semelhante ao Change Data Capture (CDC), com a diferença de que a tabela temporal abstraiu a maioria das coisas que você precisava fazer manualmente se estivesse usando o CDC.




1

Só queria adicionar uma opção que eu comecei a usar porque eu uso o Azure SQL e a coisa de várias tabelas era muito complicada para mim. Adicionei um gatilho de inserção / atualização / exclusão na minha tabela e converti a alteração antes / depois para json usando o recurso "FOR JSON AUTO".

 SET @beforeJson = (SELECT * FROM DELETED FOR JSON AUTO)
SET @afterJson = (SELECT * FROM INSERTED FOR JSON AUTO)

Isso retorna uma representação JSON para o registro antes / após a alteração. Em seguida, guardo esses valores em uma tabela de histórico com um carimbo de data e hora de quando a alteração ocorreu (também guardo o ID para o registro atual de preocupação). Usando o processo de serialização, posso controlar como os dados são preenchidos no caso de alterações no esquema.

Eu aprendi sobre isso neste link aqui


0

Você poderia apenas particionar as tabelas não?

"Estratégias de tabela e índice particionadas usando o SQL Server 2008 Quando uma tabela de banco de dados cresce em tamanho para centenas de gigabytes ou mais, pode ser mais difícil carregar novos dados, remover dados antigos e manter índices. Apenas o tamanho da tabela faz com que essas operações demorem muito mais tempo. Mesmo os dados que devem ser carregados ou removidos podem ser muito dimensionáveis, tornando impraticáveis ​​as operações INSERT e DELETE na tabela. O software de banco de dados Microsoft SQL Server 2008 fornece particionamento de tabela para tornar essas operações mais gerenciáveis. "


Sim, posso particionar as tabelas, mas esse é o padrão ao lidar com dados históricos? Os dados históricos devem ser incluídos na mesma tabela que os dados ativos? Estas são as perguntas que eu queria discutir. Isso também não é arbitrária no que se refere ao SQL Server 2008.
Aaron

0

A verdadeira questão é: você precisa usar dados históricos e ativos para relatórios? Nesse caso, mantenha-os em uma tabela, particione e crie uma exibição para que os registros ativos sejam usados ​​em consultas ativas. Se você precisar apenas examiná-los ocasionalmente (para pesquisar questões de conteúdo ou algo do tipo), coloque-as em uma tabela separada.


2
É mais difícil JOINduas tabelas em alguns relatórios históricos ou é mais difícil modificar cada inserção / atualização / exclusão de uma única tabela para estar ciente de preocupações históricas? Na verdade, um log de auditoria incluiria até os dados atuais na tabela de histórico, portanto, a tabela atual nem deveria ser necessária em um relatório.

0

Outra opção é arquivar os dados operacionais diariamente [a cada hora | a qualquer hora]. A maioria dos mecanismos de banco de dados suporta a extração dos dados em um arquivo morto .

Basicamente, a idéia é criar um trabalho agendado para Windows ou CRON que

  1. determina as tabelas atuais no banco de dados operacional
  2. seleciona todos os dados de todas as tabelas em um arquivo CSV ou XML
  3. compacta os dados exportados para um arquivo ZIP, de preferência com o registro de data e hora da geração no nome do arquivo para facilitar o arquivamento.

Muitos mecanismos de banco de dados SQL vêm com uma ferramenta que pode ser usada para esse fim. Por exemplo, ao usar o MySQL no Linux, o seguinte comando pode ser usado em um trabalho CRON para agendar a extração:

mysqldump --all-databases --xml --lock-tables=false -ppassword | gzip -c | cat > /media/bak/servername-$(date +%Y-%m-%d)-mysql.xml.gz

2
Isso não é de todo apropriado para dados históricos, porque se alguém altera um valor e o altera novamente dentro do ciclo de arquivamento, essas atualizações são perdidas. Também não há uma maneira fácil de analisar as alterações em uma entidade ao longo do tempo ou restaurar parcialmente uma entidade.
precisa saber é o seguinte

0

Conheço este post antigo, mas Só queria acrescentar alguns pontos. O padrão para esses problemas é o que funciona melhor para a situação. é muito importante compreender a necessidade desse armazenamento e o uso potencial dos dados históricos / de auditoria / controle de alterações.

Auditoria (finalidade de segurança) : use uma tabela comum para todas as suas tabelas auditáveis. defina a estrutura para armazenar os campos nome da coluna, antes e depois do valor.

Arquivar / histórico : para casos como rastrear endereço anterior, número de telefone etc. criar uma tabela separada FOO_HIST é melhor se o esquema da tabela de transações ativas não mudar significativamente no futuro (se a tabela de histórico tiver a mesma estrutura). se você antecipar a normalização da tabela, alteração / adição de tipos de dados / remoção de colunas, armazene seus dados históricos no formato xml. defina uma tabela com as seguintes colunas (ID, Data, Versão do Esquema, XMLData). isso manipulará facilmente as alterações de esquema. mas você precisa lidar com o xml e isso pode introduzir um nível de complicação na recuperação de dados.



Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.