I am examining a third party SQL Server 2008 database. In this database, there are 2 columns CREATED_DATETIME and UPDATED_DATETIME, which are present in majority of the tables, but probably not all.
I want to find the minimum and maximum value of these 2 columns across all tables in the database which have these 2 columns. That will give me a fair idea that the data in the database is from which period to which period.
How can I write such a query?
Something like the following should work
DECLARE #C1 AS CURSOR,
#TABLE_SCHEMA SYSNAME,
#TABLE_NAME SYSNAME,
#HasCreated BIT,
#HasUpdated BIT,
#MaxDate DATETIME,
#MinDate DATETIME,
#SQL NVARCHAR(MAX)
SET #C1 = CURSOR FAST_FORWARD FOR
SELECT TABLE_SCHEMA,
TABLE_NAME,
COUNT(CASE
WHEN COLUMN_NAME = 'CREATED_DATETIME' THEN 1
END) AS HasCreated,
COUNT(CASE
WHEN COLUMN_NAME = 'UPDATED_DATETIME' THEN 1
END) AS HasUpdated
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME IN ( 'CREATED_DATETIME', 'UPDATED_DATETIME' )
GROUP BY TABLE_SCHEMA,
TABLE_NAME
OPEN #C1;
FETCH NEXT FROM #C1 INTO #TABLE_SCHEMA , #TABLE_NAME , #HasCreated , #HasUpdated ;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = N'
SELECT #MaxDate = MAX(D),
#MinDate = MIN(D)
FROM ' + QUOTENAME(#TABLE_SCHEMA) + '.' + QUOTENAME(#TABLE_NAME) + N'
CROSS APPLY (VALUES ' +
CASE WHEN #HasCreated = 1 THEN N'(CREATED_DATETIME),' ELSE '' END +
CASE WHEN #HasUpdated = 1 THEN N'(UPDATED_DATETIME),' ELSE '' END + N'
(#MaxDate),
(#MinDate)) V(D)
'
EXEC sp_executesql
#SQL,
N'#MaxDate datetime OUTPUT, #MinDate datetime OUTPUT',
#MaxDate = #MaxDate OUTPUT,
#MinDate = #MinDate OUTPUT
FETCH NEXT FROM #C1 INTO #TABLE_SCHEMA , #TABLE_NAME , #HasCreated , #HasUpdated ;
END
SELECT #MaxDate AS [#MaxDate], #MinDate AS [#MinDate]
select MIN(CREATED_DATETIME) MinCREATED_DATETIME_Table1, MAX(CREATED_DATETIME) MaxCREATED_DATETIME_Table1, MIN(CREATED_DATETIME) MinCREATED_DATETIME_Table2, MAX(CREATED_DATETIME) MaxCREATED_DATETIME_Table2 from Table1, Table2
Run this script in SSMS (CTrl+T=text results, F5=execute query):
SET NOCOUNT ON;
SELECT 'SELECT MIN('
+ QUOTENAME(c.COLUMN_NAME)
+ ') AS '
+ QUOTENAME('Min '+c.TABLE_NAME+'.'+c.COLUMN_NAME)
+ ', MAX('
+ QUOTENAME(c.COLUMN_NAME)
+ ') AS '
+ QUOTENAME('Max_'+c.TABLE_NAME+'.'+c.COLUMN_NAME)
+ CHAR(13)
+ 'FROM ' + QUOTENAME(c.TABLE_SCHEMA)+'.'+QUOTENAME(c.TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.COLUMN_NAME IN ('CREATED_DATETIME', 'UPDATED_DATETIME')
ORDER BY c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME;
SET NOCOUNT OFF;
It will generate another script. Execute generated script.
Example (generated script for master database and all tables with low column name > WHERE c.COLUMN_NAME IN (N'low')):
SELECT MIN([low]) AS [Min spt_fallback_dev.low], MAX([low]) AS [Max_spt_fallback_dev.low]
FROM [dbo].[spt_fallback_dev]
SELECT MIN([low]) AS [Min spt_values.low], MAX([low]) AS [Max_spt_values.low]
FROM [dbo].[spt_values]
Run the script mentioned in the link below. You will have to slightly alter as per your requirements.
SCRIPT to Search every Table and Field
i would like to alter a stored procedure so i will not suplly the identity column name
INSERT INTO tablName ..... WHERE IDENTITY_Column = 10
can i tell sql server managment studio to just refer to the IDENTITY column so it will find
it by its type which is (at least in my tables a default) autoincremet PK ID type
You haven't really given enough code for us to see what you are trying to do but from the snippet in the question.
WHERE IDENTITY_Column = 10
You can just use
WHERE $IDENTITY = 10
for that (to filter against an identity column without specifying the name).
If you do actually need to lookup the column name then an easier way, avoiding deprecated views is
SELECT name
FROM sys.identity_columns
WHERE object_id = object_id('dbo.YourTable')
found this information by now .
that is the plain and simple version .
declare #tblName sysname = '______'--<== enter a table name
declare #NameOfIDColumn sysname =
(
SELECT Name
FROM syscolumns
WHERE COLUMNPROPERTY( id ,name, 'IsIdentity') = 1 and OBJECT_NAME(id)= #tblName )
select #NameOfIDColumn AS 'result'
you could add this as an option to display last row of a table soretd by its record#
declare #query VARCHAR(100) = 'Select Top 1 * FROM '+ #tblName +' Order BY ' + #IdentColumnName + ' desc' ;
EXEC (#query);
and to play around or even make it as a test page in a .net project
make this one as a stored proc that will outpout a message to a test page .
declare #tblName sysname = '______'--<== enter a table name
declare #IdentColumnName sysname =
(
SELECT Name
FROM syscolumns
WHERE COLUMNPROPERTY( id ,name, 'IsIdentity') = 1 and OBJECT_NAME(id)= #tblName )
declare #result VARCHAR (50) = #tblName + ' Identity Column is ' + #IdentColumnName;
select #result AS 'result'
and with a shorter version of "idntity column search", by Martin Smith
declare #tblName sysname = '______'--<== enter a table name
declare #IdentColumnName sysname =
(SELECT name FROM sys.identity_columns WHERE object_id = object_id(#TableName))
declare #result VARCHAR (50) = #tblName + ' Identity Column is ' + #IdentColumnName;
select #result AS 'result'
this is related to a table copy trick i was trying to pull via stored procedure.
USE [YourDataBaseName]
GO
/****** Object: StoredProcedure [dbo].[Utils_TableRowCopy] Script Date: 10/03/2012 18:26:58 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Utils_TableRowCopy](
#TableName VARCHAR(50) ,
#RowNumberToCopy INT
)
AS
BEGIN
declare #RowIdentity sysname =
(
SELECT name FROM sys.identity_columns WHERE object_id = object_id(#TableName)
)
DECLARE #columns VARCHAR(5000), #query VARCHAR(8000);
SET #query = '' ;
SELECT #columns =
CASE
WHEN #columns IS NULL THEN column_name
ELSE #columns + ',' + column_name
END
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (
TABLE_NAME = LTRIM(RTRIM(#TableName))
AND
column_name <> LTRIM(RTRIM(#RowIdentity))
);
SET #query = 'INSERT INTO ' + #TableName + ' (' + #columns + ') SELECT ' + #columns + ' FROM ' + #TableName + ' WHERE ' + #RowIdentity + ' = ' + CAST(#RowNumberToCopy AS VARCHAR);
--SELECT SCOPE_IDENTITY();
declare #query2 VARCHAR(100) = ' Select Top 1 * FROM '+ #TableName +' Order BY ' + #RowIdentity + ' desc' ;
EXEC (#query);
EXEC (#query2);
END
I have a fairly basic SQL script to rebuild all the table indexes under various schema within a database. The script seems to work on the 183 indexes I have, but returns the error message
(183 row(s) affected)
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'Group'
Can anyone explain why and provide a solution?
USE RedGateMonitor;
GO
declare #db varchar(150)
declare #tmp TABLE(recnum int IDENTITY (1,1), tableschema varchar(150), tablename varchar(150))
insert #tmp (tableschema, tablename)
SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.tables where TABLE_TYPE = 'BASE TABLE'
ORDER By TABLE_SCHEMA
declare #X int, #table varchar(150), #cmd varchar(500), #schema varchar(150)
set #X = 1
While #X <= (select count(*) from #tmp) BEGIN
set #db = 'RedGateMonitor'
set #table = (select tablename from #tmp where recnum = #X)
set #schema = (select tableschema from #tmp where recnum = #X)
set #cmd = 'ALTER INDEX ALL ON ' + #db + '.' + #schema + '.' + #table + ' REBUILD'
EXECUTE(#cmd)
set #X = #X + 1
END
I agree with both of Mitch's comments:
(1) you should be using an existing solution for this instead of reinventing the wheel.
(2) if you aren't going to follow basic rules for identifiers (e.g. not naming schemas or tables with reserved words), you need to properly escape them. A quick fix would be:
set #cmd = 'ALTER INDEX ALL ON ' + quotename(#db)
+ '.' + quotename(#schema)
+ '.' + Quotename(#table) + ' REBUILD;';
A slightly better fix would be the following, with no need for #temp tables or looping:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'ALTER INDEX ALL ON ' + QUOTENAME(#db)
+ '.' + QUOTENAME(SCHEMA_NAME([schema_id])
+ '.' + QUOTENAME(name) + ' REBUILD;';
EXEC sp_executesql;
But I don't think you need to rebuild all of the indexes on all of the tables in the Red Gate database. Scripts like Ola's will help you be more efficient about which indexes to rebuild, which to reorganize, and which to leave alone.
I want to get all the transactions applied on a specific table in SQL Server 2008.
I found the last time a table was updated using this script:
SELECT OBJECT_NAME(OBJECT_ID) AS DatabaseName, last_user_update,*
FROM sys.dm_db_index_usage_stats
WHERE database_id = DB_ID( 'DBName')
AND OBJECT_ID=OBJECT_ID('tableName')
I want to know all the transactions (Inserts, Updates, Deletes) for that table, and their datetime, and the query applied.
What is the best way to do this?
The only way to do this in a reasonable amount of time is to use a third party tool(as Martin said in first comment) such as ApexSQL Log that can read transaction log and get the information you need.
Note that in order for this to work your database has to be in a full recovery mode because that’s when SQL Server logs full transaction details that can be reconstructed later.
Another option is to investigate how to use undocumented fn_dblog function but this will take you a lot more time and you won’t be able to read detached logs or transaction log backups.
creating a trigger which will create a new table Emp_audit and add new tuples to it whenever any change is made to table employee
create trigger my_trigger on Employees
AFTER INSERT, UPDATE, DELETE
AS
DECLARE #What varchar(30);
DECLARE #Who varchar(30);
DECLARE #for int;
DECLARE #At time;
DECLARE #COUNTI int;
DECLARE #COUNTD int;
select #COUNTI = COUNT(*) from inserted;
select #COUNTD = COUNT(*) from deleted;
set #Who = SYSTEM_USER;
set #At = CURRENT_TIMESTAMP;
if( #COUNTD = 0 and #COUNTI = 1)
begin
set #What = 'insert';
select #for = EmployeeID from inserted i;
end
else
begin
if( #COUNTD = 1 and #COUNTI = 0)
begin
set #What = 'delete';
select #for = EmployeeID from deleted i;
end
else
begin
set #What = 'update';
select #for = EmployeeID from inserted i;
end
end
INSERT INTO EMP_Audit Values (#What, #Who, #for, #At);
You would be much better off setting up auditing for this need rather than trying to extract this information retrospectively from the transaction log.
If you are on Enterprise Edition you could use the built in SQL Server Audit functionality, otherwise it should be relative straight forward to log the desired information via triggers.
You could create your own transaction logs
Step 1: Create your own table for transaction logs
CREATE TABLE [dbo].[TransactionLogs](
[TransactionLogID] [bigint] IDENTITY(1,1) NOT NULL,
[Query] [nvarchar](max) NOT NULL,
[DateCreated] [datetime] NOT NULL,
CONSTRAINT [PK_TransactionLogs] PRIMARY KEY CLUSTERED
(
[TransactionLogID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
Step 2: Create stored procedure that create logs. (Note: Replace YourTablePKColumn with your table primary key column.)
create procedure [dbo].[sp_CreateQueryLogs]
(
#Query nvarchar(max) = null output,
#TableName nvarchar(100),
#YourTablePKColumn nvarchar(30),
#QueryTypeID tinyint --0 insert, 1 update, 2 delete
)
as
begin
declare #object_id bigint, #column_name nvarchar(100), #collation_name nvarchar(50), #column_name_id nvarchar(100) = null, #column_names nvarchar(max) = '', #column_values nvarchar(max) = '', #column_names_create nvarchar(max) = '', #values nvarchar(max) = '', #user_type_id int, #max_length nvarchar(10), #type_name nvarchar(50), #CreateTempTable nvarchar(max) = '', #is_nullable bit, #value nvarchar(max) = ''
create table #tmpValues(ColumnValues nvarchar(max))
insert into #tmpValues(ColumnValues)
exec('select CAST ( ( select * from ' + #TableName + ' where YourTablePKColumn = ' + #YourTablePKColumn + '
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) )')
select #values = ColumnValues from #tmpValues
if #QueryTypeID = 0 --insert
set #Query = 'insert into ' + #TableName + '('
else if #QueryTypeID = 1 --update
set #Query = 'update ' + #TableName + ' set '
else if #QueryTypeID = 2 --dalete
set #Query = 'delete ' + #TableName + ' '
select #object_id = object_id from sys.tables where name = #TableName
if not cursor_status('local','columnCursor') <= -1
begin
close columnCursor;
deallocate columnCursor;
end
declare columnCursor cursor local for
select name, user_type_id, convert(nvarchar(10), max_length), is_nullable from sys.columns where object_id = #object_id order by column_id ;
open columnCursor;
fetch next from columnCursor
into #column_name, #user_type_id, #max_length, #is_nullable;
while ##FETCH_STATUS = 0
begin
select #type_name = name, #collation_name = collation_name from sys.types where user_type_id = #user_type_id
if #column_name_id is null
set #column_name_id = #column_name
else
begin
set #column_names += #column_name + ', '
declare #value_keys_start nvarchar(max) = '<' + #column_name + '>', #value_keys_end nvarchar(max) = '</' + #column_name + '>'
if charindex(#value_keys_start,#values,1) = 0
begin
if #QueryTypeID = 0 --insert
set #column_values += 'null,'
else if #QueryTypeID = 1 --update
set #column_values += #column_name + ' = null,'
end
else
begin
if #QueryTypeID = 0 --insert
if #collation_name is null and not (#type_name like '%date%' or #type_name like '%time%')
set #column_values += substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))) + ','
else if #type_name like '%date%' or #type_name like '%time%'
set #column_values += '''' + replace(substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))),'T',' ') + ''','
else
set #column_values += '''' + replace(substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))),'''','''''') + ''','
else if #QueryTypeID = 1 --update
if #collation_name is null and not (#type_name like '%date%' or #type_name like '%time%')
set #column_values += #column_name + '=' + substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))) + ','
else if #type_name like '%date%' or #type_name like '%time%'
set #column_values += #column_name + '=' + '''' + replace(substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))),'T',' ') + ''','
else
set #column_values += #column_name + '=' + '''' + replace(substring(#values, charindex(#value_keys_start,#values,1) + len(#value_keys_start), charindex(#value_keys_end,#values,1) - (charindex(#value_keys_start,#values,1) + len(#value_keys_start))),'''','''''') + ''','
end
end
fetch next from columnCursor
into #column_name, #user_type_id, #max_length, #is_nullable;
end
if not cursor_status('local','columnCursor') <= -1
begin
close columnCursor;
deallocate columnCursor;
end
if #QueryTypeID = 0 --insert
set #Query += substring(#column_names,1,len(#column_names) - 1) + ')
values (' + substring(#column_values,1,len(#column_values) - 1) + ')'
else if #QueryTypeID = 1 --update or delete
set #Query += substring(#column_values,1,len(#column_values) - 1) + ' where YourTablePKColumn = ' + #YourTablePKColumn
else
set #Query += ' where YourTablePKColumn = ' + #YourTablePKColumn
end
Step 3: Created trigger to table you want to have transaction logs
CREATE TRIGGER trg_MyTrigger ON YouTableName
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare #TableName nvarchar(100) = 'YouTableName', #Query nvarchar(max), #QueryTypeID tinyint, #YourTablePKColumn nvarchar(30)
if exists(select * from deleted) and exists(select * from inserted)
begin
set #QueryTypeID = 1
if not cursor_status('local','updatedCursor') <= -1
begin
close updatedCursor;
deallocate updatedCursor;
end
declare updatedCursor cursor local for
select cast(YourTablePKColumn as nvarchar(30)) from inserted;
open updatedCursor;
fetch next from updatedCursor
into #YourTablePKColumn;
while ##FETCH_STATUS = 0
begin
exec dbo.sp_CreateQueryLogs #Query = #Query output, #TableName = #TableName, #YourTablePKColumn = #YourTablePKColumn, #QueryTypeID = #QueryTypeID
insert into TransactionLogs
(Query, DateCreated)
values (#Query,getdate())
fetch next from updatedCursor
into #YourTablePKColumn;
end
if not cursor_status('local','updatedCursor') <= -1
begin
close updatedCursor;
deallocate updatedCursor;
end
end
else if exists(select * from deleted) and not exists(select * from inserted)
begin
set #QueryTypeID = 2
if not cursor_status('local','deletedCursor') <= -1
begin
close deletedCursor;
deallocate deletedCursor;
end
declare deletedCursor cursor local for
select cast(YourTablePKColumn as nvarchar(30)) from deleted;
open deletedCursor;
fetch next from deletedCursor
into #YourTablePKColumn;
while ##FETCH_STATUS = 0
begin
exec dbo.sp_CreateQueryLogs #Query = #Query output, #TableName = #TableName, #YourTablePKColumn = #YourTablePKColumn, #QueryTypeID = #QueryTypeID
insert into TransactionLogs
(Query, DateCreated)
values (#Query,getdate())
fetch next from deletedCursor
into #YourTablePKColumn;
end
if not cursor_status('local','deletedCursor') <= -1
begin
close deletedCursor;
deallocate deletedCursor;
end
end
else
begin
set #QueryTypeID = 0
if not cursor_status('local','insertedCursor') <= -1
begin
close insertedCursor;
deallocate insertedCursor;
end
declare insertedCursor cursor local for
select cast(YourTablePKColumn as nvarchar(30)) from inserted;
open insertedCursor;
fetch next from insertedCursor
into #YourTablePKColumn;
while ##FETCH_STATUS = 0
begin
exec dbo.sp_CreateQueryLogs #Query = #Query output, #TableName = #TableName, #YourTablePKColumn = #YourTablePKColumn, #QueryTypeID = #QueryTypeID
insert into TransactionLogs
(Query, DateCreated)
values (#Query,getdate())
fetch next from insertedCursor
into #YourTablePKColumn;
end
if not cursor_status('local','insertedCursor') <= -1
begin
close insertedCursor;
deallocate insertedCursor;
end
end
END
GO
For entities in my application, I am planning to log common meta-data like DateCreated, DateModified, IPAddress, etc. Does it make sense to add these columns in entity tables or is it better to have a single table for logging meta-data where each row has link back to the item that it corresponds to? Later for purpose of reporting and analysis, I can create desired views.
I use a special query that adds all these common columns (I call them audit columns) to all tables (using a cursor going over the information schema), which makes it easy to apply them to new databases.
My columns are (SQL Server specific):
RowId UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
Created DATETIME NOT NULL DEFAULT (GETDATE()),
Creator NVARCHAR(256) NOT NULL DEFAULT(SUSER_SNAME())
RowStamp TIMESTAMP NOT NULL
Now, in a fully normalised schema, I'd only need RowId, which would link to an Audit table containing the other rows. In fact, on reflection, I almost wish I had gone down this route - mainly because it makes the tables ugly (in fact I leave these columns out of database schema diagrams).
However, when dealing with very large data sets, you do get a performance boost from having this data within the table, and I haven't experienced any problems with this system to date.
Edit: Might as well post the code to add the audit columns:
DECLARE AuditCursor CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME from INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)), 'IsMSShipped') = 0
AND TABLE_NAME NOT IN ('sysdiagrams')
AND TABLE_NAME NOT LIKE 'dt_%'
-- NB: you could change the above to only do it for certain tables
OPEN AuditCursor
DECLARE #schema varchar(128), #table varchar(128)
FETCH NEXT FROM AuditCursor INTO #schema, #table
WHILE ##FETCH_STATUS -1
BEGIN
PRINT '* Adding audit columns to [' + #schema + '].[' + #table + ']...'
IF NOT EXISTS (SELECT NULL
FROM information_schema.columns
WHERE table_schema = #schema
AND table_name = #table
AND column_name = 'Created')
BEGIN
DECLARE #sql_created varchar(max)
SELECT #sql_created = 'ALTER TABLE [' + #schema + '].[' + #table + '] ADD [Created] DATETIME NOT NULL CONSTRAINT [DF_' + #table + '_Created] DEFAULT (GETDATE())'
EXEC ( #sql_created )
PRINT ' - Added Created'
END
ELSE
PRINT ' - Created already exists, skipping'
IF NOT EXISTS (SELECT NULL
FROM information_schema.columns
WHERE table_schema = #schema
AND table_name = #table
AND column_name = 'Creator')
BEGIN
DECLARE #sql_creator varchar(max)
SELECT #sql_creator = 'ALTER TABLE [' + #schema + '].[' + #table + '] ADD [Creator] VARCHAR(256) NOT NULL CONSTRAINT [DF_' + #table + '_Creator] DEFAULT (SUSER_SNAME())'
EXEC ( #sql_creator )
PRINT ' - Added Creator'
END
ELSE
PRINT ' - Creator already exists, skipping'
IF NOT EXISTS (SELECT NULL
FROM information_schema.columns
WHERE table_schema = #schema
AND table_name = #table
AND column_name = 'RowId')
BEGIN
DECLARE #sql_rowid varchar(max)
SELECT #sql_rowid = 'ALTER TABLE [' + #schema + '].[' + #table + '] ADD [RowId] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_' + #table + '_RowId] DEFAULT (NEWID())'
EXEC ( #sql_rowid )
PRINT ' - Added RowId'
END
ELSE
PRINT ' - RowId already exists, skipping'
IF NOT EXISTS (SELECT NULL
FROM information_schema.columns
WHERE table_schema = #schema
AND table_name = #table
AND (column_name = 'RowStamp' OR data_type = 'timestamp'))
BEGIN
DECLARE #sql_rowstamp varchar(max)
SELECT #sql_rowstamp = 'ALTER TABLE [' + #schema + '].[' + #table + '] ADD [RowStamp] ROWVERSION NOT NULL'
EXEC ( #sql_rowstamp )
PRINT ' - Added RowStamp'
END
ELSE
PRINT ' - RowStamp or another timestamp already exists, skipping'
-- basic tamper protection against non-SA users
PRINT ' - setting DENY permission on audit columns'
DECLARE #sql_deny VARCHAR(1000)
SELECT #sql_deny = 'DENY UPDATE ON [' + #schema + '].[' + #table + '] ([Created]) TO [public]'
+ 'DENY UPDATE ON [' + #schema + '].[' + #table + '] ([RowId]) TO [public]'
+ 'DENY UPDATE ON [' + #schema + '].[' + #table + '] ([Creator]) TO [public]'
EXEC (#sql_deny)
PRINT '* Completed processing [' + #schema + '].[' + #table + ']'
FETCH NEXT FROM AuditCursor INTO #schema, #table
END
CLOSE AuditCursor
DEALLOCATE AuditCursor
GO
If you are wanting to keep only the latest information (as in the LAST time it was modified and by what IP address) then put it right in the table. If you are wanting something more like an audit log, where you can see all historical changes, then it should be in a separate table.
This is a classical question about whether you should normalize your database or not. I would say that you should normalize, and only de-normalize if you require it (performance etc).