none
Änderungen an Tabelle soll User anzeigen RRS feed

  • Allgemeine Diskussion

  • Hallo zusammen,

    ich habe einen Trigger geschrieben und zusammengebaut, dieser hält mir die Änderungen an einer Tabelle fest und schreibt diese in eine andere Tabelle.
    Das funktioniert  ziemlich gut.
    Jetzt habe ich nur noch ein paar kurze Fragen:

    1. Gibt es die Möglichkeit nicht nur die Änderungen in die Tabelle einzutragen, sondern auch den LogIn oder besser noch den Computer von dem aus die Änderungen erzeugt werden ???

    2. Gibt es eigentlich eine Möglichkeit alle Anfragen an eine DB mitzuloggen ??? Also nicht nur die Inserts, Updates und Deletes sondern auch alle Select und andere Anfragen ???

    Und als letztes.
    3. In meinem Skript werden ja die Änderungen an der Tabelle gespeichert, kann ich auch den verwendeten TSQL Befehl speichern ???

    Danke im Voraus für eure Bemühungen.

    Und Liebe Grüße
       Stephan

    P.S.:Hier auch einmal das gekürtzte Skript. Die Basis hatte ich hier im Forum gefunden.

    use [Datenbank] go

    /* Tabelle erzeugen */ CREATE TABLE [dbo].[CreatedObjects_Delete]( [Activity] nvarchar(10) NOT NULL, [ID] [nvarchar](50) NULL, [ShortName] [nvarchar](100) NULL ) ON [PRIMARY]; go

    /* Trigger erzeugen */ CREATE TRIGGER dbo.TR_CreatedObjects ON dbo.CreatedObjects FOR INSERT, UPDATE, DELETE AS SET NOCOUNT ON; DECLARE @Activity nvarchar(10) IF EXISTS(SELECT * FROM deleted) BEGIN SET @Activity = N'UPDATE'; IF NOT EXISTS(SELECT * FROM inserted) BEGIN SET @Activity = N'DELETE'; END IF @Activity = N'DELETE' OR UPDATE([ID]) OR UPDATE([ShortName]) BEGIN INSERT INTO dbo.CreatedObjects_Delete ( Activity, [ID], [ShortName]) SELECT @Activity AS Activity, d.[ID], d.[ShortName] FROM deleted AS d; END END ELSE BEGIN SET @Activity = N'INSERT'; INSERT INTO dbo.CreatedObjects_Delete ( Activity, [ID], [ShortName]) SELECT @Activity AS Activity, i.[ID], i.[ShortName] FROM inserted AS i; END; GO

    /* Test Sektion
    In Tabelle einfügen*/ INSERT INTO dbo.CreatedObjects ([ID], [ShortName]) VALUES ('999_Private', 'test.par');

    /* Tabelle vor Änderung anzeigen */ SELECT * FROM [dbo].[CreatedObjects_Delete];

    /* Tabelle ändern */ UPDATE dbo.CreatedObjects SET [ShortName] = 'Neuer Test.par' WHERE [ID] = '999_Private';

    /* Datensatz löschen */ DELETE dbo.CreatedObjects WHERE [ID] = '999_Private';

    /* geänderte Tabelle anzeigen */ SELECT * FROM [dbo].[CreatedObjects_Delete]; GO


    • Typ geändert Alex Pitulice Freitag, 22. März 2013 09:17 Warten auf Testen
    Mittwoch, 20. März 2013 08:02

