Question

J'ai été chargé de développer une solution permettant de suivre les modifications apportées à une base de données.

Pour les mises à jour, j'ai besoin de capturer:

  • date de mise à jour
  • ancienne valeur
  • nouvelle valeur
  • champ concerné
  • personne faisant le changement
  • identifiant d'enregistrement
  • l'enregistrement de la table est dans

Pour les suppressions:

  • date de suppression
  • personne en train de supprimer
  • Titre / description / identifiant de l'enregistrement supprimé. Les tables sur lesquelles je suis en train de suivre les modifications ont toutes un titre ou un champ de description. Je voudrais capturer cela avant que l'enregistrement soit supprimé.
  • l'enregistrement de la table était dans

Pour les insertions:

  • date d'insertion
  • personne faisant le changement
  • identifiant d'enregistrement
  • l'enregistrement de la table est dans

J'ai pensé à plusieurs façons de le faire:

  • J'utilise des procédures stockées pour toutes les mises à jour / suppressions / insertions. Je créerais un générique & Quot; tracking & Quot; table. Il y aurait suffisamment de champs pour capturer toutes les données. Je voudrais ensuite ajouter une autre ligne dans chaque proc stocké à l'effet de & "Insérer un enregistrement dans la table de suivi &";
    • inconvénient: toutes les mises à jour / suppressions / insertions sont toutes mélangées dans la même table
    • beaucoup de champs NULLed
    • comment suivre les mises à jour / suppressions / insertions par lots? < ---- cela pourrait ne pas être un problème. Je ne fais pas vraiment quelque chose comme ça dans l'application.
    • comment capturer l'utilisateur qui effectue la mise à jour. La base de données ne voit qu'un compte.
    • éditez beaucoup de code existant à éditer.
  • Enfin, je pourrais créer un déclencheur appelé après les mises à jour / suppressions / insertions. Beaucoup des mêmes inconvénients que la première solution, sauf que je devrais éditer autant de code. Je ne sais pas comment je pourrais suivre les mises à jour. Il ne semble pas qu'il soit possible d'utiliser les déclencheurs pour afficher les enregistrements mis à jour récemment.

J'utilise asp.net, C #, SQL Server 2005, IIS6, Windows 2003. Je n'ai pas de budget; malheureusement, je ne peux rien acheter pour m'aider.

Merci pour vos réponses!

Était-ce utile?

La solution

Un déclencheur ne disposerait pas de toutes les informations dont vous avez besoin pour de nombreuses raisons, mais aucun identifiant d'utilisateur n'est décisif.

Je dirais que vous êtes sur la bonne voie avec un sp commun à insérer chaque fois qu'un changement est effectué. Si vous normalisez les sp pour vos interfaces, vous êtes en avance sur le jeu. Il sera difficile d'insérer un changement qui ne fait pas l'objet d'un suivi.

Considérez ceci comme l’équivalent d’une trace d’audit dans une application comptable - c’est le Journal - une table unique avec chaque transaction enregistrée. Ils ne mettraient pas en place des journaux séparés pour les dépôts, les retraits, les ajustements, etc., et c'est le même principe.

Autres conseils

Je n'aime pas détourner le problème et je sais que vous n'avez pas de budget, mais la solution la plus simple consiste à effectuer une mise à niveau vers SQL Server 2008. Cette fonctionnalité intégré . Je pensais que cela devrait au moins être mentionné pour toute autre personne qui tombe sur cette question, même si vous ne pouvez pas l’utiliser vous-même.

(Parmi les éditions déployables de SQL 2008, cette fonctionnalité est uniquement disponible dans Enterprise.)

Je vous suggère d'utiliser 2 colonnes dans chaque tableau. nomme rowhistory et IsDeleted et le type de données sera xml et bit. Ne supprimez jamais les lignes, utilisez toujours l'indicateur IsDeleted . Maintenant, allez avec les déclencheurs de mise à jour. Je vais vous donner un exemple pour le même J'ai cette table appelée Page

    CREATE TABLE te_Page([Id] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](200) NOT NULL, [Description] [varchar](200) NULL,[CreatedBy] [uniqueidentifier] NULL, [CreatedDate] [datetime] NOT NULL, [UpdatedBy] [uniqueidentifier] NULL, [UpdatedDate] [datetime] NULL, [IsDeleted] [bit] NULL, [RowHistory] [xml] NULL, CONSTRAINT [PK_tm_Page] PRIMARY KEY CLUSTERED ([Id] ASC )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY] ) ON [PRIMARY]

