SQL Server Logging/User Tracking Capabilities - sql-server

We have recently converted an Access Application to store the date on SQL server. The forms still reside in the MS Access, but are linked through file DSN's to SQL server.
A question came up of the capabilities to track user activity within SQL server.
I have speculated that if we set up individual users in SQL server and use these individual accounts when setting up the DSN's on the user's computers that perhaps then we could use SQL server to track user activity. Is this true?
We currently have SQL server 2005 standard but will be upgrading to 2008 relatively soon.
Thanks for any suggestions!

When you upgrade to SQL 2008 you have Change Data Capture.

I work currently on tracking DDL changes , so here is one DDL trigger with the needed 2 functions and table structure ... Before running the code set ext props for your database for "DbVersion"(e.g. 2.3.4) and "DbType" (e.g. dev , test , prod ) and replace the [ga] namespace with the one with your choice or create one ...
Edit: fixed bug with null Version if object is new ...
USE [GA_DEV]
GO
/****** Object: DdlTrigger [TraceDbChanges] Script Date: 05/07/2009 11:15:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create trigger [TraceDbChanges]
on database
for create_procedure, alter_procedure, drop_procedure,
create_table, alter_table, drop_table,
create_function, alter_function, drop_function ,
create_trigger , alter_trigger , drop_trigger
as
set nocount on
declare #data xml
set #data = EVENTDATA()
declare #DbVersion varchar(20)
set #DbVersion =(select ga.GetDbVersion())
declare #DbType varchar(20)
set #DbType =(select ga.GetDbType())
declare #DbName varchar(256)
set #DbName =#data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'varchar(256)')
declare #EventType varchar(256)
set #EventType =#data.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(50)')
declare #ObjectName varchar(256)
set #ObjectName = #data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(256)')
declare #ObjectType varchar(25)
set #ObjectType = #data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'varchar(25)')
declare #TSQLCommand varchar(max)
set #TSQLCommand = #data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'varchar(max)')
declare #opentag varchar(4)
set #opentag= '<'
declare #closetag varchar(4)
set #closetag= '>'
declare #newDataTxt varchar(max)
set #newDataTxt= cast(#data as varchar(max))
set #newDataTxt = REPLACE ( REPLACE(#newDataTxt , #opentag , '<') , #closetag , '>')
-- print #newDataTxt
declare #newDataXml xml
set #newDataXml = CONVERT ( xml , #newDataTxt)
declare #Version varchar(50)
set #Version = #newDataXml.value('(/EVENT_INSTANCE/TSQLCommand/CommandText/Version)[1]', 'varchar(50)')
-- if we are dropping take the version from the existing object
if ( SUBSTRING(#EventType , 0 , 5)) = 'DROP'
set #Version =( select top 1 [Version] from ga.DbObjChangeLog where ObjectName=#ObjectName order by [LogId] desc)
if ( #Version is null)
set #Version = '1.0.0'
declare #Description varchar(max)
set #Description = #newDataXml.value('(/EVENT_INSTANCE/TSQLCommand/CommandText/Description)[1]', 'varchar(max)')
declare #ChangeDescription varchar(max)
set #ChangeDescription = #newDataXml.value('(/EVENT_INSTANCE/TSQLCommand/CommandText/ChangeDescription)[1]', 'varchar(max)')
declare #LoginName varchar(256)
set #LoginName = #data.value('(/EVENT_INSTANCE/LoginName)[1]', 'varchar(256)')
declare #FirstName varchar(50)
set #FirstName= (select [FirstName] from [ga].[LoginsForUsers] where [LoginName] = #LoginName)
declare #LastName varchar(50)
set #LastName = (select [LastName] from [ga].[LoginsForUsers] where [LoginName] = #LoginName)
declare #SchemaName sysname
set #SchemaName = #data.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname');
--declare #Description xml
--set #Description = #data.query('(/EVENT_INSTANCE/TSQLCommand/text())')
print 'VERSION IS ' + #Version
print #newDataTxt
print cast(#data as varchar(max))
-- select column_name from information_schema.columns where table_name ='DbObjChangeLog'
insert into [ga].[DbObjChangeLog]
(
[DatabaseName] ,
[SchemaName],
[DbVersion] ,
[DbType],
[EventType],
[ObjectName],
[ObjectType] ,
[Version],
[Description],
[ChangeDescription],
[SqlCommand] ,
[LoginName] ,
[FirstName],
[LastName]
)
values(
#DbName,
#SchemaName,
#DbVersion,
#DbType,
#EventType,
#ObjectName,
#ObjectType ,
#Version,
#Description,
#ChangeDescription,
#newDataTxt,
#LoginName ,
#FirstName ,
#LastName
)
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
DISABLE TRIGGER [TraceDbChanges] ON DATABASE
GO
ENABLE TRIGGER [TraceDbChanges] ON DATABASE
GO

Is this true?
Yes, triggers on tables to capture changes would work. You could also set up a server side trace to log login events to a table if you were after that kind of info. Be more specific for some more answers.

Related

Get data from many databases - dynamic database

We are using SQL Server 2014 Enterprise with many databases. I have to execute query and get reports / data from every database with EXACT SAME Schema and database starts with Cab
When a new company is added in our ERP project a new database is created with exact schema starting with Cab and incremented number is assigned to it like:
Cab1
Cab2
Cab3
Cab5
Cab10
I can get the database names as:
SELECT name
FROM master.sys.databases
where [name] like 'Cab%' order by [name]
I have to create a Stored Procedure to get data from tables of every database.
How to do that using a Stored Procedure as the databases are created dynamically starting with Cab?
You can use EXEC(#Statement) or EXEC SP_EXECUTESQL if you have to pass parameters.
CREATE OR ALTER PROCEDURE dbo.GetDataFromAllDatabases
AS
BEGIN
DECLARE #T TABLE (id INT NOT NULL IDENTITY(1, 1), dbName VARCHAR(256) NOT NULL)
INSERT INTO #T
SELECT NAME FROM MASTER.SYS.DATABASES WHERE [NAME] LIKE 'Cab%' ORDER BY [NAME]
CREATE TABLE #AllData (......)
DECLARE #Id INT, #DbName VARCHAR(128)
SELECT #Id = MIN(Id) FROM #T
WHILE #Id IS NOT NULL
BEGIN
SELECT #DbName = dbName FROM #T WHERE Id = #Id
DECLARE #Statement NVARCHAR(MAX)
SET #Statement = CONCAT(N'INSERT INTO #AllData (...) SELECT .... FROM ', #DbName, '.dbo.[TableName]')
EXEC(#Statement);
--YOU CAN USE BELOW LINE TOO IF YOU NEED TO PASS VARIABLE
--EXEC SP_EXECUTESQL #Statement, '#Value INT', #Value = 128
SET #Id = (SELECT MIN(Id) FROM #T WHERE Id > #Id)
END
END
A quick and easy dynamic SQL solution would be something like this:
DECLARE #Sql nvarchar(max);
SET #Sql = STUFF((
SELECT ' UNION ALL SELECT [ColumnsList], '''+ [name] + ''' As SourceDb FROM '+ QUOTENAME([name]) + '.[SchemaName].[TableName]' + char(10)
FROM master.sys.databases
WHERE [name] LIKE 'Cab%'
FOR XML PATH('')
), 1, 10, '');
--When dealing with dynamic SQL, print is your best friend...
PRINT #Sql
-- Once the #Sql is printed and you can see it looks OK, you can run it.
--EXEC(#Sql)
Notes:
Use quotename to protect against "funny" chars in identifiers names.
Replace [ColumnsList] with the actual list of columns you need.
There's no need for loops of any kind, just a simple stuff + for xml to mimic string_agg (which was only introduced in 2017).
I've thrown in the source database name as a "bonus", if you don't want it that's fine.
The Order by clause in the query that generates the dynamic SQL is meaningless for the final query, so I've removed it.

Using dynamic sql in a procedure to insert data into schema tables

I hope that you are all doing well.
I've been working on a project where I need to store data about my college that includes ID numbers, names, contact details etc.
I'm having a bit of difficulty in creating a stored procedure that will be able to insert data into a specified schema.table_name. The procedure must be able to allow the EXEC command to specify which schema you would like the insert data into. The table_name will stay the same for all the 14 schemas. The following code sample is what I have come up with but it doesn't seem to work:
CREATE PROCEDURE AddStudent_proc(#campus varchar(50), #StudentID numeric(4,0), #Name varchar(50), #Surname varchar(50), #ID_numeric numeric(13,0), #Address varchar(100))
AS
BEGIN
DECLARE #dynamic varchar(MAX)
SET #dynamic = 'INSERT INTO ['+quotename(#campus)+'].Student_tbl(
StudentID,
Name,
Surname,
ID_numeric,
Address
)
VALUES('+quotename(#StudentID)+','+quotename(#Name)+','+quotename(#Surname)+','+quotename(#ID_numeric)+','+quotename(#Address)+');'
EXEC (#dynamic);
END
GO
My entire structure can be found
here
I'd appreciate any help on this topic as I am still quite new to SQL as a whole.
Thanks in advance.
You don't need to use quotename for data - as the name of the function implies, it should be used with names (A.K.A identifiers).
Also, when you are using quotename it addeds [ and ] around the value it receives, so no point of adding them again (['+quotename(#campus)+'] in your code).
I would recommend three improvements to the procedure you have now:
Change the data type of #campus to sysname - this is a special data type synonym to nvarchar(128) not null used by SQL Server for all identifiers.
white-list the schema name.
This is a critical change to protect against SQL Injection attacks.
Anything that can't be parameterized needs to be white-listed.
use sp_ExecuteSql instead of EXEC
This will result in a better stored procedure because it eliminates the threat of SQL Injection.
I've written a couple of blog posts that adds some more information and background on this subject: The do’s and don’ts of dynamic SQL for SQL Server and Back to basics: SQL Injection.
Anyway, here's how I would write this procedure:
CREATE PROCEDURE AddStudent_proc(
#campus sysname,
#StudentID numeric(4,0),
#Name varchar(50),
#Surname varchar(50),
#ID_numeric numeric(13,0),
#Address varchar(100)
)
AS
BEGIN
IF EXISTS(
SELECT 1
FROM Sys.Schemas
WHERE name = #campus
)
BEGIN
DECLARE #dynamic nvarchar(4000),
#paramDefinition nvarchar(4000)
SELECT #dynamic = N'INSERT INTO '+ quotename(#campus) + N'.Student_tbl (
StudentID,
Name,
Surname,
ID_numeric,
Address
)
VALUES(#StudentID, #Name, #Surname, #ID_numeric, #Address)',
#paramDefinition =
N'#StudentID numeric(4,0),
#Name varchar(50),
#Surname varchar(50),
#ID_numeric numeric(13,0),
#Address varchar(100)'
EXEC sp_executeSql #dynamic, #paramDefinition, #StudentID, #Name, #Surname, #ID_numeric, #Address;
END
END
GO

How to Embed Variable Value in SQL Server query

I need to be able to query Active Directory from SQL Server - see
https://www.mssqltips.com/sqlservertip/2580/querying-active-directory-data-from-sql-server/
The following runs fine:
DECLARE #eid nvarchar(5)
DECLARE #username nvarchar(5)
SET #eid='123'
SELECT #username=sAMAccountName FROM OpenQuery(ADSI,'SELECT sAMAccountName, employeeID FROM ''LDAP://mydomain.com/DC=mydomain,DC=com'' WHERE objectClass = ''User'' AND employeeID=123')
PRINT #username
How to replace the hardcoded 123 value with the actual value of the #eid, so that the modified code will run fine as well?
Thanks!
Annoyingly you have to build the SQL string ahead of time to add a parameter value into it and then execute the OpenQuery using that string. You must execute all of it as you would a dynamic query EXEC sp_executeSQL ...
Here's what I came up with:
DECLARE #eid nvarchar(5), #username nvarchar(5), #Query nvarchar(MAX)
SELECT #eid='123'
SELECT #Query = N'SELECT sAMAccountName FROM OpenQuery(ADSI, ''SELECT sAMAccountName, employeeID FROM ''''LDAP://mydomain.com/DC=mydomain,DC=com'''' WHERE objectClass = ''''User'''' AND employeeID=' + #eid + ''')'
SELECT #Query
EXEC sp_executeSQL #Query, #username OUTPUT
PRINT #username
I may not have the output quite right. Another option you have is to insert the output of the EXEC sp_executeSQL into a temptable and select the username from that value. For more info you can see this question.
Another option (and probably the easiest of all) is to move the WHERE outside of the open query and filter after you get the results back like this:
DECLARE #eid nvarchar(5), #username nvarchar(5), #Query nvarchar(MAX)
SELECT #eid='123'
SELECT #username = sAMAccountName--, employeeID
FROM OpenQuery(AD, 'SELECT sAMAccountName, employeeID FROM ''LDAP://mydomain.com/DC=mydomain,DC=com'' WHERE objectClass = ''User''')
WHERE employeeID = #eid
PRINT #username
I know this is an old question, but here is a way to work with output values (since you'll return only 1 value):
DECLARE #eid nvarchar(5), #username nvarchar(50), #Query nvarchar(MAX)
SET #Query = '(SELECT #username = sAMAccountName FROM OpenQuery(ADSI,''SELECT sAMAccountName, employeeID FROM ''LDAP://mydomain.com/DC=mydomain,DC=com'' WHERE objectClass = ''User'' AND employeeID=' + #eid +') AS tblADSI)'
EXEC sp_executesql #Query , N'#username nvarchar(50) out', #username out
SELECT #username As Outputs
This will assign the result of the OpenQuery execution, in the variable #username.
We tested for Store procedure in MSSQL 2012, but should work with MSSQL 2008+.
Microsoft Says that sp_executesql(Transact-SQL): Applies to: SQL Server (SQL Server 2008 through current version), Windows Azure SQL Database (Initial release through current release). (http://msdn.microsoft.com/en-us/library/ms188001.aspx)

Disabling Index operations for certain tables in SQL Server 2008 R2 other than DDL trigger

Is there a way to disable certian index DDL operation (create, drop and alter index) for a list of tables in MS SQL Server 2008 R2?
What I was trying to do is to create a DDL trigger that catch these events and roll them back, but it seems that all ddl trigers are after triggers and if table is very large this cause performance issues.
The trigger I am currently using is the following:
CREATE TRIGGER index_guard
ON DATABASE
FOR CREATE_INDEX, DROP_INDEX, ALTER_INDEX
AS
DECLARE #object_name NVARCHAR(50);
DECLARE #table_name NVARCHAR(50);
DECLARE #target_object_type NVARCHAR(20);
DECLARE #object_type NVARCHAR(20);
DECLARE #lookup_value NVARCHAR(100);
DECLARE #protected_indexes TABLE (Name NVARCHAR(50))
INSERT INTO #protected_indexes
SELECT Name FROM (VALUES ('TABLE1/IX_IdName'), ('TABLE2/IX_NameId')) AS tbl(Name)
SELECT #object_name = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(max)');
SELECT #table_name = EVENTDATA().value('(/EVENT_INSTANCE/TargetObjectName)[1]','nvarchar(max)');
SELECT #target_object_type = EVENTDATA().value('(/EVENT_INSTANCE/TargetObjectType)[1]','nvarchar(max)');
SELECT #object_type = EVENTDATA().value('(/EVENT_INSTANCE/ObjectType)[1]','nvarchar(max)');
IF #object_type = 'INDEX' AND #target_object_type = 'TABLE'
BEGIN
SET #lookup_value = #table_name + '/' + #object_name
IF EXISTS (SELECT 1 FROM #protected_indexes A WHERE A.Name = #lookup_value)
BEGIN
ROLLBACK
END
END

Gather output of sp_helpuser for all databases of a SQL Server instance at once

Currently, sp_helpuser has to be executed for each database of a SQL Server instance separately.
Does anyone have a script that will give sp_helpuser like output for all databases at once?
You can achieve it like below. Not sure about SQL Server 2012 but works in SQL Server 2008 R2. mentioning so cause sp_msforeachdb is undocumented SP and is not guaranteed to work in future release.
sp_msforeachdb 'use [?] print ''?''; exec sp_helpuser;';
EDIT:
See the below post with exact code samples. This is exactly what you are looking for or trying to achieve.
TSQL: Audit user roles for all databases
EDIT1:
Complete code from the post I have mentioned. Have tried myself in SQL2008R2 and warks like charm.
IF OBJECT_ID('tempdb..#user_table') IS NOT NULL
BEGIN
DROP TABLE #user_table;
END;
-- tmp Table to hold the user data
CREATE TABLE #user_table
(
ServerName NVARCHAR(100) NULL,
[Database] NVARCHAR(256) NULL,
UserName NVARCHAR(128) NOT NULL,
GroupName NVARCHAR(128) NULL,
LoginName NVARCHAR(128) NULL,
DefDBName NVARCHAR(256) NULL,
DefSchemaName NVARCHAR(100) NULL,
UserID INTEGER NOT NULL,
[SID] UNIQUEIDENTIFIER NULL
);
DECLARE #sql NVARCHAR(MAX);
SET #sql = '
DECLARE #name SYSNAME,
#sql_string NVARCHAR(MAX);
-- Cursor containing all users for the current database context
DECLARE usr_name CURSOR READ_ONLY FOR SELECT [name]
FROM sysusers
WHERE hasdbaccess = 1
AND [name] NOT LIKE ''#%''
AND [name] NOT IN (''guest'');
OPEN usr_name;
FETCH NEXT FROM usr_name INTO #name;
WHILE (##FETCH_STATUS = 0) -- This loop processes each database
BEGIN
-- if it''s a windows login surround with square brackets
IF (#name LIKE ''%\%'')
BEGIN
SET #name = ''['' + #name + '']'';
END
SET #sql_string = N''EXEC sp_helpuser '' + #name;
INSERT INTO #user_table
(
UserName,
GroupName,
LoginName,
DefDBName,
DefSchemaName,
UserId,
[SID]
)
EXEC(#sql_string);
-- Add Server & database name to dataset
UPDATE #user_table
SET ServerName = ##SERVERNAME,
[Database] = DB_NAME()
WHERE ServerName IS NULL
AND [Database] IS NULL;
-- Get the next database user
FETCH NEXT FROM usr_name INTO #name; -- Get next user
END
-- Clean up
CLOSE usr_name;
DEALLOCATE usr_name;';
-- Add USE database statement to change db context
SET #sql = 'USE ?; ' + #sql;
-- Execute the string for each database
EXEC sp_MSforeachDB #sql;
SELECT *
FROM #user_table
ORDER BY LoginName, [Database];

Resources