Log meta-data to be part of entity tables or separate? - mysql

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).

Related

Compare column names on local and remote DB

I have a timed sync that references a remote database. The sync uses "Select *...". There have been some changes to the local DB structure, and now the sync fails of course because the columns are different.
How can I compare the two tables?
To get the local columms, I can do:
SELECT * -- COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
FROM information_schema.columns
WHERE table_name = 'Items'
ORDER BY ordinal_position
And to get the remote columns I can do:
EXECUTE [WebServ].[WebDB].dbo.sp_executesql
N'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH FROM
INFORMATION_SCHEMA.COLUMNS
WHERE table_name like ''ITEMS_Web'''
But how would I put them together so it just shows me which columns have been added on the local table? I guess I just don't know how to get the results from the Execute command into a useable form so I could do a JOIN or something... Forgive my n00bieness. This is MSSQL 2008
I have a stored procedure to modify archive tables for me automatically.
I have modified it for you, leave the #COMMIT_CHANGES=0 and it will print out the alters required to update the 2nd table to match the first.
Be sure you fix the database and owner in the SET statements to match your own database tables.
I have disabled the EXEC that could potentially alter your table in case you accidentally set #COMMIT_CHANGES to 1... un-comment it if you want the #COMMIT_CHANGES flag to actually execute alter codes. If nothing is returned the tables match.
Edit: I am storing the commands in a table for email to myself, but I removed the email code from this example.
This should give a good foundation for anything you need it to do.
DECLARE #TABLENAME VARCHAR(100), #TABLENAME2 VARCHAR(100), #COMMIT_CHANGES BIT, #SSERVERNAME VARCHAR(100)
SET #TABLENAME='database.dbo.ITEMS' --local
SET #TABLENAME2='database.dbo.ITEMS_WEB' --remote
SET #SSERVERNAME = 'WEBSERV' --remote server
SET #COMMIT_CHANGES=0 -- set to 1, and it WILL change your remote table
SET NOCOUNT ON
DECLARE #SQL VARCHAR(MAX)
DECLARE #DB1 SYSNAME, #OWNER1 SYSNAME, #TABLE1 SYSNAME
DECLARE #DB2 SYSNAME, #OWNER2 SYSNAME, #TABLE2 SYSNAME
DECLARE #RECIPIENTS VARCHAR(500), #ENABLEEMAIL BIT
IF #COMMIT_CHANGES = 0 PRINT 'TEST MODE ONLY, CHANGES WILL NOT BE MADE'
-- PARSE TABLE NAME INTO 3 PARTS
SELECT #TABLE1 = PARSENAME(#TABLENAME, 1)
SELECT #OWNER1 = PARSENAME(#TABLENAME, 2)
IF #OWNER1 IS NULL SELECT #OWNER1 = 'DBO'
SELECT #DB1 = PARSENAME(#TABLENAME, 3)
IF #DB1 IS NULL SELECT #DB1 = DB_NAME()
-- PARSE ARCHIVE TABLE NAME INTO 3 PARTS
SELECT #TABLE2 = PARSENAME(#TABLENAME2, 1)
SELECT #OWNER2 = PARSENAME(#TABLENAME2, 2)
IF #OWNER2 IS NULL SELECT #OWNER2 = 'DBO'
SELECT #DB2 = PARSENAME(#TABLENAME2, 3)
IF #DB2 IS NULL SELECT #DB2 = DB_NAME()
-- IF OUR TEMP TABLES EXIST, DROP THEM
IF EXISTS (SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '##T1_MAIN') DROP TABLE ##T1_MAIN
IF EXISTS (SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '##T2_ARCHIVE') DROP TABLE ##T2_ARCHIVE
-- GATHER SCHEMA INFO FOR LIVE TABLE
SET #SQL = 'SELECT TABLE_NAME,
COLUMN_NAME,
DATA_TYPE,
ISNULL(CHARACTER_MAXIMUM_LENGTH,0) AS CHARACTER_MAXIMUM_LENGTH,
ISNULL(NUMERIC_PRECISION,0) AS NUMERIC_PRECISION,
ISNULL(NUMERIC_SCALE,0) AS NUMERIC_SCALE,
IS_NULLABLE,
CAST(0 AS BIT) AS ADD_COLUMN,
CAST(0 AS BIT) AS ALTER_COLUMN
INTO ##T1_MAIN
FROM ' + #DB1 + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''' + #TABLE1 + '''
AND TABLE_SCHEMA = ''' + #OWNER1 + ''' '
--PRINT #SQL
EXEC(#SQL)
-- CHECK IF TABLES EXIST, ELSE SKIP ALL WORK
IF NOT EXISTS (SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '##T1_MAIN')
BEGIN
PRINT #TABLENAME + ' DOES NOT EXIST, EXITING PROC'
GOTO SKIPWORK
END
-- GATHER SCHEMA INFO FOR ARCHIVE TABLE
SET #SQL = 'SELECT TABLE_NAME,
COLUMN_NAME,
DATA_TYPE,
ISNULL(CHARACTER_MAXIMUM_LENGTH,0) AS CHARACTER_MAXIMUM_LENGTH,
ISNULL(NUMERIC_PRECISION,0) AS NUMERIC_PRECISION,
ISNULL(NUMERIC_SCALE,0) AS NUMERIC_SCALE,
IS_NULLABLE,
CAST(0 AS BIT) AS DROP_COLUMN
INTO ##T2_ARCHIVE
FROM ['+#SSERVERNAME+'].' + #DB2 + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''' + #TABLE2 + '''
AND TABLE_SCHEMA = ''' + #OWNER2 + ''' '
--PRINT #SQL
EXEC(#SQL)
-- CHECK IF TABLES EXIST, ELSE SKIP ALL WORK
IF NOT EXISTS (SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '##T2_ARCHIVE')
BEGIN
PRINT #TABLENAME2 + ' DOES NOT EXIST, EXITING PROC'
GOTO SKIPWORK
END
-- FLAG NEW COLUMNS
-- COLUMN IN T1 (LIVE) BUT NOT IN T2 (ARCHIVE)
UPDATE T1 SET ADD_COLUMN = 1
FROM ##T1_MAIN T1
LEFT OUTER JOIN ##T2_ARCHIVE T2
ON T1.COLUMN_NAME = T2.COLUMN_NAME
WHERE T2.COLUMN_NAME IS NULL
-- FLAG REMOVED COLUMNS
-- COLUMN IN T2 (ARCHIVE) BUT NOT IN T1 (LIVE)
-- ** NOT DOING ANYTHING WITH THIS **
UPDATE T2 SET DROP_COLUMN = 1
FROM ##T2_ARCHIVE T2
LEFT OUTER JOIN ##T1_MAIN T1
ON T2.COLUMN_NAME = T1.COLUMN_NAME
WHERE T1.COLUMN_NAME IS NULL
-- FLAG ALTERED COLUMNS
-- ONLY NEED WHERE LIVE DATA LENGTH IS > THAN ARCHIVE DATA LENGTH
-- WE WOULDN'T WANT TO SHRINK A COLUMN AND TRUNCATE A VALUE
UPDATE T1 SET ALTER_COLUMN = 1
FROM ##T1_MAIN T1
JOIN ##T2_ARCHIVE T2
ON T1.COLUMN_NAME = T2.COLUMN_NAME
AND (T1.DATA_TYPE <> T2.DATA_TYPE
OR T1.CHARACTER_MAXIMUM_LENGTH > T2.CHARACTER_MAXIMUM_LENGTH
OR T1.NUMERIC_PRECISION > T2.NUMERIC_PRECISION
OR T1.NUMERIC_SCALE > T2.NUMERIC_SCALE
OR (T1.IS_NULLABLE = 'YES' AND T2.IS_NULLABLE = 'NO'))
DECLARE #COLUMN_NAME VARCHAR(100),
#DATA_TYPE VARCHAR(100),
#CHARACTER_MAXIMUM_LENGTH INT,
#NUMERIC_PRECISION INT,
#NUMERIC_SCALE INT,
#IS_NULLABLE VARCHAR(3)
-- CREATE A TEMP TABLE TO HOLD OUR COMMANDS FOR EMAIL
IF EXISTS (SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '##COMMANDLIST') DROP TABLE ##COMMANDLIST
CREATE TABLE ##COMMANDLIST (sText VARCHAR(1000))
DECLARE ALTER_COLUMN CURSOR LOCAL
FOR
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE
FROM ##T1_MAIN
WHERE ALTER_COLUMN=1
OPEN ALTER_COLUMN
FETCH NEXT FROM ALTER_COLUMN INTO #COLUMN_NAME, #DATA_TYPE, #CHARACTER_MAXIMUM_LENGTH, #NUMERIC_PRECISION, #NUMERIC_SCALE, #IS_NULLABLE
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'ALTER TABLE ' + #DB2 + '.' + #OWNER2 + '.' + #TABLE2 + ' ALTER COLUMN ' + #COLUMN_NAME + ' ' + CASE
WHEN #DATA_TYPE IN ('NCHAR','NVARCHAR','CHAR','VARCHAR') THEN #DATA_TYPE + ' (' + CAST(#CHARACTER_MAXIMUM_LENGTH AS VARCHAR(15)) + ')'
WHEN #DATA_TYPE IN ('TINYINT','SMALLINT','INT','BIGINT','BIT','UNIQUEIDENTIFIER') THEN #DATA_TYPE
WHEN #DATA_TYPE IN ('DECIMAL','MONEY','FLOAT','NUMERIC') THEN #DATA_TYPE + ' (' + CAST(#NUMERIC_PRECISION AS VARCHAR(15)) + ',' + CAST(#NUMERIC_SCALE AS VARCHAR(15)) + ')'
END
+ ' ' +
CASE
WHEN #IS_NULLABLE = 'YES' THEN 'NULL'
ELSE 'NULL'
END
PRINT #SQL
IF #COMMIT_CHANGES = 1
BEGIN
IF(##SERVERNAME <> #sServerName) SET #SQL = 'EXEC(''' + REPLACE(#SQL,'''','''''') + ''') AT ' + QUOTENAME(#sServerName)
EXEC(#SQL)
END
IF #SQL IS NOT NULL INSERT INTO ##COMMANDLIST (sText) SELECT #SQL
FETCH NEXT FROM ALTER_COLUMN INTO #COLUMN_NAME, #DATA_TYPE, #CHARACTER_MAXIMUM_LENGTH, #NUMERIC_PRECISION, #NUMERIC_SCALE, #IS_NULLABLE
END
CLOSE ALTER_COLUMN
DEALLOCATE ALTER_COLUMN
DECLARE ADD_COLUMN CURSOR LOCAL
FOR
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE
FROM ##T1_MAIN
WHERE ADD_COLUMN=1
OPEN ADD_COLUMN
FETCH NEXT FROM ADD_COLUMN INTO #COLUMN_NAME, #DATA_TYPE, #CHARACTER_MAXIMUM_LENGTH, #NUMERIC_PRECISION, #NUMERIC_SCALE, #IS_NULLABLE
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'ALTER TABLE ' + #DB2 + '.' + #OWNER2 + '.' + #TABLE2 + ' ADD ' + #COLUMN_NAME + ' ' + CASE
WHEN #DATA_TYPE IN ('NCHAR','NVARCHAR','CHAR','VARCHAR') THEN #DATA_TYPE + ' (' + CAST(#CHARACTER_MAXIMUM_LENGTH AS VARCHAR(15)) + ')'
WHEN #DATA_TYPE IN ('TINYINT','SMALLINT','INT','BIGINT','BIT','UNIQUEIDENTIFIER') THEN #DATA_TYPE
WHEN #DATA_TYPE IN ('DECIMAL','MONEY','FLOAT','NUMERIC') THEN #DATA_TYPE + ' (' + CAST(#NUMERIC_PRECISION AS VARCHAR(15)) + ',' + CAST(#NUMERIC_SCALE AS VARCHAR(15)) + ')'
END
+ ' ' +
CASE
WHEN #IS_NULLABLE = 'YES' THEN 'NULL'
ELSE 'NOT NULL'
END
PRINT #SQL
IF #COMMIT_CHANGES = 1
BEGIN
IF(##SERVERNAME <> #sServerName) SET #SQL = 'EXEC(''' + REPLACE(#SQL,'''','''''') + ''') AT ' + QUOTENAME(#sServerName)
-- uncomment this EXEC to make the commit changes flag work...
--EXEC(#SQL)
END
IF #SQL IS NOT NULL INSERT INTO ##COMMANDLIST (sText) SELECT #SQL
FETCH NEXT FROM ADD_COLUMN INTO #COLUMN_NAME, #DATA_TYPE, #CHARACTER_MAXIMUM_LENGTH, #NUMERIC_PRECISION, #NUMERIC_SCALE, #IS_NULLABLE
END
CLOSE ADD_COLUMN
DEALLOCATE ADD_COLUMN
SKIPWORK:

Creating 'util' - stored procedure section, as with .net helper classes

A few minutes ago I was only searching for a simple syntax (SQL server) query that will copy a table Row .
This is usually done from time to time, when working on a ASP.net project, testing data with queries
inside the SQL SERVER management studio . so one of the routine actions is copying a row, altering the required columns to be different from each other, then testing data with queries
So I've encountered - this stored procedure- ,as answer by Dan Atkinson
but adding it to where all non testing purpose are stored lead me to think
is it possible to store them in sorted order so I could Distinguish
'utils' or 'testingPurpose' ones from those used in projects
(default folder inside managment treeview is Programmabilty) could this be another folder too
or this is not an option ?
if not , I thought of Utils. prefix like that (if no other way exist)
dbo.Utils.CopyTableRow
dbo.Utils.OtherRoutineActions ....
Or there's a designated way to achieve what I was thinking of.
this is a first "Util" stored procedure i've made , found it's only solution
prefexing it via Util_
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

insert command SQL server -identity Pkey autoincrement - column name by type , in "where condition"

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

sql update with dynamic column names

EDIT: Database names have been modified for simplicity
I'm trying to get some dynamic sql in place to update static copies of some key production tables into another database (sql2008r2). The aim here is to allow consistent dissemination of data (from the 'static' database) for a certain period of time as our production databases are updated almost daily.
I am using a CURSOR to loop through a table that contains the objects that are to be copied into the 'static' database.
The prod tables don't change that frequently, but I'd like to make this somewhat "future proof" (if possible!) and extract the columns names from INFORMATION_SCHEMA.COLUMNS for each object (instead of using SELECT * FROM ...)
1) From what I have read in other posts, EXEC() seems limiting, so I believe that I'll need to use EXEC sp_executesql but I'm having a little trouble getting my head around it all.
2) As an added extra, if at all possible, i'd also like to exclude some columns for particular tables (structures vary slightly in the 'static' database)
here's what i have so far.
when executed, #colnames returns NULL and therefore #sql returns NULL...
could someone guide me to where i might find a solution?
any advice or help with this code is much appreciated.
CREATE PROCEDURE sp_UpdateRefTables
#debug bit = 0
AS
declare #proddbname varchar(50),
#schemaname varchar(50),
#objname varchar(150),
#wherecond varchar(150),
#colnames varchar(max),
#sql varchar(max),
#CRLF varchar(2)
set #wherecond = NULL;
set #CRLF = CHAR(10) + CHAR(13);
declare ObjectCursor cursor for
select databasename,schemaname,objectname
from Prod.dbo.ObjectsToUpdate
OPEN ObjectCursor ;
FETCH NEXT FROM ObjectCursor
INTO #proddbname,#schemaname,#objname ;
while ##FETCH_STATUS=0
begin
if #objname = 'TableXx'
set #wherecond = ' AND COLUMN_NAME != ''ExcludeCol1'''
if #objname = 'TableYy'
set #wherecond = ' AND COLUMN_NAME != ''ExcludeCol2'''
--extract column names for current object
select #colnames = coalesce(#colnames + ',', '') + QUOTENAME(column_name)
from Prod.INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = + QUOTENAME(#objname,'') + isnull(#wherecond,'')
if #debug=1 PRINT '#colnames= ' + isnull(#colnames,'null')
--replace all data for #objname
--#proddbname is used as schema name in Static database
SELECT #sql = 'TRUNCATE TABLE ' + #proddbname + '.' + #objname + '; ' + #CRLF
SELECT #sql = #sql + 'INSERT INTO ' + #proddbname + '.' + #objname + ' ' + #CRLF
SELECT #sql = #sql + 'SELECT ' + #colnames + ' FROM ' + #proddbname + '.' + #schemaname + '.' + #objname + '; '
if #debug=1 PRINT '#sql= ' + isnull(#sql,'null')
EXEC sp_executesql #sql
FETCH NEXT FROM ObjectCursor
INTO #proddbname,#schemaname,#objname ;
end
CLOSE ObjectCursor ;
DEALLOCATE ObjectCursor ;
P.S. i have read about sql injection, but as this is an internal admin task, i'm guessing i'm safe here!? any advice on this is also appreciated.
many thanks in advance.
You have a mix of SQL and dynamic SQL in your query against information_schema. Also QUOTENAME isn't necessary in the where clause and will actually prevent a match at all, since SQL Server stores column_name, not [column_name], in the metadata. Finally, I'm going to change it to sys.columns since this is the way we should be deriving metadata in SQL Server. Try:
SELECT #colnames += ',' + name
FROM Prod.sys.columns
WHERE OBJECT_NAME([object_id]) = #objname
AND name <> CASE WHEN #objname = 'TableXx' THEN 'ExcludeCol1' ELSE '' END
AND name <> CASE WHEN #objname = 'TableYy' THEN 'ExcludeCol2' ELSE '' END;
SET #colnames = STUFF(#colnames, 1, 1, '');

SQL script to rebuild indexes - Incorrect syntax near the keyword 'Group'

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.