Maintenant, après avoir créé le tableau, il vous suffit de copier / coller le code ci-dessous et votre tâche est terminée pour le tableau de pages. Il commencera à enregistrer l'historique de la ligne dans la même ligne qui est mise à jour avec les anciennes et les nouvelles valeurs.

                ALTER Trigger [dbo].[Trg_Te_Page]    
        On [dbo].[te_Page]                
        After Update                
        As                 
        --If @@rowcount = 0 Or Update(RowHistory)    
        --Return    

        Declare @xml NVARCHAR(MAX)     
        Declare @currentxml NVARCHAR(MAX)   
        Declare @node NVARCHAR(MAX)    
        Declare @ishistoryexists XML    

        Declare @FormLineAttributeValueId int  

        -- new Values  
        Declare @new_Name varchar(200)  
        Declare @new_Description varchar(200)  

        Declare @new_CreatedBy UNIQUEIDENTIFIER    
        Declare @new_CreatedDate DATETIME    
        Declare @new_UpdatedBy UNIQUEIDENTIFIER    
        Declare @new_UpdatedDate DATETIME    
        Declare @new_IsDeleted BIT  

        --old values  
        Declare @old_Name varchar(200)  
        Declare @old_Description varchar(200)  

        Declare @old_CreatedBy UNIQUEIDENTIFIER    
        Declare @old_CreatedDate DATETIME    
        Declare @old_UpdatedBy UNIQUEIDENTIFIER    
        Declare @old_UpdatedDate DATETIME    
        Declare @old_IsDeleted BIT  


        -- declare temp fmId  
        Declare @fmId int  
        -- declare cursor  
        DECLARE curFormId cursor   
        FOR select Id from INSERTED   
        -- open cursor       
        OPEN curFormId  
        -- fetch row  
        FETCH NEXT FROM curFormId INTO @fmId  

        WHILE @@FETCH_STATUS  = 0   
        BEGIN   

        Select   
        @FormLineAttributeValueId = Id,   
        @old_Name = Name,  
        @old_Description = [Description],  

        @old_CreatedBy = CreatedBy,    
        @old_CreatedDate =CreatedDate,  
        @old_UpdatedBy =UpdatedBy,    
        @old_UpdatedDate =UpdatedDate,  
        @old_IsDeleted  = IsDeleted,  
        @currentxml = cast(RowHistory as NVARCHAR(MAX))  
        From DELETED where Id=@fmId  



        Select      
        @new_Name = Name,  
        @new_Description = [Description],  

        @new_CreatedBy = CreatedBy,    
        @new_CreatedDate =CreatedDate,  
        @new_UpdatedBy =UpdatedBy,    
        @new_UpdatedDate =UpdatedDate,  
        @new_IsDeleted  = IsDeleted  
        From INSERTED where Id=@fmId  

        set @old_Name = Replace(@old_Name,'&','&amp;')
        set @old_Name = Replace(@old_Name,'>','&gt;')  
        set @old_Name = Replace(@old_Name,'<','&lt;')     
        set @old_Name = Replace(@old_Name,'"','&quot;')
        set @old_Name = Replace(@old_Name,'''','&apos;')          

        set @new_Name = Replace(@new_Name,'&','&amp;')      
        set @new_Name = Replace(@new_Name,'>','&gt;')  
        set @new_Name = Replace(@new_Name,'<','&lt;')     
        set @new_Name = Replace(@new_Name,'"','&quot;')
        set @new_Name = Replace(@new_Name,'''','&apos;') 

        set @old_Description = Replace(@old_Description,'&','&amp;')
        set @old_Description = Replace(@old_Description,'>','&gt;')  
        set @old_Description = Replace(@old_Description,'<','&lt;')     
        set @old_Description = Replace(@old_Description,'"','&quot;')
        set @old_Description = Replace(@old_Description,'''','&apos;')          

        set @new_Description = Replace(@new_Description,'&','&amp;')      
        set @new_Description = Replace(@new_Description,'>','&gt;')  
        set @new_Description = Replace(@new_Description,'<','&lt;')     
        set @new_Description = Replace(@new_Description,'"','&quot;')
        set @new_Description = Replace(@new_Description,'''','&apos;')   

        set @xml = ''     

        BEGIN      

        -- for Name  
        If ltrim(rtrim(IsNull(@new_Name,''))) != ltrim(rtrim(IsNull(@old_Name,'')))    
        set @xml = @xml + '<ColumnInfo ColumnName="Name" OldValue="'+ @old_Name + '" NewValue="' + @new_Name + '"/>'    

        -- for Description  
        If ltrim(rtrim(IsNull(@new_Description,''))) != ltrim(rtrim(IsNull(@old_Description,'')))    
        set @xml = @xml + '<ColumnInfo ColumnName="Description" OldValue="'+ @old_Description + '" NewValue="' + @new_Description + '"/>'    

        -- CreatedDate     
        If IsNull(@new_CreatedDate,'') != IsNull(@old_CreatedDate,'')  
        set @xml = @xml + '<ColumnInfo ColumnName="CreatedDate" OldValue="'+ cast(isnull(@old_CreatedDate,'') as varchar(100)) + '" NewValue="' + cast(isnull(@new_CreatedDate,'') as varchar(100)) + '"/>'    

        -- CreatedBy     
        If cast(IsNull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar (36)) != cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar(36))    
        set @xml = @xml + '<ColumnInfo ColumnName="CreatedBy" OldValue="'+ cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(isnull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
        '"/>'    

        -- UpdatedDate       
        If IsNull(@new_UpdatedDate,'') != IsNull(@old_UpdatedDate,'')    
        set @xml = @xml + '<ColumnInfo ColumnName="UpdatedDate" OldValue="'+ cast(IsNull(@old_UpdatedDate,'') as varchar(100)) + '" NewValue="' + cast(IsNull(@new_UpdatedDate,'') as varchar(100)) + '"/>'    

        -- UpdatedBy     
        If cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) != cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))    
        set @xml = @xml + '<ColumnInfo ColumnName="UpdatedBy" OldValue="'+ cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
        '"/>'    

        -- IsDeleted  
        If cast(IsNull(@new_IsDeleted,'') as varchar(10)) != cast(IsNull(@old_IsDeleted,'') as varchar(10))    
        set @xml = @xml + '<ColumnInfo ColumnName="IsDeleted" OldValue="'+ cast(IsNull(@old_IsDeleted,'') as varchar(10)) + '" NewValue="' + cast(IsNull(@new_IsDeleted,'') as varchar(10)) + '" />'    

        END    

        Set @xml = '<RowInfo TableName="te_Page" UpdatedBy="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(50)) +  '" UpdatedDate="' + Convert(Varchar(20),GetDate()) + '">' + @xml + '</RowInfo>'    
        Select @ishistoryexists = RowHistory From DELETED     

        --print @ishistoryexists  


        If @ishistoryexists is null    
        Begin     
        Set @xml = '<History>' + @xml + '</History>'      
        Update te_Page    
        Set    
        RowHistory = @xml    
        Where     
        Id = @FormLineAttributeValueId    

        End    

        Else    
        Begin     
        set @xml = REPLACE(@currentxml, '<History>', '<History>' + @xml)  
        Update te_Page  
        Set  
        RowHistory = @xml  
        Where   
        Id = @FormLineAttributeValueId     
        End  


        FETCH NEXT FROM curFormId INTO @fmId  
        END   


        CLOSE curFormId  
        DEALLOCATE curFormId  

