SQL Server Trigger

mmarschner

Benutzer
Beiträge
16
Hallo zusammen,

ich muss auf einem SQL Server die letzte Änderung, bzw. die Neuanlage an den Daten protokollieren (Kundenwunsch - und keine Diskussion über den Datenschutz). Dabei soll das Datum und die Uhrzeit der letzten Änderung dokumentiert werden, und der Anmeldename des Benutzers, der die Änderungen durchgeführt hat.

Die Änderung an den Daten werden in der betreffenden Tabelle gespeichert. Es geht hier nicht um eine Historie, sondern nur um die letzten Daten.

Bisher wird die letzte Änderung über eine entsprechende VBA Prozedur dokumentiert, aber das wird auf lange Sicht zu unübersichtlich.

Daher wollte ich einen Trigger auf dem SQL Server für die jeweilige Tabelle benutzen.

Einen Trigger auf dem SQL Server zur betreffenden Tabelle anzulegen, ist kein Problem und das Datum mit Uhrzeit wird auch korrekt abgespeichert. Nur wie komme ich an den Benutzernamen, der die Neuanlage / Änderungen durchgeführt hat ran.

Die ODBC Verbindung zum SQL Server läuft über einen gemeinsamen Benutzer als SQL Server Authentifizierung. Für die Benutzeranmeldung gibt es eine Tabelle mit den Benutzern, die sich in der Datenbank anmelden dürfen. Ich kann die ODBC Verbindung aber auch auf Windows Authentifizierung umstellen, wenn das notwendig ist, und ggf. die Anmeldung in Access mit dem SQL Server User abgleichen.

Fragende Grüße von

Michael
 
Werbung:
Es gibt sowas wie SESSION_USER (auf dem Server, u.U. auch Varianten davon), kannst Du einfach mal auf dem Server oder auch in Access ausprobieren mit einem Select Statement.
Eigentlich sollte es ziemlich egal sein, wie Du die Authentifizierung machst. Wichtig ist nur, dass von Access aus nicht ein zentraler Benutzer gegen SQL verwendet wird. Falls das doch so ist, gibt es vielleicht andere Mittel und Wege, trotzdem an den am Client angemeldeten Windows-Benutzer zu kommen.
U.U. kann das auch über die Konfiguration in ODBC (Dateidatenqqquelle, User-, System-) noch variiert werden.
Ich würde mich an der Stelle mal etwas absichern (erkundigen), wie die Produktstrategie von MS ist. Office "classic" oder wie das jetzt heißt, versus 360 und was da so demnächst passiert. Hab irgendwo gelesen, dass sie z.B. mit Outlook wieder kräftig basteln und alles in die Cloud ziehen.
Also nicht dass man die ganze Anwendung umbaut und am Ende wird das Feature dann gestrichen.
Hab mit 360 noch nie was gemacht und hab es auch nicht vor.
 
Hallo, das verstehe ich nicht.

Bisher wird die letzte Änderung über eine entsprechende VBA Prozedur dokumentiert, aber das wird auf lange Sicht zu unübersichtlich.

Warum ist das unübersichtlich?
Der große Vorteil eines Triggers ist, dass du auch Manipulationen an den Daten, die an Access vorbeigehen, dokumentieren kannst (z.B. Schnittstellen)
Der Nachteil ist, dass es Trigger ja so tief im Untergrund laufen, dass man gerne auf sie vergisst. Aus Access heraus, was oft den Großteil der Logik enthält, ist auf einmal nicht mehr klar, wie die Daten in eine Tabelle kommen.
Das bedeutet, du erkaufst dir Übersichtlichkeit in Access / VBA (als Codereduktion) durch Verlagerung von Code in eine andere Anwendung. Damit wird das Verfahren nicht einfacher oder durchsichtiger. Dafür benötigst du eine dezidierte Dokumentation die dieses Vergehen beschreibt.

Daher: Gibt es wirklich den Bedarf an zusätzlichen Protokollierungen von außen oder ist es eher eine Frage am Konzept der Funktion in Access?
 
Hallo MDDaniel,

das Problem mit der Dokumentation kommt daher, dass in der Anwendung Aufträge von verschiedenen Mitarbeitern erfasst werden, die an ein bestimmtes Datum gebunden sind. Diese Erfassung erfolgt nach Ausführung der Aufträge und da passiert es leider immer wieder, dass falsche Daten erfasst werden, insbesondere Datumsangaben. Eine Prüfung auf Daten in der Zukunft ist ja relativ einfach, da wie gesagt die Aufträge in der Vergangenheit liegen und zum Teil auch schon Monate zurückliegen. Aber genau hier tritt immer wieder das Problem mit der Falscheingabe auf. Diese erfassten Aufträge sind die Basis für die Abrechnung von ca. 100 Mitarbeitern, die pro Monat bis zu 100 Aufträge abarbeiten können.

