Stored procedure inside a trigger for logging - sql

I am using an identical trigger for multiple tables for logging DML events and because of that I have lots of redundant code. How can I write a stored procedure and call it inside those triggers to log data?
Here is what my trigger looks like
CREATE OR ALTER TRIGGER [Person].tr_logInsertDeleteOrUpdateemployee
ON [Person].employee
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE #tableName varchar(100) = '[Person].employee'
IF EXISTS (SELECT TOP 1 * FROM inserted)
AND EXISTS (SELECT TOP 1 * FROM Deleted)
BEGIN
INSERT INTO dbo.DMLLogs
SELECT updatedRecord = 'updated row', #tableName, ID, SYSTEM_USER, GETDATE()
FROM deleted
END
IF EXISTS (SELECT TOP 1 * FROM inserted)
AND NOT EXISTS (SELECT TOP 1 * FROM Deleted)
BEGIN
INSERT INTO dbo.DMLLogs
SELECT insertedRecord = 'inserted', #tableName, ID, SYSTEM_USER, GETDATE()
FROM inserted
END
IF EXISTS (SELECT TOP 1 * FROM deleted)
AND NOT EXISTS (SELECT TOP 1 * FROM inserted)
BEGIN
INSERT INTO dbo.DMLLogs
SELECT deletedRecord = 'deleted from', #tableName, ID, SYSTEM_USER, GETDATE()
FROM deleted
END
END