Chaque fois que vous effectuerez une mise à jour, vos données seront stockées dans la colonne historique de rangée

.

L’une des façons dont j’ai vu le traitement de cette situation (bien que je ne le recommande pas, honnêtement) est de le gérer via des procédures stockées, en passant l’utilisateur ID / nom d’utilisateur / peu importe en tant que paramètre. Les procédures stockées appellent une procédure de journalisation, qui écrit les détails pertinents dans une table de journalisation centrale.

Voilà où ça a un peu piqué, mais ...

Pour les INSERT / UPDATE, les lignes pertinentes ont été stockées dans la table sous forme de données XML une fois que INSERT / UPDATE s'est terminé avec succès. Pour DELETE, la ligne était stockée avant son exécution (bien que, de façon réaliste, elle aurait pu l'obtenir à partir de la sortie de l'instruction DELETE, du moins avec SQL Server 2005).

Si je me souviens bien, la table ne contenait que quelques colonnes: ID utilisateur, DateTime de la journalisation, Type de transaction (I / U / D), données XML contenant les lignes pertinentes, nom de la table et valeur de la clé primaire (principalement utilisée). pour rechercher rapidement les enregistrements souhaités).

De nombreuses façons de peler un chat, cependant ...

Mon conseil est de garder, c'est simple. Développez-le plus tard si / quand vous en aurez besoin.

