SQL Server 2016 - Temporal Table - how to identify the user











up vote
5
down vote

favorite
2












Is it possible to get the information about the user/connection that modified data that is in the historical table? I read about the audit scenario where I can use temporal tables and that it's possible to detect who has changed the data. But how can I do that?










share|improve this question




















  • 1




    This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
    – Aaron Bertrand
    Jan 12 '17 at 1:17










  • And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
    – Aaron Bertrand
    Jan 12 '17 at 1:20










  • Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
    – user1481065
    Jan 12 '17 at 18:50












  • Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
    – Aaron Bertrand
    Jan 12 '17 at 18:53












  • And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
    – Aaron Bertrand
    Jan 13 '17 at 19:00















up vote
5
down vote

favorite
2












Is it possible to get the information about the user/connection that modified data that is in the historical table? I read about the audit scenario where I can use temporal tables and that it's possible to detect who has changed the data. But how can I do that?










share|improve this question




















  • 1




    This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
    – Aaron Bertrand
    Jan 12 '17 at 1:17










  • And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
    – Aaron Bertrand
    Jan 12 '17 at 1:20










  • Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
    – user1481065
    Jan 12 '17 at 18:50












  • Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
    – Aaron Bertrand
    Jan 12 '17 at 18:53












  • And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
    – Aaron Bertrand
    Jan 13 '17 at 19:00













up vote
5
down vote

favorite
2









up vote
5
down vote

favorite
2






2





Is it possible to get the information about the user/connection that modified data that is in the historical table? I read about the audit scenario where I can use temporal tables and that it's possible to detect who has changed the data. But how can I do that?










share|improve this question















Is it possible to get the information about the user/connection that modified data that is in the historical table? I read about the audit scenario where I can use temporal tables and that it's possible to detect who has changed the data. But how can I do that?







sql-server sql-server-2016






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jun 7 '17 at 20:16









skeletank

2,09453764




2,09453764










asked Jan 11 '17 at 23:20









user1481065

2001113




2001113








  • 1




    This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
    – Aaron Bertrand
    Jan 12 '17 at 1:17










  • And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
    – Aaron Bertrand
    Jan 12 '17 at 1:20










  • Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
    – user1481065
    Jan 12 '17 at 18:50












  • Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
    – Aaron Bertrand
    Jan 12 '17 at 18:53












  • And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
    – Aaron Bertrand
    Jan 13 '17 at 19:00














  • 1




    This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
    – Aaron Bertrand
    Jan 12 '17 at 1:17










  • And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
    – Aaron Bertrand
    Jan 12 '17 at 1:20










  • Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
    – user1481065
    Jan 12 '17 at 18:50












  • Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
    – Aaron Bertrand
    Jan 12 '17 at 18:53












  • And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
    – Aaron Bertrand
    Jan 13 '17 at 19:00








1




1




This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
– Aaron Bertrand
Jan 12 '17 at 1:17




This isn't really for auditing. If you think about it, when a user modifies a row, the row in the base table is the one that user changed. So you couldn't capture the user name of the person who caused a history row to be created, they could be updating a row that I updated yesterday, and therefore the row that moves to the history table represents the changes I made yesterday. So you would need to have a column in the base table that gets updated using a trigger, but this will create two versions of the history row (one with the previous editor, and one with the new editor).
– Aaron Bertrand
Jan 12 '17 at 1:17












And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
– Aaron Bertrand
Jan 12 '17 at 1:20




And no, you can't sneak around this with INSTEAD OF triggers. I tried it, and there was a loophole, but I reported it, and they fixed it.
– Aaron Bertrand
Jan 12 '17 at 1:20












Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
– user1481065
Jan 12 '17 at 18:50