Alle Antworten

  • Hi,

    den Benutzer bekommst Du bspw. über:

      SUSER_SNAME()

    Den Hostnamen über:

      HOST_NAME()

    Details darüber findest Du hier:

      http://msdn.microsoft.com/de-de/library/ms174427.aspx

      http://msdn.microsoft.com/de-de/library/ms178598.aspx

    Einfach mal das hier ausführen, dann siehst Du, was bei den einzelnen Werten herauskommt:

    SELECT SUSER_SNAME(),
           HOST_NAME()
    


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Mittwoch, 20. März 2013 08:08
    Moderator
  • Hallo Stefan,

    eingeschränkt funktioniert - fast - alles :). Was m. E. nicht so einfach geht, ist das SQL-Statement. Das ist technisch bedingt, da das letzte SQL-Statement immer das "CREATE TRIGGER" sein wird, wenn es IM Trigger abgefragt wird :( Das nachfolgende Beispiel sollte aber schon mal einen Eindruck von der generellen Funktionsweise geben.

    Was nichts bringt ist die Verwendung von HOST_NAME() im SQL-String. Sie gibt immer den Namen des SQL Servers aus. Du mußt über die dmv's gehen, um an die Connection-Eigenschaften zu gelangen.

    USE tempdb
    GO
    
    DROP TABLE dbo.foo;
    DROP TABLE dbo.foo_History;
    
    CREATE TABLE dbo.foo
    (
    	Id	int		NOT NULL	IDENTITY (1, 1) PRIMARY KEY CLUSTERED,
    	col1	char(20)	NOT NULL,
    	col2	char(20)	NOT NULL,
    	col3	char(20)	NOT NULL
    )
    GO
    
    CREATE TABLE dbo.foo_History
    (
    	Id			int,
    	Action		char(20),
    	col1		char(20),
    	col2		char(20),
    	col3		char(20),
    	InsertUser	sysname		DEFAULT (ORIGINAL_LOGIN()),
    	InsertDate	datetime	DEFAULT (getdate()),
    	InsertHost	sysname,
    	InsertApp	sysname,
    	SQLStmt		nvarchar(max),
    	
    	CONSTRAINT pk_foo_History_Id_InsertDate PRIMARY KEY CLUSTERED
    	(Id, InsertDate)
    )
    GO
    
    CREATE TRIGGER dbo.foo_Update_Delete
    ON	dbo.foo
    FOR	UPDATE, DELETE
    AS
    	SET NOCOUNT ON
    	
    	DECLARE	@HostName	sysname;
    	DECLARE	@Application	sysname;
    	DECLARE	@SQLStmt	nvarchar(max);
    	
    	SELECT	@Hostname = s.host_name,
    		@Application = s.program_name,
    		@SQLStmt = st.text
    	FROM	sys.dm_exec_connections c INNER JOIN sys.dm_exec_sessions s
    		ON (c.session_id = s.session_id) CROSS APPLY sys.dm_exec_sql_text(most_recent_sql_handle) st
    	WHERE	c.session_id = @@spid;
    	
    	INSERT INTO dbo.foo_History
    	SELECT	d.id,
    		CASE WHEN i.Id IS NULL
    			 THEN 'Delete'
    			 ELSE 'Update'
    		END,
    		d.col1,
    		d.col2,
    		d.col3,
    		ORIGINAL_LOGIN(),
    		GETDATE(),
    		@HostName,
    		@Application,
    		@SQLStmt
    	FROM	deleted d LEFT JOIN inserted i
    		ON (d.id = i.id)
    			
    	SET NOCOUNT OFF
    GO
    
    -- Test
    INSERT INTO dbo.foo (col1, col2, col3)
    VALUES
    ('Uwe', 'Ricken', 'UR'),
    ('Beate', 'Ricken', 'BR')
    
    SELECT * FROM dbo.foo;
    SELECT * FROM dbo.foo_History
    GO
    
    UPDATE	dbo.foo
    SET	col3 = 'AR'
    WHERE	Id = 1
    GO
    
    SELECT * FROM dbo.foo;
    SELECT * FROM dbo.foo_History
    GO

    Weitere Informationen zu den dmv's findest Du hier.

    sys.dm_exec_connections: http://msdn.microsoft.com/de-de/library/ms181509.aspx
    sys.dm_exec_sessions: http://msdn.microsoft.com/de-de/library/ms176013.aspx
    sys.dm_exec_sql_text(): http://msdn.microsoft.com/de-de/library/ms181929.aspx


    Uwe Ricken

    MCSE - SQL Server 2012
    MCSA - SQL Server 2012
    MCITP Database Administrator 2005
    MCITP Database Administrator 2008
    MCITP Microsoft SQL Server 2008, Database Development

    db Berater GmbH
    http://www-db-berater.de
    SQL Server Blog (german only)

    Mittwoch, 20. März 2013 08:59
  • Hallo Stefan,

    [klugscheissmodus an]
    SUSER_SNAME() würde ich nicht empfehlen, da seit SQL 2005 IMPERSONATION möglich ist. Besser ist immer ORIGINAL_LOGIN()

    HOST_NAME() schlägt leider fehl, da es immer nur den Hostnamen des SQL-Servers ausgibt und nicht den der Workstation
    [klugscheissmodus aus]

    USE tempdb
    GO
    
    CREATE USER test WITHOUT LOGIN;
    GO
    
    EXECUTE AS User = 'Test'
    SELECT	SUSER_SNAME(), USER_NAME(), ORIGINAL_LOGIN();
    REVERT
    
    DROP USER test; 

    SCNR ;)


    Uwe Ricken

    MCSE - SQL Server 2012
    MCSA - SQL Server 2012
    MCITP Database Administrator 2005
    MCITP Database Administrator 2008
    MCITP Microsoft SQL Server 2008, Database Development

    db Berater GmbH
    http://www-db-berater.de
    SQL Server Blog (german only)


    Mittwoch, 20. März 2013 09:00
  • Hallo Uwe,

    also bei mir gibt HOST_NAME() den Namen des Clients aus. Sowohl für (die gerade getesteten) SQL Server 2000 und 2008 als auch 2008 R2.

    Ok, bei Impersonation ist das sicher richtig. Falls man noch einen alten SQL Server 2000 hat, funktioniert ORIGINAL_LOGIN() aber nicht, da es das dort wohl noch nicht gibt. SUSER_SNAME() hingegen schon^^


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community



    Mittwoch, 20. März 2013 10:02
    Moderator
  • Servus Stefan,

    joo - hast Recht. Da habe ich mich vertan (mea culpa). Jedoch empfiehlt Microsoft selbst nicht die Verwendung von HOST_NAME als Sicherheitsfunktion!
    "<sentencetext xmlns="http://www.w3.org/1999/xhtml">Die Clientanwendung stellt den Namen der Arbeitsstation bereit, und sie kann fehlerhafte Daten angeben.</sentencetext><sentencetext xmlns="http://www.w3.org/1999/xhtml">Verwenden Sie HOST_NAME nicht als Sicherheitsfunktion."</sentencetext>

    http://msdn.microsoft.com/de-de/library/ms178598.aspx

    Da der TE nicht gesagt hat, mit welcher Version von SQL Server er sich die Zeit vertreibt, bin ich mal still und leise davon ausgegangen, dass es schon etwas moderner ist :) aber auch hier hast Du natürlich Recht, wenn es sich um Versionen <= 2000 handelt...


    Uwe Ricken

    MCSE - SQL Server 2012
    MCSA - SQL Server 2012
    MCITP Database Administrator 2005
    MCITP Database Administrator 2008
    MCITP Microsoft SQL Server 2008, Database Development

    db Berater GmbH
    http://www-db-berater.de
    SQL Server Blog (german only)

    Mittwoch, 20. März 2013 10:17
  • Da der TE nicht gesagt hat, mit welcher Version von SQL Server er sich die Zeit vertreibt, bin ich mal still und leise davon ausgegangen, dass es schon etwas moderner ist :) aber auch hier hast Du natürlich Recht, wenn es sich um Versionen <= 2000 handelt...

    Was heisst denn "TE"?

    Übrigens funktioniert weder SUSER_SNAME() noch ORIGINAL_LOGIN() zufriedenstellend, wenn der Client eine WebGUI unter IIS ist. Es sei denn, man möchte den Service Account haben, unter dem der App Pool der Website läuft...


    -- Frank Kalis Microsoft SQL Server MVP Webmaster: http://www.insidesql.org

    Mittwoch, 20. März 2013 10:21
  • Hallo Frank,

    ich für meinen Teil arbeite bei Webanwendungen überwiegend mit SQL Server Benutzern und hier je nach Anforderung mit einem zentralen User oder einem SQL Benutzer pro Websitebenutzer.

    In diesen Fällen liefert SUSER_SNAME() zumindest bei mir immer den richtigen Wert. Wenn man versucht, die Verbindung mit dem Benutzer herzustellen, der den Application Pool ausführt, würde das natürlich diesen Benutzer liefern. Aber eigentlich ist es meiner Erfahrung nach sehr selten, dass man das so macht.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Mittwoch, 20. März 2013 10:34
    Moderator
  • Hallo Frank,
    hallo Stefan,

    TE = Threadersteller

    Ich arbeite ich mit einem Application Pool und führe eine "Impersonation" durch.
    Das ist zwar minimal langsamer aber vertretbar (für mich) :)

    Bei reinen Webanwendungen ist das natürlich Blödsinn!


    Uwe Ricken

    MCSE - SQL Server 2012
    MCSA - SQL Server 2012
    MCITP Database Administrator 2005
    MCITP Database Administrator 2008
    MCITP Microsoft SQL Server 2008, Database Development

    db Berater GmbH
    http://www-db-berater.de
    SQL Server Blog (german only)

    Mittwoch, 20. März 2013 10:58
  • Hallo an alle,

    ich möchte mich für alle Antworten bedanken.
    Da habe ich erst einmal eine Menge zu lesen und zum ausprobieren.

    Da ich es leider vergessen habe zu erwähnen *schäm* reiche ich es nach.
    Ich nutze derzeit auf meinem Laptop MS SQL 2012 Express und auf dem Server haben wir MS SQL 2008 R2 laufen.
    Ein großteil der Datenbank welche ich mit meinem Skript mitlogge ist unter Access 2003 entstanden und wurde anschließend über den Upsizing Assistenten nach MS SQL 2005 gebracht und dort weiter verarbeitet.

    Mittwoch, 20. März 2013 14:55
  • Jedoch empfiehlt Microsoft selbst nicht die Verwendung von HOST_NAME als Sicherheitsfunktion!

    Und das aus gutem Grund, denn den Hostname kann man frei über den Connection String mit "WSID = Workstation ID" mitgeben:

    using System;
    using System.Data.SqlClient;
    
    namespace HostTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                var conn = new SqlConnection(@"WSID=IchBinUweSeinRechner; Data Source=.\SQLEXPRESS; Integrated Security=TRUE;");
                var cmd = new SqlCommand(@"SELECT HOST_NAME()", conn);
                conn.Open();
                var hostname = cmd.ExecuteScalar();
                conn.Close();
                Console.WriteLine(hostname.ToString());
                Console.ReadKey();
            }
        }
    }


    Olaf Helper

    Blog Xing


    • Bearbeitet Olaf HelperMVP Donnerstag, 21. März 2013 08:27 Hinweis WSID
    Donnerstag, 21. März 2013 07:44
  • Hallo Olaf,

    das ist ein wichtiger Hinweis und führt dann auch bei meinem Beispiel zu möglichen Mißverständnissen da ja diese Informationen in sys.dm_exec_sessions abgelegt werden :(

    Aber eine andere Möglichkeit (für die bessere Nachverfolgung wäre dann, zusätzlich die IP-Adresse zu speichern. Fragt sich nur, ob das dann noch wirklich prakikabel ist im Zeitalter von DHCP :)


    Uwe Ricken

    MCSE - SQL Server 2012
    MCSA - SQL Server 2012
    MCITP Database Administrator 2005
    MCITP Database Administrator 2008
    MCITP Microsoft SQL Server 2008, Database Development

    db Berater GmbH
    http://www-db-berater.de
    SQL Server Blog (german only)

    Donnerstag, 21. März 2013 08:14