Si vous en avez la possibilité, verrouillez les utilisateurs pour qu'ils puissent uniquement exécuter des instructions exploitables sur les tables via des procédures stockées, puis gérer la journalisation (comme vous le souhaitez) à partir de cet emplacement.

nous avons construit notre propre logiciel et nous avons juste besoin que l’utilisateur et le PC soient transmis à chaque procédure stockée d’ajout / mise à jour. ensuite, il suffit d'obtenir l'enregistrement original, de renseigner les variables, de les comparer aux variables transmises et de consigner les données dans notre table. pour les suppressions, nous ne disposons que d’une copie des tables d’origine et d’un champ d’horodatage. Ainsi, l’enregistrement n’est jamais réellement supprimé et peut être restauré à tout moment (le programme de suppression vérifie évidemment les relations FK et autres).

le tableau de journal d'ajout / mise à jour ressemble à date / heure, nom de la table, nom de colonne, record_id, old_value, nouvelle valeur, identifiant d'utilisateur, ordinateur

nous n'insérons jamais de valeur NULL; nous les convertissons donc en chaînes vides. Les nouvelles entrées sont marquées d'un '{nouvelle entrée}' dans la colonne old_value. record_id est constitué d'autant de colonnes de clés permettant d'identifier de manière unique cet enregistrement (field1 + '.' + field2 + ...)

Tout d’abord, dans toutes vos tables, ces colonnes doivent au moins être ajoutées aux colonnes de données DateCreated, UserCreated, DateModified, UserModified. Peut-être voudrez-vous ajouter un & Quot; Status & Quot; ou " LastAction " colonne de sorte que vous ne supprimiez jamais réellement une ligne que vous venez de définir à un statut supprimé / inséré / mis à jour. Ensuite, vous pouvez créer un & Quot; Table d'historique & Quot; qui est une copie exacte de la première table. Ensuite, pour toute mise à jour ou suppression, le déclencheur copie les entrées de la table supprimées dans la table historique en modifiant simultanément les champs DateModified, UserModified et Status.

J'ai eu une installation dans SQL Server où nous utilisions des vues pour accéder à nos données, qui géraient les insertions, les mises à jour et les suppressions à l'aide de déclencheurs INSTEAD OF.

Par exemple: un déclencheur INSTEAD OF DELETE dans la vue marquerait les enregistrements de la table sous-jacente comme supprimés, et la vue a été filtrée pour ne pas afficher les enregistrements supprimés.

Dans tous les déclencheurs, nous avons mis à jour une date de modification et un nom d'utilisateur. Le problème est que cela enregistre le nom d'utilisateur de la base de données, ce qui n'est pas le même que le nom d'utilisateur de l'application ultime.

La vue doit être liée au schéma pour que cela fonctionne.

À propos de la journalisation des utilisateurs qui changent de base de données: vous pouvez créer autant d'utilisateurs SQL que nécessaire pour votre base de données et si vous utilisez des sessions et un accès restreint / enregistré à votre programme / script vous pouvez utiliser ces informations pour initialiser différents paramètres de connexion à la base de données (nom d'utilisateur, par exemple), avant toute opération avec DB.

Au moins, cela devrait être faisable pour les scripts PHP, mais je me trompe peut-être pour asp.net.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top