Thx for the hints. I read this german/english msdn article msdn.microsoft.com/en-us/library/mt631669.aspx Data Audit "Use temporal system-versioning on tables that store critical information for which you need to keep track of what has changed, when and by whom, and to perform data forensics at any point in time." I use currently a solution with a usercolumn and cdc. I thought that i can replace this solution with a temporal table. But it looks like that i must wait for the next update.
– user1481065
Jan 12 '17 at 18:50














Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
– Aaron Bertrand
Jan 12 '17 at 18:53






Yeah, that article does mention auditing data, but that isn't what I meant by auditing. Other than a hand-wavy "by whom," it doesn't mention anything explicit at all about auditing the user who made the change.
– Aaron Bertrand
Jan 12 '17 at 18:53














And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
– Aaron Bertrand
Jan 13 '17 at 19:00




And the documentation has now been updated - notice that it no longer makes any kind of statement about "by whom."
– Aaron Bertrand
Jan 13 '17 at 19:00












4 Answers
4






active

oldest

votes

















up vote
5
down vote













A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):





SELECT 
e.EmployeeID, e.FirstName, e.Score,
COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy,
e.ValidFromUTC, e.ValidToUTC

FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
LEFT JOIN dbo.EmployeeHistory AS eh -- history table
ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC

OUTER APPLY
(SELECT TOP 1 CreatedBy
FROM dbo.EmployeeHistory
WHERE EmployeeID = e.EmployeeID
ORDER BY ValidFromUTC ASC) AS o -- oldest history record

WHERE e.EmployeeID = 1
ORDER BY e.ValidFromUTC


Result set




  • Does not use triggers or user defined functions

  • Requires small changes to the table


  • NB: Note that SQL Server always uses UTC, not local time, for time stamps in temporal tables.





Explanation:



Two fields are added to the main and history tables:




  • To record the name of the user who created the record - a normal SQL default:

    CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())

  • To record the name of the current logged in user at any time. A computed column:

    LoggedInUser AS (SUSER_SNAME())


When a record is inserted into the main table, SQL Server does not insert anything into the history table. But the field CreatedBy records who created the record, because of the default constraint. But if/when the record gets updated, SQL Server inserts a record into the associated history table. The key idea here is that the name of the logged-in user who made the change is recorded into the history table, i.e. the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.



That's almost what we want, but not quite - it's one change behind. E.g. if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted. However, all the information is there - it just needs to be unravelled. Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table). However, there's no record in the history table for the originally inserted version of the record. In that case we retrieve the CreatedBy field.



This seems to provide a watertight auditing solution. Even if a user edits the field CreatedBy, the edit will be recorded in the history table. For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.



Deleted records



The query above does not show who deleted records from the main table. This can be retrieved using the following (could be simplified?):





SELECT 
d.EmployeeID, d.LoggedInUser AS DeletedBy,
d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
FROM
(SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh -- list of IDs
OUTER APPLY
(SELECT TOP 1 * FROM dbo.EmployeeHistory
WHERE EmployeeID = eh.EmployeeID
ORDER BY ValidFromUTC DESC) AS d -- last history record, which may be for DELETE
LEFT JOIN
dbo.Employees AS e
ON eh.EmployeeID = e.EmployeeID
WHERE e.EmployeeID IS NULL -- record is no longer in main table


Sample table script



The above examples are based on the table script (history table is created by SQL Server):



CREATE TABLE dbo.Employees(
EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
FirstName NVARCHAR(40) NOT NULL,
Score INTEGER NULL,
LoggedInUser AS (SUSER_SNAME()),
CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
)
WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))


Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.