Daher wünscht der Kunde diese Dokumentation, um ggf. mit dem Erfasser diese Falscheingaben zu klären. Es ist nicht immer nur Unachtsamkeit, sondern manchmal auch eine Unleserlichkeit durch denjenigen der den Auftrag ausgeführt und dokumentiert hat - Reports, etc.

Und die Unübersichtlichkeit liegt an der Komplexität der Anwendung, die ständig weiterentwickelt wird und bei der sich ständig die Anforderungen an die einzelnen Prozeduren und Berechnungen ändern.

Deshalb wollte ich die Datenänderung auslagern, sowie nach und nach etliche Prozeduren auf den SQL Server legen, um auch die Performance im Auge zu behalten.

@dabadepdu,

Session_User gibt leider nur den am Management Studio angemeldeten Benutzer zurück.
 
Wenn der Speichervorgang nicht an einer Stelle stattfindet ist das verständlich und nachvollziehbar. Dem Problem kannst du begegnen indem du zumindest die Dokumentation aus als zentrale Funktion bereitstellst und dann zentral (after_Update) oder von allen Funktionen aus aufrufst.

Auf SQL Seite müsste die Function SUSER_SNAME() den Windows User liefern.
 
Session_User gibt leider nur den am Management Studio angemeldeten Benutzer zurück.
Es gibt ein halbes Dutzend dieser Funktionen, ANSI, original, alte, neue, Login, Session, probiere mal, was bei Dir unter ACCESS das gewünschte Ergbnis liefert oder schau mal nach Dokumentation, es gibt auch welche, die abgekündigt sind. Ich benutze MS Produkte nicht mehr. Egal wo Du Dich anmeldest, Management Studio, Access, es kann ja immer nur einen (DEN) gerade angemeldeten Benutzer zurück geben.
Vielleicht ist der Tipp von @MDDaniel genau richtig.
Deshalb wollte ich die Datenänderung auslagern, sowie nach und nach etliche Prozeduren auf den SQL Server legen, um auch die Performance im Auge zu behalten.
Scheint mir genau richtig. Das sollte wesentlich zuverlässiger sein und kann nur noch manipuliert werden, Versehen lässt sich also ausschließen und es ist keine Extraarbeit mehr, Dinge einzugeben, die das System sowieso zuverlässig erfassen kann.
 
SYSTEM_USER könnte interessant sein, der funktioniert bei mir (allerdings kein Access Front End). Ansonsten gibt es noch SESSION_USER, CURRENT_USER...

Teste es einfach. Aktiviere den Trigger, schreibe SYSTEM_USER in eine Log-Tabelle und führe dann ein INSERT oder UPDATE auf der Tabelle aus aber aus Access, nicht aus dem SSMS.

Der Datenschutz dürfte dem übrigens nicht im Wege stehen, eher im Gegenteil. Wenn es um personenbezogene Daten geht, solltest, vielleicht sogar musst du gewisse Dinge protokollieren. Spannend wirds, wenn du SELECT loggen willst. Das mache ich tatsächlich in einem Fall, war aber schwieriger als gedacht.
 
Guten Morgen,

danke für die Tipps, was mich aber bisher nicht zum gewünschten Ziel geführt hat. Ich werde mich zu ggf. Zeit weiter darum kümmern, da jetzt erst einmal andere Dinge vorrangig behandelt werden müssen.

Michael
 
Es hat mir keine Ruhe gelassen und ich habe nun folgenden Code:

In der ODBC Verknüpfung habe ich den Benutzernamen für den Login auf dem SQL Server mit entsprechendem Passwort hinterlegt und ich kann eine korrekte Verbindung zur Datenbank herstellen.

Der Code sieht jetzt so aus:

Code:
USE [Fahrwerk]
GO
/****** Object:  Trigger [dbo].[Datum_Aenderung_Filiale]    Script Date: 11.04.2025 09:26:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        Name
-- Create date:
-- Description:   
-- =============================================
ALTER TRIGGER [dbo].[Datum_Aenderung_Filiale]
   ON  [dbo].[BtoB_Filiale]
   AFTER INSERT,DELETE,UPDATE
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for trigger here
    Update BtoB_Filiale
    set BtoB_Filiale.Letzte_Aenderung =  GETDATE(),
    BtoB_Filiale.Aenderung_Durch=(SELECT login_name
        FROM sys.dm_exec_sessions
        where client_interface_name=N'ODBC'
        and host_name <> N'SQLServer'
        group by login_name)

END

Leider werden mir dabei alle Datensätze in der Tabelle geändert.

Woran kann das liegen?
 
Ich habe es doch noch hinbekommen:

Code:
USE [Fahrwerk]
GO
/****** Object:  Trigger [dbo].[Datum_Aenderung_Filiale]    Script Date: 11.04.2025 09:26:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        Name
-- Create date: 
-- Description:    
-- =============================================
ALTER TRIGGER [dbo].[Datum_Aenderung_Filiale] 
   ON  [dbo].[BtoB_Filiale] 
   AFTER INSERT,UPDATE
AS 
BEGIN

    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    Declare @Index    int

    set @index = (SELECT idx_Filiale from inserted)

    -- Insert statements for trigger here
    Update BtoB_Filiale
    set BtoB_Filiale.Letzte_Aenderung =  GETDATE(),
    BtoB_Filiale.Aenderung_Durch=(SELECT login_name
        FROM sys.dm_exec_sessions
        where client_interface_name=N'ODBC'
        and host_name <> N'SQLServer'
        group by login_name)
    where BtoB_Filiale.idx_Filiale = @Index

END
 
Und doch noch ein Schönheitsfehler, den ich beheben muss.

Die SELECT Anweisung zur Ermittlung des Loginnamens gibt mir natürlich alle angemeldeten Benutzer zurück und ich brauche ja nur den, der die Änderungen durchgeführt hat.

Also weitersuchen, oder hat jemand dazu eine Idee?
 
Dein Trigger krankt an einigen Problemen, die du vermutlich noch nicht siehst.

1) Inserted kann mehr als einen Datensatz beinhalten, wenn mehr als ein Datensatz vom INSERT, UPDATE oder DELETE betroffen ist. Bei
set @index = (SELECT idx_Filiale from inserted)
knallt es also, weil SELECT mehr als einen Wert liefert. Das gleiche gilt auch bei
BtoB_Filiale.Aenderung_Durch=(SELECT login_name FROM sys.dm_exec_sessions...

2) Selbst, wenn man das mit TOP 1 abfängt, könnte das von dir ungewollte Nebenwirkungen haben. Der Trigger feuert pro Statement einmal, nicht pro Datensatz, wie man das z.B. bei MySQL vorgeben kann. Man sollte also alles, was im Trigger passiert, immer so aufbauen, das es mehr als einen Datensatz verarbeiten kann. Auch wenn der User vor der Software vielleicht immer nur einen Datensatz gleichzeitig anfasst, kommt irgendwann der Punkt, wo mal Daten importiert oder exportiert werden, das sollte schon gehen.

3) Du aktualisierst immer den Datensatz in BtoB_Filiale für die Quelltabelle mit Benutzer und Zeitpunkt. Ja, das geht theoretisch. Aber sobald zwei Aktionen in der Tabelle, eventuell auf unterschiedlichen Datensätzen, hinter einander erfolgen, verlierst du alle Informationen über die Erste. Ich kann mir nicht vorstellen, das du das willst. Normalerweise schreibt man sich eher eine Log und erzeugt für jede Aktion einen neuen Log-Datensatz.

4) Ich verstehe nicht, warum du sys.dm_exec_sessions überhaupt abfragst. Das kann mehrere Probleme mit sich bringen, aber eigentlich brauchst du das gar nicht. SYSTEM_USER ist eine Variable, die dir immer zur Verfügung steht. Wenn du also als Benutzer SELECT SYSTEM_USER; aufrufst, bekommst du den aktuellen Benutzer angezeigt, mit dem du den Select ausgeführt hast. Ein Trigger läuft im Kontext des Benutzers, der die Aktion auf der Tabelle ausgelöst hat. Wenn also ein Trigger SYSTEM_USER abfragt und in eine Tabelle schreibt, dann ist das immer der Benutzer, der die Aktion auf der Tabelle gemacht hat. Eigentlich sollte das völlig ausreichen.

Ich schreibe dir mal einen Beispielcode. Ich kenne den Primary Key nicht und verwende pk als Spaltenname. In der Log-Tabelle setze ich mal noch zwei Spalten voraus: "fk_BtoB_Filiale" ist die ID des Datensatzes, der betroffen war und "aktion" das, was passiert ist. Das kann man theoretisch natürlich weg lassen aber irgendwie will man ja schon sehen, was genau passiert ist.
Code:
USE [Fahrwerk]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER TRIGGER [dbo].[Datum_Aenderung_Filiale]
   ON  [dbo].[BtoB_Filiale]
   AFTER INSERT,UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO BtoB_Filiale(Letzte_Aenderung,Aenderung_Durch,fk_BtoB_Filiale,aktion)
    SELECT    getdate(),
            SYSTEM_USER,
            i.pk,
            (CASE WHEN i.pk IS NOT NULL AND d.pk IS NULL THEN 'Insert' WHEN i.pk IS NOT NULL AND d.pk IS NOT NULL THEN 'Update' WHEN i.pk IS NULL THEN 'Delete' ELSE NULL END)
    FROM    INSERTED i
    FULL OUTER JOIN DELETED d
    ON        i.pk = d.pk;

END
 
Die ODBC Verbindung zum SQL Server läuft über einen gemeinsamen Benutzer als SQL Server Authentifizierung. Für die Benutzeranmeldung gibt es eine Tabelle mit den Benutzern, die sich in der Datenbank anmelden dürfen. Ich kann die ODBC Verbindung aber auch auf Windows Authentifizierung umstellen, wenn das notwendig ist, und ggf. die Anmeldung in Access mit dem SQL Server User abgleichen.
Solange die Benutzerinfo nur in dieser Tabelle liegt haben die Benutzer keinerlei Accounts auf dem SQL Server und es kann sich auch niemand an der Datenbank anmelden. Sowas muss zwingend im SQL Server abgebildet werden. Dann würdest auch im Audit sehen welcher Account was gemacht hat.
 
Werbung:
Guten Morgen,

ich habe mir das Ganze noch einmal mit Euren Informationen angesehen.

Zuerst noch ein paar zusätzliche Informationen:

  • es werden keine Daten in die Datenbank importiert und es ist auch nicht vorgesehen
  • es ist nicht vorgesehen Daten aus der Datenbank zu exportieren und wenn, dann muss das nicht dokumentiert werden
  • die Tabelle BtoB_Filiale ist nur ein Beispiel gewesen, da diese nicht so umfangreich und eher überschaubar für mich ist.
  • bei dem "SELECT System_User" war ich von falschen Voraussetzungen ausgegangen, was sich aber durch Eure Hilfe klären lies
Ich habe jetzt dank Euch folgenden Ansatz gefunden, der sicherlich nicht optimal ist, aber für meine Zwecke ausreicht:

  • ich werde die Mitarbeiter die die Daten erfassen und die mit der Datenbank arbeiten, am SQL Server als lokale Benutzer anlegen,
  • ich werde die Verbindung zum SQL Server mit diesem Benutzer steuern, damit der Trigger den richtigen Mitarbeiter erkennen kann,
  • ich werde versuchen, die ODBC Verbindung zum Server durch einen Connectionstring zu ersetzen, der automatisch den Mitarbeiter übergibt,
Code:
USE [Fahrwerk]
GO
/****** Object:  Trigger [dbo].[Datum_Aenderung_Filiale]    Script Date: 13.04.2025 08:21:44 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        Name
-- Create date:
-- Description:   
-- =============================================
CREATE TRIGGER [dbo].[Datum_Aenderung_Filiale]
   ON  [dbo].[BtoB_Filiale]
   AFTER INSERT,UPDATE
AS
BEGIN

    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    Declare @Index    int

    set @index = (SELECT idx_Filiale from inserted)

    -- Insert statements for trigger here
    Update BtoB_Filiale
    set BtoB_Filiale.Letzte_Aenderung =  GETDATE(),
    BtoB_Filiale.Aenderung_Durch=(SELECT SYSTEM_USER)
    where BtoB_Filiale.idx_Filiale = @Index

END

Dabei steht @Index für den PK des betreffenden Datensatzes.

Ich habe das jetzt mehrfach probiert (noch mit einem Benutzer, der in der ODBC Verbindung übergeben wurde) und das hat den gewünschten Erfolg gebracht.

Wie oben schon geschrieben, werde ich versuchen, die ODBC Verbindung durch einen Connection String zu ersetzten und den betreffenden User aus der Anmeldung mit übergeben. Der betreffende Anwendername weicht leider von dem Windows Benutzernamen ab, sonst hätte ich den nehmen können - Anmeldung an mehreren PCs mit dem gleichen Benutzernamen, aber unterschiedliche Datenbankbenutzer.

Noch einmal vielen Dank und ich bin für weitere Anregungen und Kommentare dankbar, da auch ich - nach über 20 Jahren Programmiererfahrung - immer noch eine Menge dazulernen kann, insbesondere was den SQL Server angeht.
 


Schreibe deine Antwort....
Zurück
Oben