Putting aside the question of whether there's a better option than creating triggers, the inserted and deleted virtual tables are not visible in a stored procedure, so there's not really a way to do that. The usual practice here is to automate the creation of the boilerplate triggers so they are all created from a single template. Something like:
create schema admin
go
create or alter proc admin.GenerateAuditingTriggers
as
begin
declare c cursor local for
select name, schema_name(schema_id)
from sys.tables
where schema_id in (schema_id('dbo'))
open c
declare #tableName sysname
declare #schemaName sysname
fetch next from c into #tableName, #schemaName
while ##FETCH_STATUS = 0
begin
declare #sql nvarchar(max) = concat(
'
CREATE OR ALTER TRIGGER ',quotename(#schemaName),'.',quotename('tr_logInsert' + #tableName),'
ON ',quotename(#schemaName),'.',quotename(#tableName),'
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE #tableName varchar(100) = ''',quotename(#schemaName),'.',quotename(#tableName),'''
INSERT INTO dbo.DMLLogs
SELECT updatedRecord = ''inserted row'', #tableName, ID, SYSTEM_USER, GETDATE()
FROM inserted
END
')
print #sql
exec (#sql)
print 'trigger created'
print ''
fetch next from c into #tableName, #schemaName
end
close c
deallocate c
end

Related

Stored procedure to drop the column in SQL Server

I created many tables and I have noticed that I have created one useless column in all the tables. I want to create a stored procedure which will drop one specific column and can be useful in all the column.
I created this stored procedure but I'm getting an error. Help me please
You cannot parametrize table and column names with parameters - those are only valid for values - not for object names.
If this is a one-time operation, the simplest option would be to generate the ALTER TABLE ... DROP COLUMN ... statements in SSMS using this code:
SELECT
'ALTER TABLE ' + SCHEMA_NAME(t.schema_id) + '.' + t.Name +
' DROP COLUMN Phone;'
FROM
sys.tables t
and then execute this code in SSMS; the output from it is a list of statement which you can then copy & paste to a new SSMS window and execute.
If you really want to do this as a stored procedure, you can apply the same basic idea - and then just use code (a cursor) to iterate over the commands being generated, and executing them - something like this:
CREATE PROCEDURE dbo.DropColumnFromAllTables (#ColumnName NVARCHAR(100))
AS
BEGIN
DECLARE #SchemaName sysname, #TableName sysname
-- define cursor over all tables which contain this column in question
DECLARE DropCursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT
SchemaName = s.Name,
TableName = t.Name
FROM
sys.tables t
INNER JOIN
sys.schemas s ON t.schema_id = s.schema_id
WHERE
EXISTS (SELECT * FROM sys.columns c
WHERE c.object_id = t.object_id
AND c.Name = #ColumnName);
-- open cursor and start iterating over the tables found
OPEN DropCursor
FETCH NEXT FROM DropCursor INTO #SchemaName, #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Stmt NVARCHAR(1000)
-- generate the SQL statement
SET #Stmt = N'ALTER TABLE [' + #SchemaName + '].[' + #TableName + '] DROP COLUMN [' + #ColumnName + ']';
-- execute that SQL statement
EXEC sp_executeSql #Stmt
FETCH NEXT FROM DropCursor INTO #SchemaName, #TableName
END
CLOSE DropCursor
DEALLOCATE DropCursor
END
This procedure should work.
It loops through all cols and then deletes the column where sum(col) is zero.
Take a Backup of the Table
alter procedure deletecolumnsifzero #tablename varchar(1000)
as
set nocount on
declare #n int
declare #sql nvarchar(1000)
declare #sum_cols nvarchar(1000)
declare #c_id nvarchar(100)
set #n = 0
declare c1 cursor for
select column_name from information_schema.columns
where
table_name like #tablename
--Cursor Starts
open c1
fetch next from c1
into #c_id
while ##fetch_status = 0
begin
set #sql=''
set #sql='select #sum_cols = sum('+#c_id+') from ['+#tablename+']'
exec sp_Executesql #sql,N'#sum_cols int out,#tablename nvarchar(100)',#sum_cols out,#tablename
if(#sum_cols = 0)
begin
set #n=#n+1
set #sql=''
set #sql= #sql+'alter table ['+#tablename+'] drop column ['+#c_id+']'
exec sp_executesql #sql
end
fetch next from c1
into #c_id
end
close c1
deallocate c1

how to check same Object Name across databases on the same server

I was trying to find duplicate object name across all databases on one server. Is there away to find all duplicate? I am using cursor to loop through the databases and I want to check if an object shows more than one and column called duplicates will be populated with Yes or No.
use the msforeachdb builtin function/proc, then insert those results into a temp table. Then query the temp table and do a groupby having count(1) > 1
create table #tables (db varchar(100) not null, name varchar(100) not null)
exec sp_msforeachdb 'use [?] insert into #tables select distinct db_name(), [name] from sys.objects where [type] = ''U'''
select name, count(1) from #tables group by name having count(1) > 1 order by 2 desc
drop table #tables
This isn't exact, but you get the idea. I'll keep tweaking until I know it's right.
Here's the cursor version:
set nocount on
SELECT name
into #dbs
FROM sys.databases
declare dbCursor cursor local forward_only for
select name from #dbs
declare #db varchar(100)
create table #tables (db varchar(100) not null, name varchar(100) not null)
open dbCursor
fetch next from dbCursor into #db
while (##fetch_status = 0) begin
begin try
exec ('use ' + #db + ' insert into #tables select distinct db_name(), [name] from sys.objects where [type] = ''U''')
print 'got ' + ##rowcount + ' from db ' + #db
end try
begin catch
print 'couldn''t retrieve data from database ' + #db
end catch
fetch next from dbCursor into #db
end
close dbCursor
deallocate dbCursor
select name, count(1) from #tables group by name having count(1) > 1 order by 2 desc
drop table #tables
drop table #dbs

How to get the row count for views from a database?

I'm trying to make a customized view that gets me the row count for all the views and tables of a database.
Getting the count of table is damn in SQL Server
SELECT TABLE_SCHEMA,
TABLE_NAME = TABLES.TABLE_NAME,
RECORD_COUNT = MAX(SYSINDEXES.ROWS)
FROM SYS.SYSINDEXES "SYSINDEXES",
INFORMATION_SCHEMA.TABLES "TABLES"
WHERE TABLES.TABLE_NAME = OBJECT_NAME(SYSINDEXES.ID)
AND TABLES.TABLE_TYPE = 'BASE TABLE'
GROUP BY TABLES.TABLE_SCHEMA,
TABLES.TABLE_NAME
Now, I need to get the rowcount for VIEWS
I feel the only way is to count the number of rows from the views
i.e. count(*) from view_name
But, I could not find a way to have the rowcount for view along with view_name, table_schema and so on.
Any advance on this would be helpful.
Well, it isn't very pretty, but this should do the trick.
This applies to a single database because I'm using the system view 'all_views' to get the data. I'm sure it could be adapted if you wanted something else.
I've also limited the views I'm returning counts for, via the 'schema_id'. You can look at this and determine what would be best for you.
Lastly, I've just spit the results out so that you can view them if you run this in Management Studio. You would probably want to insert the results into a table or something instead. If so, it's simply a matter of changing the dynamic query from a select to an insert.
So without further adieu:
SET NOCOUNT ON
DECLARE #ViewName AS nVarChar(128)
, #Query AS nVarChar(500)
/* Declare Cursor */
DECLARE Cur_Views CURSOR
FOR
SELECT name
FROM [sys].[all_views] x
WHERE x.schema_id = 1
-- Loop through the views.
OPEN Cur_Views
-- Fetch the first view
FETCH NEXT FROM Cur_Views
INTO #ViewName
WHILE ##Fetch_Status = 0 BEGIN
-- Set up our dynamic sql
SELECT #Query = 'SELECT COUNT(*) AS [Count] FROM ' + #ViewName
-- Print the query we're executing for debugging purposes
-- PRINT #Query
-- Execute the dynamic query
EXECUTE(#Query)
-- Fetch subsequent views
FETCH NEXT FROM Cur_Views
INTO #ViewName
-- Loop back to the beginning
END -- WHILE ##Fetch_Status = 0 BEGIN
-- Close the cursor
CLOSE Cur_Views
-- Dispose of the cursor
DEALLOCATE Cur_Views
GO
Here , is the final solution:
SET NOCOUNT ON
DECLARE #ViewName AS nVarChar(128)
, #TmpQuery AS nVarChar(500)
, #Out3 as int
DECLARE Cur_Views CURSOR FOR
SELECT schema_name(schema_id)+'.'+name as "Table_Name" FROM [sys].[all_views]
OPEN Cur_Views
FETCH NEXT FROM Cur_Views INTO #ViewName
WHILE ##Fetch_Status = 0 BEGIN
--SELECT #Query = 'SELECT COUNT(*) AS [Count] FROM ' + #ViewName
--EXECUTE(#Query)
CREATE TABLE #Data (var int)
SELECT #TmpQuery = 'SELECT COUNT(*) AS [Count] FROM ' + #ViewName
INSERT #Data exec (#TmpQuery)
SELECT #Out3 = var from #Data
--PRINT #ViewName
--PRINT #Out3
insert into Person.ViewCountTracker values(#ViewName,#Out3)
DROP TABLE #Data
FETCH NEXT FROM Cur_Views INTO #ViewName
END
CLOSE Cur_Views
DEALLOCATE Cur_Views
GO
--create table Person.ViewCountTracker
--(
-- ViewName varchar(255),
-- RowValue int
--)
--select * from Person.ViewCountTracker
I know this is an old post and I do not intend to take credit from the marked answer but wanted to add to it.
SELECT #Query = 'SELECT ''' + #ViewName + ''' AS Name, COUNT(*) AS [Count] FROM ' + #ViewName
I added the View name to help distinguish the numbers more.
SET NOCOUNT ON
DECLARE #ViewName AS nVarChar(128), #TmpQuery AS nVarChar(384)
CREATE TABLE #Results (Name nVarChar(128), Cnt BigInt)
DECLARE Cur_Views CURSOR FOR SELECT schema_name(schema_id) + '.' + name AS Name FROM [sys].[all_views] WHERE is_ms_shipped = 0
OPEN Cur_Views
WHILE (1=1)
BEGIN
FETCH NEXT FROM Cur_Views INTO #ViewName
If ##Fetch_Status != 0 BREAK
SELECT #TmpQuery = 'SELECT ''' + #ViewName + ''' AS Name, COUNT_BIG(*) AS Cnt FROM ' + #ViewName + ' (NoLock)'
PRINT #TmpQuery
INSERT #Results EXEC (#TmpQuery)
END
CLOSE Cur_Views
DEALLOCATE Cur_Views
SET NOCOUNT OFF
SELECT * FROM #Results
DROP TABLE #Results
GO

how to select table name from the table itself in a dynamic way?

I want to select table name from the table itself
for example
Select * ,(TableName) from Table1
union
Select *,(TableName) from Table2
i don't want to do it in a static way
Select * ,'TableName' as tbl from Table1
union
Select *,'TableName' as tbl from Table2
thanks in advance
You can't.
There is no metadata function like SELECT OBJECT_NAME(this) you can use.
The FROM Table1 bit is static so what's the problem anyway?
The solution can be dynamic but it's a lot more complex than your first code block up there.
Declare a varchar variable large enough to hold your select script (eg. #script).
Declare a cursor for iterating through your tables in sysobjects table. It's something like:
declare cc cursor for select name from sysobjects
where type='U' and name like '%yourTableNamePatternHere%'
order by name
Go through the records in a while loop using fetch next and ##fetch_status, assemble your big union-query into #script and finally use sp_executesql to have #script executed by the server.
That's it.
Here is the script in one listing:
declare #n varchar(100), #script nvarchar(2000)
set #script = ''
declare cc cursor for select name from sysobjects where type='u' and name like '%yourTableNamePatternHere%'
open cc
fetch next from cc into #n
while ##fetch_status = 0
begin
if #script <> '' set #script = #script + ' union '
set #script = #script + 'select <column1>, <column2>, ''' + #n + ''' as tableName from ' + #n
fetch next from cc into #n
end
close cc
deallocate cc
-- print #script
exec sp_executesql #script

How to get the right order for creation of stored procedure, user-defined functions and triggers

I read that object dependencies have been improved in SQL server 2008.
I have a rather complex database schema containing stored procedure, user-defined functions, triggers.
Can anybody give me a query that would return the right order of creation of those items based on their dependencies ?
I read here that there are tools that can do the job, but I am looking for something scriptable. Also, they often give the dependencies of one object and I would like a database-wide solution.
Thank you.
Redgate's sql compare pro can be executed via the command line. I have the GUI version and it always gets things correct. I've even learned a few things by looking at the output from this tool!
You can also use SMO scripting to do it, but I believe you'll need to figure out dependencies and the proper order of operations for yourself.
Well, I did something based on that article
I modified the sp_FindDependencies to include the level in the dependency tree :
CREATE PROCEDURE sp_FindDependencies
(
#ObjectName SYSNAME,
#ObjectType VARCHAR(5) = NULL
)
AS
BEGIN
DECLARE #ObjectID AS BIGINT
SELECT TOP(1) #ObjectID = object_id
FROM sys.objects
WHERE name = #ObjectName
AND type = ISNULL(#ObjectType, type)
SET NOCOUNT ON ;
WITH DependentObjectCTE (DependentObjectID, DependentObjectName, ReferencedObjectName, ReferencedObjectID, Level)
AS
(
SELECT DISTINCT
sd.object_id,
OBJECT_NAME(sd.object_id),
ReferencedObject = OBJECT_NAME(sd.referenced_major_id),
ReferencedObjectID = sd.referenced_major_id,
1 AS Level
FROM
sys.sql_dependencies sd
JOIN sys.objects so ON sd.referenced_major_id = so.object_id
WHERE
sd.referenced_major_id = #ObjectID
UNION ALL
SELECT
sd.object_id,
OBJECT_NAME(sd.object_id),
OBJECT_NAME(referenced_major_id),
object_id,
Level + 1
FROM
sys.sql_dependencies sd
JOIN DependentObjectCTE do ON sd.referenced_major_id = do.DependentObjectID
WHERE
sd.referenced_major_id <> sd.object_id
)
SELECT DISTINCT
DependentObjectName, Level
FROM
DependentObjectCTE c
END
GO
Then I walk through all the objects I want to track down. In my system, I maintain the version of those objects in a table SpVersion.
DECLARE #err int;
DECLARE #level int;
DECLARE #name varchar(50);
DECLARE #name2 varchar(50);
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT SP.name AS obj_name
FROM sys.procedures SP, SpVersion SPV WHERE
SP.name = SPV.SpName
UNION
SELECT SO.name AS obj_name
FROM sys.objects SO, SpVersion SPV
WHERE type_desc LIKE '%FUNCTION%' AND SO.name=SPV.SpName
UNION
SELECT ST.name AS obj_name
FROM sys.triggers ST, SpVersion SPV
WHERE ST.name=SPV.SpName;
CREATE TABLE #T1 (procname varchar(50) COLLATE DATABASE_DEFAULT, Level int);
CREATE TABLE #T2 (procname varchar(50) COLLATE DATABASE_DEFAULT, Level int);
OPEN cur
SELECT #err = ##error IF #err <> 0 RETURN
WHILE 1=1 BEGIN
FETCH NEXT FROM cur INTO #name
SELECT #err = ##error IF #err <> 0 RETURN
IF ##FETCH_STATUS <> 0 BREAK
DELETE #T1;
INSERT #T1 (procname, level) VALUES (#name, 0);
INSERT #T1 (procname, level) EXEC sp_FindDependencies #name;
DECLARE cur2 CURSOR LOCAL FAST_FORWARD FOR
SELECT procname, level FROM #T1;
OPEN cur2
SELECT #err = ##error IF #err <> 0 RETURN
WHILE 1=1 BEGIN
FETCH NEXT FROM cur2 INTO #name2, #level
SELECT #err = ##error IF #err <> 0 RETURN
IF ##FETCH_STATUS <> 0 BREAK
PRINT #name;
PRINT CONVERT(nvarchar, #level)
IF NOT EXISTS(SELECT 1 FROM #T2 WHERE procname = #name) BEGIN
INSERT INTO #T2 (procname, level) VALUES (#name, #level);
END ELSE BEGIN
UPDATE #T2 SET level=#level WHERE procname=#name;
END
END
CLOSE cur2;
DEALLOCATE cur2;
END
CLOSE cur;
DEALLOCATE cur;
SELECT * FROM #T2 ORDER BY Level DESC;
DROP TABLE #T1
DROP TABLE #T2
GO