{"id":178,"date":"2006-11-03T00:00:00","date_gmt":"2006-11-03T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/pop-rivetts-sql-server-faq-no-5-pop-on-the-audit-trail\/"},"modified":"2021-08-24T13:40:48","modified_gmt":"2021-08-24T13:40:48","slug":"pop-rivetts-sql-server-faq-no-5-pop-on-the-audit-trail","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/pop-rivetts-sql-server-faq-no-5-pop-on-the-audit-trail\/","title":{"rendered":"Pop Rivett&#8217;s SQL Server FAQ No.5: Pop on the Audit Trail"},"content":{"rendered":"<h2>Pop does SQL Server auditing<\/h2>\n<p>&#160;<img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/301-image001a.jpg\" alt=\"301-image001a.jpg\" \/><\/p>\n<p><em>Good morning Pop, I see the turnips are doing well<\/em><\/p>\n<p>Well, God bless you and keep you Miss, they do well, but we could do with a drop of rain. But do tell me, Miss why the long face?<\/p>\n<p><em>Oh Pop&#8230;such a dreadful thing! I&#8217;ve just been checking through the accounts of the Womens&#8217; Institute and someone has hacked in and removed everything we had in savings for the new Village Hall!<\/em><\/p>\n<p>Lorks a&#8217;mercy, missy&#8230;but at least your audit trail will show up how it happened and who it was that did it?<\/p>\n<p><em>What audit-tail?! [<b>Subsiding into sobs<\/b>] We haven&#8217;t got such a thing. The vicar&#8217;s son put the system together as a college project. Frankly, it&#8217;s lucky that the tables even have primary keys<\/em>.<\/p>\n<p>Well, when IT people talk of audit trails, they refer to keeping an auditable account of who changed the data in a table, when, from what and to what. Basically, an audit trail is a way of tracking the details of all changes to data in certain database tables &#8211; usually in order to check the integrity of financial transactions. It provides a record of who has accessed a database and what operations he or she has performed during a given period of time.<\/p>\n<p>When data is financial, this is often built into the system. Records are never updated or deleted, merely superseded, or &#8216;terminated&#8217;, so that databases grow to become a history of all financial transactions. The application programmers are allowed access to the database only through stored procedures.<\/p>\n<p>If you don&#8217;t have the luxury of a properly-designed financial system, as you don&#8217;t by the sounds of it, then you need to devise a means of logging all the transactions that you want to monitor. You might try looking in the database transaction log but the problem with this is that it&#8217;s an indiscriminate record of all transactions, whether data-feeds, cosmetic changes or data modifications &#8211; so it&#8217;s hard to use it to monitor and track down particular financial business transactions.<\/p>\n<p><em>So what&#8217;s to be done then, Pop?!<\/em><\/p>\n<p>Well, by my reckoning, an audit trigger is the way to go for you. Take a look at this&#8230;you simply create one of these triggers for every table that you need to monitor. This trigger will provide an &#8216;audit trail&#8217; of all changes made to a table. In the line <b>select @TableName = &#8216;trigtest&#8217;<\/b> you simply swap <b>trigrest<\/b> for the name of your table, and then the trigger will monitor all changes to that table.<\/p>\n<p>It will place a record of these changes in a table called <b>Audit<\/b>. It records in <b>Audit <\/b>details of all inserted, deleted, changed columns and the old\/new values of the fdata that was altered in the table that the trigger protects. Anyway, here is the code&#8230;notice that it will put out an error message if there is no primary key on the table as the routine would then be unable to identify the affected rows&#8230;so it&#8217;s lucky the vicar&#8217;s son got that right at least!<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\"><em>&#8212; Set up the tables<br \/>&#8212; Firstly, we create the audit table.<br \/>&#8212; There will only need to be one of these in a database<\/p>\n<p>IF NOT EXISTS<br \/>      (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[Audit]&#8217;<\/em><em>) <br \/>&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;AND OBJECTPROPERTY(id, N&#8217;IsUserTable&#8217;) = 1<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; CREATE TABLE <\/em><em>Audit <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (Type CHAR(1<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName VARCHAR(128<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PK VARCHAR(1000<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FieldName VARCHAR(128<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldValue VARCHAR(1000<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; NewValue VARCHAR(1000<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UpdateDate datetime<\/em><em>, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UserName VARCHAR(128<\/em><em>))<br \/><\/em><em>GO<\/p>\n<p><\/em><em>&#8212; now we will illustrate the use of this tool<br \/>&#8212; by creating a dummy test table called TrigTest. <\/p>\n<p>IF EXISTS<br \/>   (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[trigtest]&#8217;<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND OBJECTPROPERTY(id, N&#8217;IsUserTable&#8217;) = 1<\/em><em>)<br \/>DROP TABLE <\/em><em>[dbo].[trigtest]<br \/>GO<br \/>CREATE TABLE <\/em><em>trigtest <br \/>&#160;&#160;&#160;&#160;&#160;&#160; (i INT <\/em><em>NOT NULL, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; j INT <\/em><em>NOT NULL, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; s VARCHAR(10<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; t VARCHAR(10<\/em><em>))<br \/><\/em><em>GO<\/p>\n<p><\/em><em>&#8211;note that for this system to work there must be a primary key<br \/>&#8211;to the table but then a table without a primary key<br \/>&#8211;isn&#8217;t really a table is it?<br \/>ALTER TABLE trigtest ADD CONSTRAINT pk PRIMARY KEY (i, j<\/em><em>)<br \/><\/em><em>GO<\/p>\n<p><\/em><em>&#8211;and now create the trigger itself. This has to be created for every<br \/>-table you want to monitor<\/p>\n<p>CREATE TRIGGER tr_trigtest ON trigtest FOR INSERT, UPDATE, <\/em><em>DELETE<br \/>AS<\/p>\n<p>DECLARE @bit INT <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @field INT <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @maxfield INT <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @char INT <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @fieldname VARCHAR(128<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @TableName VARCHAR(128<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @PKCols VARCHAR(1000<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @sql VARCHAR(2000<\/em><em>), <br \/>&#160;&#160;&#160;&#160;&#160;&#160; @UpdateDate VARCHAR(21<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @UserName VARCHAR(128<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @Type CHAR(1<\/em><em>) ,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @PKSelect VARCHAR(1000<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; <br \/><\/em><br \/><em>&#8211;You will need to change @TableName to match the table to be audited<br \/><\/em><em>SELECT @TableName = <\/em><em>&#8216;trigtest&#8217;<\/p>\n<p><\/em><em>&#8212; date and user<br \/>SELECT&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @UserName = SYSTEM_USER <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160; @UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112<\/em><em>) <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; &#8216; + CONVERT(VARCHAR(12), GETDATE(), 114<\/em><em>)<\/p>\n<p><\/em><em>&#8212; Action<br \/>IF EXISTS (SELECT * FROM inserted<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; IF EXISTS (SELECT * FROM deleted<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT @Type = <\/em><em>&#8216;U&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; <\/em><em>ELSE<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT @Type = <\/em><em>&#8216;I&#8217;<br \/><\/em><em>ELSE<br \/>&#160;&#160;&#160;&#160;&#160;&#160; SELECT @Type = <\/em><em>&#8216;D&#8217;<\/p>\n<p><\/em><em>&#8212; get list of columns<br \/>SELECT * INTO #ins FROM <\/em><em>inserted<br \/>SELECT * INTO #del FROM <\/em><em>deleted<br \/><\/em><br \/><em>&#8212; Get primary key columns for full outer join<br \/><\/em><em>SELECT @PKCols = COALESCE(@PKCols + &#8216; and&#8217;, &#8216; on&#8217;<\/em><em>) <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; i.&#8217; + c.COLUMN_NAME + &#8216; = d.&#8217; + <\/em><em>c.COLUMN_NAME<br \/>&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk <\/em><em>,<\/p>\n<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <\/em><em>INFORMATION_SCHEMA.KEY_COLUMN_USAGE c<br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; pk.TABLE_NAME = <\/em><em>@TableName<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; CONSTRAINT_TYPE = <\/em><em>&#8216;PRIMARY KEY&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; c.TABLE_NAME = <\/em><em>pk.TABLE_NAME<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; c.CONSTRAINT_NAME = <\/em><em>pk.CONSTRAINT_NAME<\/p>\n<p><\/em><em>&#8212; Get primary key select for insert<br \/>SELECT @PKSelect = COALESCE(@PKSelect+&#8217;+&#8217;,&#8221;<\/em><em>) <br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8221;'&lt;&#8216; + <\/em><em>COLUMN_NAME <br \/>&#160;&#160;&#160;&#160;&#160;&#160; + <\/em><em>&#8216;=&#8221;+convert(varchar(100),<br \/>coalesce(i.&#8217; + COLUMN_NAME +&#8217;,d.&#8217; + COLUMN_NAME + <\/em><em>&#8216;))+&#8221;&gt;&#8221;&#8217; <br \/>&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk <\/em><em>,<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <\/em><em>INFORMATION_SCHEMA.KEY_COLUMN_USAGE c<br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; pk.TABLE_NAME = <\/em><em>@TableName<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; CONSTRAINT_TYPE = <\/em><em>&#8216;PRIMARY KEY&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; c.TABLE_NAME = <\/em><em>pk.TABLE_NAME<br \/>&#160;&#160;&#160;&#160;&#160;&#160; AND&#160;&#160;&#160;&#160; c.CONSTRAINT_NAME = <\/em><em>pk.CONSTRAINT_NAME<\/p>\n<p>IF @PKCols IS <\/em><em>NULL<br \/><\/em><em>BEGIN<br \/>&#160;&#160;&#160;&#160;&#160;&#160; RAISERROR(&#8216;no PK on table %s&#8217;, 16, -1, @TableName<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; <\/em><em>RETURN<br \/>END<\/p>\n<p>SELECT&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @field = 0<\/em><em>, <br \/>&#160;&#160;&#160;&#160;&#160;&#160; @maxfield = MAX(ORDINAL_POSITION<\/em><em>) <br \/>&#160;&#160;&#160;&#160;&#160;&#160; FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = <\/em><em>@TableName<br \/>WHILE @field &lt; <\/em><em>@maxfield<br \/><\/em><em>BEGIN<br \/>&#160;&#160;&#160;&#160;&#160;&#160; SELECT @field = MIN(ORDINAL_POSITION<\/em><em>) <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM <\/em><em>INFORMATION_SCHEMA.COLUMNS <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE TABLE_NAME = <\/em><em>@TableName <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ORDINAL_POSITION &gt; <\/em><em>@field<br \/>&#160;&#160;&#160;&#160;&#160;&#160; SELECT @bit = (@field &#8211; 1 )% 8 + <\/em><em>1<br \/>&#160;&#160;&#160;&#160;&#160;&#160; SELECT @bit = POWER(2,@bit &#8211; 1<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; SELECT @char = ((@field &#8211; 1) \/ 8) + <\/em><em>1<br \/>&#160;&#160;&#160;&#160;&#160;&#160; IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) &amp; @bit &gt; 0<br \/>OR @Type IN (&#8216;I&#8217;,&#8217;D&#8217;<\/em><em>)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; <\/em><em>BEGIN<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT @fieldname = <\/em><em>COLUMN_NAME <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM <\/em><em>INFORMATION_SCHEMA.COLUMNS <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE TABLE_NAME = <\/em><em>@TableName <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND ORDINAL_POSITION = <\/em><em>@field<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT @sql = <\/em><em>&#8216;<br \/>insert Audit (&#160;&#160;&#160; Type, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; TableName, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PK, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FieldName, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldValue, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; NewValue, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UpdateDate, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UserName)<br \/>select &#8221;&#8217; + @Type + <\/em><em>&#8221;&#8217;,&#8221;&#8217; <br \/><\/em>&#160;&#160;&#160;&#160;&#160;&#160; + @TableName + &#8221;&#8217;,&#8217; + @PKSelect<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216;,&#8221;&#8217; + @fieldname + &#8221;&#8221;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216;,convert(varchar(1000),d.&#8217; + @fieldname + &#8216;)&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216;,convert(varchar(1000),i.&#8217; + @fieldname + &#8216;)&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216;,&#8221;&#8217; + @UpdateDate + &#8221;&#8221;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216;,&#8221;&#8217; + @UserName + &#8221;&#8221;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; from #ins i full outer join #del d&#8217;<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + @PKCols<br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; where i.&#8217; + @fieldname + &#8216; &lt;&gt; d.&#8217; + @fieldname <br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; or (i.&#8217; + @fieldname + &#8216; is null and&#160; d.&#8217;<br \/>+ @fieldname<br \/>+ &#8216; is not null)&#8217; <br \/>&#160;&#160;&#160;&#160;&#160;&#160; + &#8216; or (i.&#8217; + @fieldname + &#8216; is not null and&#160; d.&#8217; <br \/>+ @fieldname<br \/>+ &#8216; is null)&#8217; <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; EXEC (@sql)<br \/>&#160;&#160;&#160;&#160;&#160;&#160; END<br \/>END<\/p>\n<p>GO<\/p>\n<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<\/p>\n<p>&#8211;now we can test the trigger out <\/p>\n<p>INSERT trigtest SELECT 1,1,&#8217;hi&#8217;, &#8216;bye&#8217;<\/p>\n<p>INSERT trigtest SELECT 2,2,&#8217;hi&#8217;, &#8216;bye&#8217;<\/p>\n<p>INSERT trigtest SELECT 3,3,&#8217;hi&#8217;, &#8216;bye&#8217;<\/p>\n<p>SELECT * FROM Audit<\/p>\n<p>SELECT * FROM trigtest<\/p>\n<p>UPDATE trigtest SET s = &#8216;hibye&#8217; WHERE i &lt;&gt; 1<\/p>\n<p>UPDATE trigtest SET s = &#8216;bye&#8217; WHERE i = 1<\/p>\n<p>UPDATE trigtest SET s = &#8216;bye&#8217; WHERE i = 1<\/p>\n<p>UPDATE trigtest SET t = &#8216;hi&#8217; WHERE i = 1<\/p>\n<p>SELECT * FROM Audit<\/p>\n<p>SELECT * FROM trigtest<\/p>\n<p>DELETE trigtest<\/p>\n<p>SELECT * FROM Audit<\/p>\n<p>SELECT * FROM trigtest<\/p>\n<p>GO<\/p>\n<p>DROP TABLE Audit<\/p>\n<p>GO<\/p>\n<p>DROP TABLE trigtest<\/p>\n<p>GO<\/p>\n<p><em>Why Pop, you&#8217;re a life saver!<\/em><\/p>\n<p><em><\/em><\/p>\n<p>That&#8217;s quite all right, missy. Just don&#8217;t forget that there is, of course, an overhead to this technique. Also, bear in mind the potential <a href=\"http:\/\/www.simple-talk.com\/community\/forums\/thread\/2130.aspx#2149\">problems<\/a> of using the @@Identity variable when you have triggers on tables with <b>IDENTITY<\/b> columns.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pop provides a cunning, trigger-based technique for auditing the activity on SQL Server tables&hellip;<\/p>\n","protected":false},"author":143519,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4492,4490,4493,4491,4170,4150,4151],"coauthors":[],"class_list":["post-178","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-audit-table","tag-audit-trail","tag-audit-triggers","tag-auditing","tag-database-administration","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/143519"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=178"}],"version-history":[{"count":3,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/178\/revisions"}],"predecessor-version":[{"id":92289,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/178\/revisions\/92289"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=178"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=178"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=178"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}