I currently have a trigger on each table that handles a history log. The trigger is the exact same on every table. See below.
If I move this to a stored procedure, will it be faster?
Also if I use a stored procedure will the trigger release for the user to continue?
create trigger ' + #TABLE_NAME + '_ChangeTracking on ' + #TABLE_NAME + ' for
insert, update, delete
as
declare #bit int ,
#field int ,
#maxfield int ,
#char int ,
#fieldname varchar(128) ,
#TableName varchar(128) ,
#PKCols varchar(1000) ,
#sql nvarchar(max),
#Type nvarchar(1) ,
#PKValueSelect varchar(1000),
#MasterId nvarchar(max) = ''0''
select #TableName = ''' + #TABLE_NAME + '''
if exists(select * from CNF_HIL_Tables where referencetable = #TableName and Active = 1)
begin
if exists (select * from inserted)
if exists (select * from deleted)
select #Type = ''2''
else
select #Type = ''3''
else
select #Type = ''1''
select * into #ins from inserted
select * into #del from deleted
select #PKCols = coalesce(#PKCols + '' and'', '' on'') + '' i.'' + c.COLUMN_NAME + '' = d.'' + c.COLUMN_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE c on c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME and c.TABLE_NAME = pk.TABLE_NAME
where pk.TABLE_NAME = #TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
select #PKValueSelect = coalesce(#PKValueSelect+''+'','''') + ''convert(varchar(100), coalesce(i.'' + COLUMN_NAME + '',d.'' + COLUMN_NAME + ''))''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE c on c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME and c.TABLE_NAME = pk.TABLE_NAME
where pk.TABLE_NAME = #TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
select #field = 0,
#maxfield = max(ORDINAL_POSITION)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName
while #field < #maxfield
begin
select #field = min(ORDINAL_POSITION)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName
and ORDINAL_POSITION > #field
select #bit = (#field - 1 )% 8 + 1
select #bit = power(2,#bit - 1)
select #char = ((#field - 1) / 8) + 1
if substring(COLUMNS_UPDATED(),#char, 1) & #bit > 0 or #Type in (''1'',''3'')
begin
select #fieldname = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = #TableName and ORDINAL_POSITION = #field
if exists(select * from CNF_Hil_Columns INNER JOIN CNF_HIL_Tables ON CNF_HIL_Tables.TablesId = CNF_Hil_Columns.TablesId
where CNF_HIL_Tables.referencetable = #TableName and CNF_Hil_Columns.ColumnName = #fieldname
and CNF_Hil_Columns.Active = 1
)
begin
if #MasterId = 0
begin
select #sql = ''insert DATA_HIL_Master (OperationType, ReferenceTable, ReferenceId, UserId, WorkstationId, InsDateTime)''
select #sql = #sql + '' select '''''' + #Type + ''''''''
select #sql = #sql + '', '''''' + #TableName + ''''''''
select #sql = #sql + '','' + #PKValueSelect
select #sql = #sql + '',convert(varchar(1000),i.Last_UserId_Log)''
select #sql = #sql + '',convert(varchar(1000),i.Last_WorkstationId_Log)''
select #sql = #sql + '',convert(varchar(1000),i.Last_DateTime_Log)''
select #sql = #sql + '' from #ins i full outer join #del d''
select #sql = #sql + #PKCols
select #sql = #sql + '' SELECT #MasterId = SCOPE_IDENTITY() ''
EXECUTE sp_executesql #sql, N''#MasterId nvarchar(max) OUTPUT'', #MasterId OUTPUT
end
select #sql = ''insert data_HIL_Detail (MasterId, ColumnName, OriginalValue, ModifiedValue)''
select #sql = #sql + '' select convert(varchar(1000),'' + #MasterId + '')''
select #sql = #sql + '','''''' + #fieldname + ''''''''
select #sql = #sql + '', convert(varchar(1000),d.'' + #fieldname + '')''
select #sql = #sql + '', convert(varchar(1000),i.'' + #fieldname + '')''
select #sql = #sql + '' from #ins i full outer join #del d''
select #sql = #sql + #PKCols
EXECUTE sp_executesql #sql
END
END
END
END
I was actually searching for an answer to this question and stumbled in here.
I've found a lot of different answers, but as a student, I am currently being told that "stored procedures run quicker than individual SQL statements; this improves performance." So, it seems the answer is "yes".
However, it seems "performance" may be interpreted differently by different people. I'm not very experienced yet, so I don't really understand all the nuances yet. I've seen some comments attributing the difference to "cache", and others that suggest using stored procedure only because of better "control" for security and maintenance rather than anything performance related.
While reading my course material, I also came across something that might be relevant. This is from Beginning Databases with PostgreSQL: From Novice to Professional (Stones and Matthew, 2005):
Stored Procedures reside on the server side, not on the client side, adding to
access control. Invoked from the client side, only the results are passed on to the caller, this reduces network traffic. Several applications can use a single stored procedure, standardizing processing rules.
So, maybe that's what is meant by "performance".
Stored procedures also seem more similar to functions themselves, which are objects stored in a database and used by all other database objects. Whereas triggers are object associated with a table that runs a function.
Generally, Irrespective of trigger or stored procedure, you have got the same code. In trigger, you can not call it directly and in stored procedure, you are calling directly. So, whether you use trigger or stored procedure, execution wise it is same. The first time it is called, the execution plan is cached.
In your case, as you are using specifically using inserted, deleted tables, you should have different stored procedure code to implement auditing. Or you can consider using SQL Server temporal tables or Change Data Capture or SQL Server auditing
But, there are few disadvantages of using trigger.
It can make the transaction longer
Difficult to debug
Related
I would like to search through an MS SQL Server DB for a specific value in the SQL Views.
This SO post is helpful to search all tables for a specific value (Find a value anywhere in a database) but does not cover Views.
Does anyone have any SQL Script they can share? Google has only turned up how to search through all the tables for the value.
This procedure (based on this tip from 2015) will build a separate search command for every string column in any view, table, or both, in any database, or all user databases. Note that I use sys.objects instead of INFORMATION_SCHEMA for reasons I outline here.
CREATE OR ALTER PROCEDURE dbo.SearchViewsAndOrTables
#SearchTerm nvarchar(255) = NULL,
#SingleDatabase nvarchar(128) = NULL,
#ViewsOnly bit = 0,
#TablesOnly bit = 0
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
IF #SearchTerm IS NULL OR #SearchTerm NOT LIKE N'%[^%^_]%'
BEGIN
RAISERROR(N'Please enter a valid search term.', 11, 1);
RETURN;
END
CREATE TABLE #results
(
[database] sysname,
[schema] sysname,
[object] sysname,
[column] sysname,
ExampleValue nvarchar(4000)
);
DECLARE #DatabaseCommands nvarchar(max) = N'',
#ColumnCommands nvarchar(max) = N'';
SELECT #DatabaseCommands = #DatabaseCommands + N'
EXEC ' + QUOTENAME(name) + '.sys.sp_executesql
#ColumnCommands, N''#SearchTerm nvarchar(255)'', #SearchTerm;'
FROM sys.databases
WHERE database_id > 4 -- non-system databases
AND [state] = 0 -- online
AND user_access = 0 -- multi-user
AND LOWER(name) = LOWER(COALESCE(#SingleDatabase, name));
SET #ColumnCommands = N'DECLARE #q nchar(1),
#SearchCommands nvarchar(max);
SELECT #q = nchar(39),
#SearchCommands = N''DECLARE #VSearchTerm varchar(255) = #SearchTerm;'';
SELECT #SearchCommands = #SearchCommands + char(13) + char(10) + N''
SELECT TOP (1)
[db] = DB_NAME(),
[schema] = N'' + #q + s.name + #q + '',
[table] = N'' + #q + t.name + #q + '',
[column] = N'' + #q + c.name + #q + '',
ExampleValue = LEFT('' + QUOTENAME(c.name) + '', 4000)
FROM '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + ''
WHERE '' + QUOTENAME(c.name) + N'' LIKE #'' + CASE
WHEN c.system_type_id IN (35, 167, 175) THEN ''V''
ELSE '''' END + ''SearchTerm;''
FROM sys.schemas AS s
INNER JOIN sys.objects AS t
ON s.[schema_id] = t.[schema_id]
INNER JOIN sys.columns AS c
ON t.[object_id] = c.[object_id]
WHERE c.system_type_id IN (35, 99, 167, 175, 231, 239)
AND c.max_length >= LEN(#SearchTerm)
AND t.type IN ('
+ CASE #TablesOnly WHEN 1 THEN '''''' ELSE '''V''' END
+ ','
+ CASE #ViewsOnly WHEN 1 THEN '''''' ELSE '''U''' END
+ N');
PRINT #SearchCommands;
EXEC sys.sp_executesql #SearchCommands,
N''#SearchTerm nvarchar(255)'', #SearchTerm;';
INSERT #Results
(
[database],
[schema],
[object],
[column],
ExampleValue
)
EXEC [master].sys.sp_executesql #DatabaseCommands,
N'#ColumnCommands nvarchar(max), #SearchTerm nvarchar(255)',
#ColumnCommands, #SearchTerm;
SELECT [Searched for] = #SearchTerm;
SELECT [database],[schema],[object],[column],ExampleValue
FROM #Results
ORDER BY [database],[schema],[object],[column];
END
GO
I want to write a function that counts non null and non empty entries of a field. My problem is that the query does not run since the #tableName variable is not recognized in the select statement and I do not know why
create function dbo.getCount(#cod int, #columnName as varchar(20), #tableName as varchar(20))
Returns int as
Begin
--Count all filled entries
Return (select COUNT(*) from #tableName
where #columnName <> '' and #columnName is not null)
End;
go
As mentioned in the comments, but to reiterate, as I'll delete them after this answer:
You can't do this with a function, for multiple reasons. SELECT
COUNT(*) FROM #TableName means count the number of rows in the
table variable #TableName not the table who's name is the value of #TableName. WHERE #ColumnName <> '' would mean where the value of the scalar variable doesn't have the value '',
not where the column (in the aforementioned table) with the name of value of #ColumnName doesn't have the value ''.
And you can't do this in a function as to do this type of thing, you
need dynamic SQL; and you can't use dynamic SQL in a function (as you
can't use the EXEC command).
You can, however, do this with a Stored Procedure:
CREATE PROC dbo.GetCount #SchemaName sysname = N'dbo', #TableName sysname, #ColumnName sysname, #Count int OUTPUT AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = N'SELECT #Count = COUNT(NULLIF(' + QUOTENAME(c.[name]) + N',''''))' + #CRLF +
N'FROM ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.[name]) + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.[name] = #SchemaName
AND t.[name] = #TableName
AND c.[name] = #ColumnName;
--PRINT #SQL; --Your debugging friend
EXEC sp_executesql #SQL, N'#Count int OUTPUT', #Count OUTPUT;
END
GO
And you run the SP like below (with sample table):
CREATE TABLE dbo.TestTable (SomeColumn varchar(10));
INSERT INTO dbo.TestTable (SomeColumn)
VALUES(''),('abc'),(NULL);
GO
DECLARE #Count int;
EXEC dbo.GetCount #TableName = N'TestTable', #ColumnName = N'SomeColumn', #Count = #Count OUTPUT;
SELECT #Count; --Returns 1
GO
DB<>Fiddle
Shortly,
I don't mean the last 5 rows. via SELECT TOP 5 * FROM Table Order By Key DESC
But I mean last 5 columns in Results Tab.
Why? because each time I add a new column and filling it outside SQL-Server, I need to see its result without moving Horizontal Scroll. and many columns have the same name at the beginning.
It's not coding issues. but it's about the SQL-Server IDE Results tab itself.
I Searched many times. but this thing never asked I think. so please I
want an approach for that. I assume something like built-in function
or something anyone knows
Probably a dynamic statement, based on system catalog views, may help here:
DECLARE #tablename sysname = 'table'
DECLARE #schemaname sysname = 'dbo'
DECLARE #stm nvarchar(max)
SELECT #stm = (
SELECT TOP(5) CONCAT(#stm, N',', col.[name])
FROM sys.columns col
JOIN sys.tables tab ON col.object_id = tab.object_id
JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE
tab.[name] = #tablename AND
sch.[name] = #schemaname
ORDER BY col.column_id DESC
FOR XML PATH('')
)
SELECT #stm = CONCAT(
N'SELECT ',
STUFF(#stm, 1, 1, N''),
N' FROM ',
QUOTENAME(#schemaname),
N'.',
QUOTENAME(#tablename)
)
PRINT #stm
EXEC sp_executesql #stm
Like I mentioned in the comments, if your table's definition is constantly changing this suggests a far larger design flaw in your database. Object definitions should be pretty static, and they should definitely not be changing every time you connect to the instance. That is the root cause of your problem, not that it's "too difficult", to type the name of 5 columns, rather than using *.
This means fixing your design, which we can't comment on, but I suggest that is your next major step to do. Normalise your design, and use multiple tables (as I suspect you're adding extra columns each time due to a lack of normalisation).
In the interim, you can use dynamic SQL:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Schema sysname = N'dbo',
#Table sysname = N'rCTE_Vs_Tally';
SET #SQL = N'SELECT ' +
STUFF((SELECT TOP(5)
N',' + #CRLF +
N' ' + QUOTENAME(c.[name])
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.[name] = #Schema
AND t.[name] = #Table
ORDER BY C.column_id DESC
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,10,'') + #CRLF +
N'FROM ' + QUOTENAME(#Schema) + N'.' + QUOTENAME(#Table) + #CRLF +
N'ORDER BY [Key] DESC;' --Assumes all tables have the column Key
PRINT #SQL;
EXEC sp_executesql #SQL;
Ok Deveton, as explained, a Dynamic SQL is needed for this.
I think the below would answer your question:
declare #columns varchar(max)
, #sql varchar(max)
, #tblname varchar(20) = 'Settings'
select #columns = stuff((select top 5 ',' + quotename(column_name)
from information_schema.columns
where table_name = #tblname
order by ordinal_position desc
for xml path(''), type).value('.', 'nvarchar(max)'),1,1,'')
set #sql = 'select '+#columns+' from '+quotename(#tblname)
exec (#sql)
Please advise if it works for you?
EDIT
I also added the functionality to specify top 200 and order by the Identity column descending:
declare #columns varchar(max)
, #sql varchar(max)
, #tblname varchar(20) = '_btblInvoiceLines'
declare #idcolumn varchar(max) = quotename((select column_name from information_schema.columns where table_name = #tblname and COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1))
select #columns = stuff((select top 5 ',' + quotename(column_name)
from information_schema.columns
where table_name = #tblname
order by ordinal_position desc
for xml path(''), type).value('.', 'nvarchar(max)'),1,1,'')
set #sql = 'select top 200'+#columns+' from '+quotename(#tblname)+' order by '+#idcolumn+' desc'
print(#sql)
exec (#sql)
Just press "End" key when you are in the Results tab.
After that you will be at end of columns.
I want to select phone numbers from all tables in my databese and names of these tables too. I write a query that shows me all phone_numbers but I dont't know how to select table name to each phone number. This is my query:
DECLARE #SQL AS VarChar(MAX)
SET #SQL = ''
SELECT #SQL = #SQL + 'SELECT phone_number FROM ' + TABLE_SCHEMA + '.[' + TABLE_NAME + ']' + CHAR(13)
FROM INFORMATION_SCHEMA.TABLES where table_name in (select table_name
from information_schema.columns
where column_name = 'phone_number'
)
You can simply add the table name as a constant to the SELECT clause. But, I presume you're going to want to run this query, which means you have a few more things to change:
You're probably going to want sp_executesql, which requires a Unicode variable. So, you need to DECLARE #SQL NVARCHAR(MAX).
Do you want one result set or multiple result sets? I'm guessing you want all the results in one result set, which means you're going to want to use UNION ALL between the parts of the query.
So, try something like this:
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'SELECT '''' AS table_name, '''' AS phone_number FROM [dbo].[SomeTable] WHERE 1 = 0'
DECLARE #table_name SYSNAME
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT table_name
FROM INFORMATION_SCHEMA.columns
WHERE column_name = 'phone_number'
OPEN cur
FETCH NEXT FROM cur INTO #table_name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = #sql + N' UNION ALL SELECT ''' + #table_name + N''', phone_number FROM [' + #table_name + N']'
FETCH NEXT FROM cur INTO #table_name
END
CLOSE cur
DEALLOCATE cur
EXEC sp_executesql #sql
When I used [dbo].[SomeTable], just use some table that you know exists. You would also need to modify the query if you want fully-qualified table names, but the above should get you started.
Another solution without CURSOR. You could combine each query with UNION like this.
--SELECT DISTINCT phone_number FROM dbo.Course c
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + N' SELECT DISTINCT phone_number, '''+ s.name + '.' + t.name + ''' AS TableName
FROM '+ s.name + '.' + t.name + Char(13) + ' UNION' + char(13)
FROM sys.tables t
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
INNER JOIN sys.columns c ON c.object_id = t.object_id
WHERE c.name ='phone_number'
IF(#sql != '')
BEGIN
SET #sql = LEFT(#sql,len(#sql) - 6) -- remove last UNION
PRINT #sql
-- execute sql
EXEC sp_executesql #sql
END
I also add column TableName: Table of phone_number
If you want TableName if the first column then change it
SELECT #sql = #sql + N' SELECT DISTINCT '''+ s.name + '.' + t.name + ''' AS TableName, phone_number
FROM '+ s.name + '.' + t.name + Char(13) + ' UNION' + char(13)
Does anybody know of a proc or script which will generate any row into an insert statement into the same table?
Basically, I'd like to call something like
exec RowToInsertStatement 'dbo.user', 45;
And the following code would be output
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'John', 'MacIntyre', 'Software Consultant');
I realize I could
insert into dbo.MyTable
select * from dbo.MyTable where id=45;
But this obviously won't work, because the ID column will complain (I hope it complains) and there's no way to just override that one column without listing all columns, and in some tables there could be hundreds.
So, does anybody know of a proc that will write this simple insert for me?
EDIT 3:04: The purpose of this is so I can make a copy of the row, so after the INSERT is generated, I can modify it into something like
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'Dave', 'Smith', 'Software Consultant');
.. no obviously this contrived example is so simple it doesn't make sense, but if you have a table with 60 columns, and all you need is to change 3 or 4 values, then it starts to be a hassle.
Does that make sense?
Update
I believe the following dynamic query is what you want:
declare #tableName varchar(100), #id int, #columns varchar(max), #pk varchar(20)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
print 'insert into [' + #tableName + '] (' + #columns + ')
select ' + #columns + '
from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
Update 2
The actual thing that you wanted:
declare #tableName varchar(100), #id int, #columns nvarchar(max), #pk nvarchar(20), #columnValues nvarchar(max)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
set #columnValues = 'set #actualColumnValues = (select' +
stuff((select ','','''''' + cast(['+c.name+'] as varchar(max)) + '''''''' [text()]' [text()]
from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
+ 'from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
+ 'for xml path(''''))'
--select #columnValues
declare #actualColumnValues nvarchar(max), #columnValuesParams nvarchar(500)
SET #columnValuesParams = N'#actualColumnValues nvarchar(max) OUTPUT';
EXECUTE sp_executesql #columnValues, #columnValuesParams, #actualColumnValues OUTPUT;
--SELECT stuff(#actualColumnValues, 1,1, '')
declare #statement nvarchar(max)
set #statement =
'insert into [' + #tableName + '] (' + #columns + ')
select ' + stuff(#actualColumnValues,1,1,'')
print #statement
What it does is this:
It generates the insert statement and then it queries the actual data from the table and generates the select statement with that data. May not work correctly for some really complex datatypes but for varchars, datetimes and ints should work like a charm.
This stored proc works great for me:
http://vyaskn.tripod.com/code.htm#inserts
Did you know that in Enterprise Manager and SQL Server Management Studio that you can, from the object browser, drag the list of columns into the text window and it will drop the names of all the columns into the text, separated by commas?