share|improve this answer






























    up vote
    3
    down vote













    EDIT: See my much better answer elsewhere on this page



    My solution does not need triggers. I have a computed column in the main table which always contains the logged in user, e.g.



    CREATE TABLE dbo.Employees(
    EmployeeID INT NOT NULL,
    FirstName sysname NOT NULL,
    ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
    LoggedInUser AS (SUSER_SNAME()), --<<-- computed column


    ... etc.



    The field LoggedInUser always contains the name of the currently logged in user in every record, and is thus saved into the history table at the time any change was made to any record.



    Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record. But in the history table it gets frozen at the point the change was made, which is very useful, (although it records the user at the end of the period, not the start).



    Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.



    Main (current) table:
    Main table



    History table:
    History table



    Of course in the history table, it records who changed the record from that state, not to that state, i.e. the logged in user at the end of the validity period. It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.



    Any ideas about how to improve this would be welcome, e.g. how to record who made the change at the start of each validity period in the history table. I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.



    Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here, which uses a trigger.






    share|improve this answer























    • Thx for sharing your solution, that was very usefull for me.
      – user1481065
      Feb 9 at 6:56


















    up vote
    2
    down vote













    In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change. And don't read that statement as me having some sort of insider knowledge that that situation may change in the future; I don't know anything about it. If you need that information, you will need to record it in row. A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.






    share|improve this answer




























      up vote
      0
      down vote













      Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.



      This would show who modified the table and thus created the history record. As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.



      This would then also be in the history table each time the record is modified.






      share|improve this answer





















      • This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
        – Aaron Bertrand
        Jan 12 '17 at 18:57








      • 2




        I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
        – fran.tonkovic
        Jan 24 '17 at 15:19













      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f41602579%2fsql-server-2016-temporal-table-how-to-identify-the-user%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      4 Answers
      4






      active

      oldest

      votes








      4 Answers
      4






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      5
      down vote













      A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):





      SELECT 
      e.EmployeeID, e.FirstName, e.Score,
      COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy,
      e.ValidFromUTC, e.ValidToUTC

      FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
      LEFT JOIN dbo.EmployeeHistory AS eh -- history table
      ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC

      OUTER APPLY
      (SELECT TOP 1 CreatedBy
      FROM dbo.EmployeeHistory
      WHERE EmployeeID = e.EmployeeID
      ORDER BY ValidFromUTC ASC) AS o -- oldest history record

      WHERE e.EmployeeID = 1
      ORDER BY e.ValidFromUTC


      Result set




      • Does not use triggers or user defined functions

      • Requires small changes to the table


      • NB: Note that SQL Server always uses UTC, not local time, for time stamps in temporal tables.





      Explanation:



      Two fields are added to the main and history tables:




      • To record the name of the user who created the record - a normal SQL default:

        CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())

      • To record the name of the current logged in user at any time. A computed column:

        LoggedInUser AS (SUSER_SNAME())


      When a record is inserted into the main table, SQL Server does not insert anything into the history table. But the field CreatedBy records who created the record, because of the default constraint. But if/when the record gets updated, SQL Server inserts a record into the associated history table. The key idea here is that the name of the logged-in user who made the change is recorded into the history table, i.e. the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.



      That's almost what we want, but not quite - it's one change behind. E.g. if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted. However, all the information is there - it just needs to be unravelled. Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table). However, there's no record in the history table for the originally inserted version of the record. In that case we retrieve the CreatedBy field.



      This seems to provide a watertight auditing solution. Even if a user edits the field CreatedBy, the edit will be recorded in the history table. For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.



      Deleted records



      The query above does not show who deleted records from the main table. This can be retrieved using the following (could be simplified?):





      SELECT 
      d.EmployeeID, d.LoggedInUser AS DeletedBy,
      d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
      FROM
      (SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh -- list of IDs
      OUTER APPLY
      (SELECT TOP 1 * FROM dbo.EmployeeHistory
      WHERE EmployeeID = eh.EmployeeID
      ORDER BY ValidFromUTC DESC) AS d -- last history record, which may be for DELETE
      LEFT JOIN
      dbo.Employees AS e
      ON eh.EmployeeID = e.EmployeeID
      WHERE e.EmployeeID IS NULL -- record is no longer in main table


      Sample table script



      The above examples are based on the table script (history table is created by SQL Server):



      CREATE TABLE dbo.Employees(
      EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
      FirstName NVARCHAR(40) NOT NULL,
      Score INTEGER NULL,
      LoggedInUser AS (SUSER_SNAME()),
      CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
      ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
      ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
      CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
      PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
      )
      WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))


      Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.






      share|improve this answer



























        up vote
        5
        down vote













        A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):





        SELECT 
        e.EmployeeID, e.FirstName, e.Score,
        COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy,
        e.ValidFromUTC, e.ValidToUTC

        FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
        LEFT JOIN dbo.EmployeeHistory AS eh -- history table
        ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC

        OUTER APPLY
        (SELECT TOP 1 CreatedBy
        FROM dbo.EmployeeHistory
        WHERE EmployeeID = e.EmployeeID
        ORDER BY ValidFromUTC ASC) AS o -- oldest history record

        WHERE e.EmployeeID = 1
        ORDER BY e.ValidFromUTC


        Result set




        • Does not use triggers or user defined functions

        • Requires small changes to the table


        • NB: Note that SQL Server always uses UTC, not local time, for time stamps in temporal tables.





        Explanation:



        Two fields are added to the main and history tables:




        • To record the name of the user who created the record - a normal SQL default:

          CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())

        • To record the name of the current logged in user at any time. A computed column:

          LoggedInUser AS (SUSER_SNAME())


        When a record is inserted into the main table, SQL Server does not insert anything into the history table. But the field CreatedBy records who created the record, because of the default constraint. But if/when the record gets updated, SQL Server inserts a record into the associated history table. The key idea here is that the name of the logged-in user who made the change is recorded into the history table, i.e. the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.



        That's almost what we want, but not quite - it's one change behind. E.g. if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted. However, all the information is there - it just needs to be unravelled. Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table). However, there's no record in the history table for the originally inserted version of the record. In that case we retrieve the CreatedBy field.



        This seems to provide a watertight auditing solution. Even if a user edits the field CreatedBy, the edit will be recorded in the history table. For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.



        Deleted records



        The query above does not show who deleted records from the main table. This can be retrieved using the following (could be simplified?):





        SELECT 
        d.EmployeeID, d.LoggedInUser AS DeletedBy,
        d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
        FROM
        (SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh -- list of IDs
        OUTER APPLY
        (SELECT TOP 1 * FROM dbo.EmployeeHistory
        WHERE EmployeeID = eh.EmployeeID
        ORDER BY ValidFromUTC DESC) AS d -- last history record, which may be for DELETE
        LEFT JOIN
        dbo.Employees AS e
        ON eh.EmployeeID = e.EmployeeID
        WHERE e.EmployeeID IS NULL -- record is no longer in main table


        Sample table script



        The above examples are based on the table script (history table is created by SQL Server):



        CREATE TABLE dbo.Employees(
        EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
        FirstName NVARCHAR(40) NOT NULL,
        Score INTEGER NULL,
        LoggedInUser AS (SUSER_SNAME()),
        CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
        ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
        ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
        CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
        PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
        )
        WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))


        Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.






        share|improve this answer

























          up vote
          5
          down vote










          up vote
          5
          down vote









          A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):





          SELECT 
          e.EmployeeID, e.FirstName, e.Score,
          COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy,
          e.ValidFromUTC, e.ValidToUTC

          FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
          LEFT JOIN dbo.EmployeeHistory AS eh -- history table
          ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC

          OUTER APPLY
          (SELECT TOP 1 CreatedBy
          FROM dbo.EmployeeHistory
          WHERE EmployeeID = e.EmployeeID
          ORDER BY ValidFromUTC ASC) AS o -- oldest history record

          WHERE e.EmployeeID = 1
          ORDER BY e.ValidFromUTC


          Result set




          • Does not use triggers or user defined functions

          • Requires small changes to the table


          • NB: Note that SQL Server always uses UTC, not local time, for time stamps in temporal tables.





          Explanation:



          Two fields are added to the main and history tables:




          • To record the name of the user who created the record - a normal SQL default:

            CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())

          • To record the name of the current logged in user at any time. A computed column:

            LoggedInUser AS (SUSER_SNAME())


          When a record is inserted into the main table, SQL Server does not insert anything into the history table. But the field CreatedBy records who created the record, because of the default constraint. But if/when the record gets updated, SQL Server inserts a record into the associated history table. The key idea here is that the name of the logged-in user who made the change is recorded into the history table, i.e. the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.



          That's almost what we want, but not quite - it's one change behind. E.g. if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted. However, all the information is there - it just needs to be unravelled. Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table). However, there's no record in the history table for the originally inserted version of the record. In that case we retrieve the CreatedBy field.



          This seems to provide a watertight auditing solution. Even if a user edits the field CreatedBy, the edit will be recorded in the history table. For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.



          Deleted records



          The query above does not show who deleted records from the main table. This can be retrieved using the following (could be simplified?):





          SELECT 
          d.EmployeeID, d.LoggedInUser AS DeletedBy,
          d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
          FROM
          (SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh -- list of IDs
          OUTER APPLY
          (SELECT TOP 1 * FROM dbo.EmployeeHistory
          WHERE EmployeeID = eh.EmployeeID
          ORDER BY ValidFromUTC DESC) AS d -- last history record, which may be for DELETE
          LEFT JOIN
          dbo.Employees AS e
          ON eh.EmployeeID = e.EmployeeID
          WHERE e.EmployeeID IS NULL -- record is no longer in main table


          Sample table script



          The above examples are based on the table script (history table is created by SQL Server):



          CREATE TABLE dbo.Employees(
          EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
          FirstName NVARCHAR(40) NOT NULL,
          Score INTEGER NULL,
          LoggedInUser AS (SUSER_SNAME()),
          CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
          ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
          ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
          CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
          PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
          )
          WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))


          Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.






          share|improve this answer














          A seemingly watertight auditing solution, which gives the name of the logged-in user who made each change (and a great improvement on my previous answer on this page):





          SELECT 
          e.EmployeeID, e.FirstName, e.Score,
          COALESCE (eh.LoggedInUser, o.CreatedBy, e.CreatedBy) AS CreatedOrModifiedBy,
          e.ValidFromUTC, e.ValidToUTC

          FROM dbo.Employees FOR SYSTEM_TIME ALL AS e
          LEFT JOIN dbo.EmployeeHistory AS eh -- history table
          ON e.EmployeeID = eh.EmployeeID AND e.ValidFromUTC = eh.ValidToUTC

          OUTER APPLY
          (SELECT TOP 1 CreatedBy
          FROM dbo.EmployeeHistory
          WHERE EmployeeID = e.EmployeeID
          ORDER BY ValidFromUTC ASC) AS o -- oldest history record

          WHERE e.EmployeeID = 1
          ORDER BY e.ValidFromUTC


          Result set




          • Does not use triggers or user defined functions

          • Requires small changes to the table


          • NB: Note that SQL Server always uses UTC, not local time, for time stamps in temporal tables.





          Explanation:



          Two fields are added to the main and history tables:




          • To record the name of the user who created the record - a normal SQL default:

            CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME())

          • To record the name of the current logged in user at any time. A computed column:

            LoggedInUser AS (SUSER_SNAME())


          When a record is inserted into the main table, SQL Server does not insert anything into the history table. But the field CreatedBy records who created the record, because of the default constraint. But if/when the record gets updated, SQL Server inserts a record into the associated history table. The key idea here is that the name of the logged-in user who made the change is recorded into the history table, i.e. the contents of field LoggedInUser in the main table (which always contains the name of who is logged in to the connection) is saved into the field LoggedInUser in the history table.



          That's almost what we want, but not quite - it's one change behind. E.g. if user Dave inserted the record, but user Andrew made the first update, "Andrew" is recorded as the user name in the history table, next to the original contents of the record that Dave inserted. However, all the information is there - it just needs to be unravelled. Joining the system generated fields for ROW START and ROW END, we get the user who made the change (from the previous record in the history table). However, there's no record in the history table for the originally inserted version of the record. In that case we retrieve the CreatedBy field.



          This seems to provide a watertight auditing solution. Even if a user edits the field CreatedBy, the edit will be recorded in the history table. For that reason, we recover the oldest value for CreatedBy from the history table, instead of the current value from the main table.



          Deleted records



          The query above does not show who deleted records from the main table. This can be retrieved using the following (could be simplified?):





          SELECT 
          d.EmployeeID, d.LoggedInUser AS DeletedBy,
          d.CreatedBy, d.ValidFromUTC, d.ValidToUTC AS DeletedAtUTC
          FROM
          (SELECT EmployeeID FROM dbo.EmployeeHistory GROUP BY EmployeeID) AS eh -- list of IDs
          OUTER APPLY
          (SELECT TOP 1 * FROM dbo.EmployeeHistory
          WHERE EmployeeID = eh.EmployeeID
          ORDER BY ValidFromUTC DESC) AS d -- last history record, which may be for DELETE
          LEFT JOIN
          dbo.Employees AS e
          ON eh.EmployeeID = e.EmployeeID
          WHERE e.EmployeeID IS NULL -- record is no longer in main table


          Sample table script



          The above examples are based on the table script (history table is created by SQL Server):



          CREATE TABLE dbo.Employees(
          EmployeeID INT /*IDENTITY(1,1)*/ NOT NULL,
          FirstName NVARCHAR(40) NOT NULL,
          Score INTEGER NULL,
          LoggedInUser AS (SUSER_SNAME()),
          CreatedBy NVARCHAR(128) NOT NULL DEFAULT (SUSER_SNAME()),
          ValidFromUTC DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT SYSUTCDATETIME(),
          ValidToUTC DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2),
          CONSTRAINT PK_Employees PRIMARY KEY CLUSTERED (EmployeeID ASC),
          PERIOD FOR SYSTEM_TIME (ValidFromUTC, ValidToUTC)
          )
          WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ))


          Edit: (2018/11/19) Added default constraints against the system_time fields, which is considered by some to be best practice, and helps if you're adding system-versioning to an existing table.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 19 at 19:01

























          answered Mar 23 at 17:08









          DaveBoltman

          621617




          621617
























              up vote
              3
              down vote













              EDIT: See my much better answer elsewhere on this page



              My solution does not need triggers. I have a computed column in the main table which always contains the logged in user, e.g.



              CREATE TABLE dbo.Employees(
              EmployeeID INT NOT NULL,
              FirstName sysname NOT NULL,
              ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
              ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
              LoggedInUser AS (SUSER_SNAME()), --<<-- computed column


              ... etc.



              The field LoggedInUser always contains the name of the currently logged in user in every record, and is thus saved into the history table at the time any change was made to any record.



              Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record. But in the history table it gets frozen at the point the change was made, which is very useful, (although it records the user at the end of the period, not the start).



              Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.



              Main (current) table:
              Main table



              History table:
              History table



              Of course in the history table, it records who changed the record from that state, not to that state, i.e. the logged in user at the end of the validity period. It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.



              Any ideas about how to improve this would be welcome, e.g. how to record who made the change at the start of each validity period in the history table. I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.



              Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here, which uses a trigger.






              share|improve this answer























              • Thx for sharing your solution, that was very usefull for me.
                – user1481065
                Feb 9 at 6:56















              up vote
              3
              down vote













              EDIT: See my much better answer elsewhere on this page



              My solution does not need triggers. I have a computed column in the main table which always contains the logged in user, e.g.



              CREATE TABLE dbo.Employees(
              EmployeeID INT NOT NULL,
              FirstName sysname NOT NULL,
              ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
              ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
              LoggedInUser AS (SUSER_SNAME()), --<<-- computed column


              ... etc.



              The field LoggedInUser always contains the name of the currently logged in user in every record, and is thus saved into the history table at the time any change was made to any record.



              Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record. But in the history table it gets frozen at the point the change was made, which is very useful, (although it records the user at the end of the period, not the start).



              Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.



              Main (current) table:
              Main table



              History table:
              History table



              Of course in the history table, it records who changed the record from that state, not to that state, i.e. the logged in user at the end of the validity period. It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.



              Any ideas about how to improve this would be welcome, e.g. how to record who made the change at the start of each validity period in the history table. I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.



              Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here, which uses a trigger.






              share|improve this answer























              • Thx for sharing your solution, that was very usefull for me.
                – user1481065
                Feb 9 at 6:56













              up vote
              3
              down vote










              up vote
              3
              down vote









              EDIT: See my much better answer elsewhere on this page



              My solution does not need triggers. I have a computed column in the main table which always contains the logged in user, e.g.



              CREATE TABLE dbo.Employees(
              EmployeeID INT NOT NULL,
              FirstName sysname NOT NULL,
              ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
              ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
              LoggedInUser AS (SUSER_SNAME()), --<<-- computed column


              ... etc.



              The field LoggedInUser always contains the name of the currently logged in user in every record, and is thus saved into the history table at the time any change was made to any record.



              Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record. But in the history table it gets frozen at the point the change was made, which is very useful, (although it records the user at the end of the period, not the start).



              Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.



              Main (current) table:
              Main table



              History table:
              History table



              Of course in the history table, it records who changed the record from that state, not to that state, i.e. the logged in user at the end of the validity period. It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.



              Any ideas about how to improve this would be welcome, e.g. how to record who made the change at the start of each validity period in the history table. I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.



              Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here, which uses a trigger.






              share|improve this answer














              EDIT: See my much better answer elsewhere on this page



              My solution does not need triggers. I have a computed column in the main table which always contains the logged in user, e.g.



              CREATE TABLE dbo.Employees(
              EmployeeID INT NOT NULL,
              FirstName sysname NOT NULL,
              ValidFrom DATETIME2(7) GENERATED ALWAYS AS ROW START NOT NULL,
              ValidTo DATETIME2(7) GENERATED ALWAYS AS ROW END NOT NULL,
              LoggedInUser AS (SUSER_SNAME()), --<<-- computed column


              ... etc.



              The field LoggedInUser always contains the name of the currently logged in user in every record, and is thus saved into the history table at the time any change was made to any record.



              Of course, that's not very useful in the main table, as it doesn't show who made the last change for each record. But in the history table it gets frozen at the point the change was made, which is very useful, (although it records the user at the end of the period, not the start).



              Note that as a computed column, LoggedInUser must be nullable, and therefore the corresponding column in the history table must be as well.



              Main (current) table:
              Main table



              History table:
              History table



              Of course in the history table, it records who changed the record from that state, not to that state, i.e. the logged in user at the end of the validity period. It works for deletes as well, but the SQL Server temporal table system does not insert a record in the history table for inserts.



              Any ideas about how to improve this would be welcome, e.g. how to record who made the change at the start of each validity period in the history table. I have an idea involving another calculated field in the main table, which uses a UDF to get the user who made the last change in the history table.



              Edit: I found a lot of inspiration from @Aaron Bertrand's excellent article here, which uses a trigger.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Mar 23 at 17:10

























              answered Feb 1 at 13:29









              DaveBoltman

              621617




              621617












              • Thx for sharing your solution, that was very usefull for me.
                – user1481065
                Feb 9 at 6:56


















              • Thx for sharing your solution, that was very usefull for me.
                – user1481065
                Feb 9 at 6:56
















              Thx for sharing your solution, that was very usefull for me.
              – user1481065
              Feb 9 at 6:56




              Thx for sharing your solution, that was very usefull for me.
              – user1481065
              Feb 9 at 6:56










              up vote
              2
              down vote













              In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change. And don't read that statement as me having some sort of insider knowledge that that situation may change in the future; I don't know anything about it. If you need that information, you will need to record it in row. A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.






              share|improve this answer

























                up vote
                2
                down vote













                In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change. And don't read that statement as me having some sort of insider knowledge that that situation may change in the future; I don't know anything about it. If you need that information, you will need to record it in row. A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.






                share|improve this answer























                  up vote
                  2
                  down vote










                  up vote
                  2
                  down vote









                  In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change. And don't read that statement as me having some sort of insider knowledge that that situation may change in the future; I don't know anything about it. If you need that information, you will need to record it in row. A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.






                  share|improve this answer












                  In the current implementation of temporal tables, it records only time based information and nothing else about the session that made the change. And don't read that statement as me having some sort of insider knowledge that that situation may change in the future; I don't know anything about it. If you need that information, you will need to record it in row. A classic approach for doing that is to use a trigger that fires on DML operations and maintains that value on behalf of the user.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Jan 12 '17 at 0:29









                  Ben Thul

                  22k32445




                  22k32445






















                      up vote
                      0
                      down vote













                      Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.



                      This would show who modified the table and thus created the history record. As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.



                      This would then also be in the history table each time the record is modified.






                      share|improve this answer





















                      • This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                        – Aaron Bertrand
                        Jan 12 '17 at 18:57








                      • 2




                        I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                        – fran.tonkovic
                        Jan 24 '17 at 15:19

















                      up vote
                      0
                      down vote













                      Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.



                      This would show who modified the table and thus created the history record. As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.



                      This would then also be in the history table each time the record is modified.






                      share|improve this answer





















                      • This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                        – Aaron Bertrand
                        Jan 12 '17 at 18:57








                      • 2




                        I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                        – fran.tonkovic
                        Jan 24 '17 at 15:19















                      up vote
                      0
                      down vote










                      up vote
                      0
                      down vote









                      Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.



                      This would show who modified the table and thus created the history record. As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.



                      This would then also be in the history table each time the record is modified.






                      share|improve this answer












                      Another option that I was thinking about to solve this issue is to have a LastModifiedBy field in your base temporal table that is filled when the row is saved or updated.



                      This would show who modified the table and thus created the history record. As Aaron mentioned above, you could do it in a trigger, but my thought is to have it decided before it gets to the insert/update and put the value in the LastModifiedBy field at the time the record is updated.



                      This would then also be in the history table each time the record is modified.







                      share|improve this answer












                      share|improve this answer



                      share|improve this answer










                      answered Jan 12 '17 at 17:19









                      xDJR1875

                      19014




                      19014












                      • This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                        – Aaron Bertrand
                        Jan 12 '17 at 18:57








                      • 2




                        I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                        – fran.tonkovic
                        Jan 24 '17 at 15:19




















                      • This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                        – Aaron Bertrand
                        Jan 12 '17 at 18:57








                      • 2




                        I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                        – fran.tonkovic
                        Jan 24 '17 at 15:19


















                      This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                      – Aaron Bertrand
                      Jan 12 '17 at 18:57






                      This would work if they can control all data access through stored procedures and/or have access to the application code that issues the updates. Many can't.
                      – Aaron Bertrand
                      Jan 12 '17 at 18:57






                      2




                      2




                      I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                      – fran.tonkovic
                      Jan 24 '17 at 15:19






                      I made a (not yet production ready) solution using a LastModifiedBy field and utilizing the new SESSION_CONTEXT feature. I created a default constraint on the field with the value SESSION_CONTEXT('UserID') which I set while opening the connection. It works OK but can be overwritten if someone explicitly states the value.
                      – fran.tonkovic
                      Jan 24 '17 at 15:19




















                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.





                      Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                      Please pay close attention to the following guidance:


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f41602579%2fsql-server-2016-temporal-table-how-to-identify-the-user%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Wiesbaden

                      Marschland

                      Dieringhausen