I have a database that uses Insert, Update, and Delete Triggers for almost all tables. They log the host and program performing the operation in a separate auditing table. The triggers all include this select statement to set variables that get inserted into the auditing table:
select #HostName = HostName, #ProgramName = Program_Name
from master..sysprocesses where SPID = ##SPID
We are now looking to migrate to Azure SQL Database, which does not support the master..sysprocesses syntax. It also appears that table is deprecated as well: https://learn.microsoft.com/en-us/sql/relational-databases/system-compatibility-views/sys-sysprocesses-transact-sql?view=sql-server-ver15
What we need to do is update the triggers to use this instead:
select #HostName = [host_name], #ProgramName = [program_name]
from sys.dm_exec_sessions where session_id = ##SPID
However, the database has hundreds of tables and each table has three triggers that need updating. The text-replacement for each trigger is identical. Is there a feasible way to script out something to perform this update on all triggers in the database?
OK, I just tested this by jamming your string in a few triggers (as a comment of course) and then running it. I am not advocating this as the correct way to do it, as this link will help you with the correct way to do dynamic sql https://dba.stackexchange.com/questions/165149/exec-vs-sp-executesql-performance
However, this does work and will help you understand how you would piece these things together to get to that point.
Note, any formatting difference between your triggers may cause this to miss some, so youll want to verify that 0on your own.
DECLARE #string VARCHAR(8000)='select #HostName = HostName, #ProgramName = Program_Name
from master..sysprocesses where SPID = ##SPID'
, #counter INT=1
, #Max INT
, #Sql VARCHAR(mAX)
;
IF OBJECT_ID('TempDB..#TrigUpdate') IS NOT NULL DROP TABLE #TrigUpdate;
CREATE TABLE #TrigUpdate
(
SqlVar VARCHAR(MAX)
, RowID INT
)
;
INSERT INTO #TrigUpdate
SELECT REPLACE(REPLACE(t.definition, #string, ''), 'CREATE TRIGGER', 'ALTER TRIGGER')
, Row_Number() OVER (ORDER BY t.Definition ASC) AS RowID
FROM sys.objects o
INNER JOIN sys.sql_modules t on o.object_id =t.object_id
WHERE o.type_desc='SQL_TRIGGER'
AND CHARINDEX(#string, t.definition,1)>0
;
SET #Max = (SELECT COUNT(*) FROM #TrigUpdate);
WHILE #Counter<=#Max
BEGIN
SET #sql = (SELECT SqlVar FROM #TrigUpdate WHERE RowID=#counter);
EXEC(#Sql);
SET #Counter=#Counter+1;
END
It could be done with Object_Definition and Replace.
Create Table #Triggers_new (TriggerName sysname, QueryText VarChar(max))
Declare #string_pattern VarChar(max), #string_replacement VarChar(max)
Select #string_pattern = '<string_pattern>'
Select #string_replacement = '<string_replacement>'
Insert Into #Triggers_new (TriggerName, QueryText)
Select [name], Replace(Object_Definition(object_id), #string_pattern, #string_replacement)
From sys.objects
Where [type] = 'TR'
Order by [name]
-- Update #Triggers_new Set QueryText = Replace(QueryText, 'Create Trigger ', 'Alter Trigger ')
Why do you use a so heavy query on system table/view that can be changed without your consent ?
Can't you simplify you by using metada functions like :
SELECT HOST_NAME(), PROGRAM_NAME()...
That will give the requested information values ?
Related
I have a system that takes in Revit models and loads all the data in the model to a 2016 SQL Server. Unfortunately, the way the system works it created a new database for each model that is loaded. All the databases start with an identical schema because there is a template database that the system uses to build any new ones.
I need to build a view that can query data from all databases on the server but can automatically add new databases as they are created. The table names and associated columns will be identical across all databases, including data types.
Is there a way to pull a list of current database names using:
SELECT [name] FROM sys.databases
and then use the results to UNION the results from a basic SELECT query like this:
SELECT
[col1]
,[col2]
,[col3]
FROM [database].[dbo].[table]
Somehow replace the [database] part with the results of the sys.databases query?
The goal would be for the results to look as if I did this:
SELECT
[col1]
,[col2]
,[col3]
FROM [database1].[dbo].[table]
UNION
SELECT
[col1]
,[col2]
,[col3]
FROM [database2].[dbo].[table]
but dynamically for all databases on the server and without future management from me.
Thanks in advance for the assistance!
***Added Info: A couple suggestions using STRING_AGG have been made, but that function is not available in 2016.
Try this. It will automatically detect and include new databases with the specified table name. If a database is dropped it will automatically exclude it.
I updated the TSQL. STRING_AGG concatenates the string with each database. Without it it only returns the last database. STRING_AGG is more secure than += which also concatenates. I changed the code so it generates and executes the query. In SQL 2019 the query is all in one line using +=. I don't have SQL 2016. It may format it better in SQL 2016. You can uncomment --SELECT #SQL3 to see what the query looks like. Please mark as answer if this is what you need.
DECLARE #TblName TABLE
(
TblName VARCHAR(100)
)
Declare #SQL VARCHAR(MAX),
#SQL3 VARCHAR(MAX),
#DBName VARCHAR(50),
#Count Int,
#LoopCount Int
Declare #SQL2 VARCHAR(MAX) = ''
Select Identity(int,1,1) ID, name AS DBName into #Temp from sys.databases
Select #Count = ##RowCount
Set #LoopCount = 1
While #LoopCount <= #Count
Begin
SET #DBName = (SELECT DBName FROM #Temp Where ID = #LoopCount)
SET #SQL =
' USE ' + #DBName +
' SELECT TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''table'''
INSERT INTO #TblName (TblName)
EXEC (#SQL)
Set #LoopCount=#LoopCount + 1
End
SELECT #SQL2 +=
' SELECT ' + char(10) +
' [col1] ' + char(10) +
' ,[col2] ' + char(10) +
' ,[col3] ' + char(10) +
' FROM [' + TblName + '].[dbo].[table] ' + char(10) +
' UNION '
FROM #TblName
DROP TABLE #Temp
SET #SQL3 = (SELECT SUBSTRING(#SQL2, 1, LEN(#SQL2) - 5))
--SELECT #SQL3
EXEC (#SQL3)
In below scenario, I'm trying to import hundreds of procedures into other database.
Solution: SQL Server
Source
SERVER: A
DATABASE: Apple
PROCEDURES: SP1, SP2, SP3 ... SP100
Destination
SERVER: B
DATABASE: Orange
First thing I did was find only non-existing procedures when compared to both databases.
To do so, I used INFORMATION_SCHEMA.ROUTINES from each database and compared in Excel.
After finding a list of procedures to be imported, I wanted to import all procedures at once.
However, if I create procedure in one-lined text, it will be saved one line which has zero visibility.
So, I used sp_helptext to copy by line for each. Then, created little query like below:
create table #proceduretext (runquery varchar(max))
insert into #proceduretext
exec sp_helptext 'SP1'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP2'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP3'
insert into #proceduretext select 'go'
.
.
.
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
Then do a select from temp table, paste result, run.
However, above was still insufficient.
Please help with below questions:
Is there a way to use INFORMATION_SCHEMA.ROUTINES for linked servers?
If there is an answer to question 1, how can I loop below query for all missing procedures?
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
You can do that with a linked server pretty easily. Here's an example I prepared for you. Run it from within the target database and change the [GREGT580] to whatever your linked server name is.
USE tempdb;
GO
-- Copying from [GREGT580] to local server
SET NOCOUNT ON;
DECLARE #MissingProcedures TABLE
(
MissingProcedureID int IDENTITY(1,1) PRIMARY KEY,
SchemaName sysname,
ProcedureName sysname,
ProcedureDefinition nvarchar(max)
);
INSERT #MissingProcedures
(
SchemaName, ProcedureName, ProcedureDefinition
)
SELECT s.[name], p.[name], sm.definition
FROM [GREGT580].AdventureWorks.sys.procedures AS p
INNER JOIN [GREGT580].AdventureWorks.sys.schemas AS s
ON p.schema_id = s.schema_id
INNER JOIN [GREGT580].AdventureWorks.sys.sql_modules AS sm
ON sm.object_id = p.object_id
WHERE NOT EXISTS (SELECT 1
FROM sys.procedures AS pl
INNER JOIN sys.schemas AS sl
ON sl.schema_id = pl.schema_id
AND sl.[name] = s.[name] COLLATE DATABASE_DEFAULT
AND pl.[name] = p.[name] COLLATE DATABASE_DEFAULT);
DECLARE #SchemaName sysname;
DECLARE #ProcedureName sysname;
DECLARE #ProcedureDefinition nvarchar(max);
DECLARE #Counter int = 1;
WHILE #Counter < (SELECT MAX(MissingProcedureID) FROM #MissingProcedures AS mp)
BEGIN
SELECT #SchemaName = mp.SchemaName,
#ProcedureName = mp.ProcedureName,
#ProcedureDefinition = mp.ProcedureDefinition
FROM #MissingProcedures AS mp
WHERE mp.MissingProcedureID = #Counter;
PRINT #ProcedureDefinition; -- Change to EXEC (#ProcedureDefinition) to create
SET #Counter += 1;
END;
I made the code just print out the procedures, so you could test it. Change the PRINT line to the EXEC option shown when you want to actually created them. (Note that PRINT will truncate what it shows if they are long but EXEC will be fine).
Hope that helps you.
try using https://www.red-gate.com/ compare tool you can download a trail version. Works great
I am trying to write this query to find all tables with specific column with some specific value. This is what I've done so far -
EXEC sp_MSforeachtable
#command1='
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=PARSENAME("?",2) AND TABLE_NAME=PARSENAME("?",1) AND COLUMN_NAME="EMP_CODE")
BEGIN
IF (SELECT COUNT(*) FROM ? WHERE EMP_CODE="HO081")>0
BEGIN
SELECT * FROM ? WHERE EMP_CODE="HO081"
END
END
'
I hope my intensions are clear, I just want to select only those tables where the column EMP_CODE is present and in those tables I want to select those rows where EMP_CODE='HO081'.
Edit -
Now it stands like this. But I'm not able to replace #EMPCODE variable in the query.
DECLARE #EMPCODE AS VARCHAR(20)
SET #EMPCODE='HO081'
EXEC sp_MSforeachtable
#command1='
DECLARE #COUNT AS INT
SELECT #COUNT=COUNT(*) FROM ? WHERE EMP_CODE='''+#EMPCODE+'''
IF #COUNT>0
BEGIN
PRINT PARSENAME("?",1)+'' => ''+CONVERT(VARCHAR,#COUNT)+'' ROW(S)''
--PRINT ''DELETE FROM ''+PARSENAME("?",1)+'' WHERE EMP_CODE='''''+#EMPCODE+'''''''
END
',#whereand='AND O.ID IN (SELECT OBJECT_ID FROM SYS.COLUMNS C WHERE C.NAME='''+#EMPCODE+''')'
You know how sp_MSforeachtable is undocumented, and may go away at any time/be modified?
Well, if you're happy to ignore that, it has another parameter called #whereand, which is appended to the WHERE clause of the internal query that is being used to find the tables (and should start with an AND).
You also have to know that there's an alias, o against sysobjects, and a second alias syso against sys.all_objects.
Using this knowledge, you might craft your #whereand parameter as:
EXEC sp_MSforeachtable
#command1='...',
#whereand='AND o.id in (select object_id from sys.columns c where c.name=''EMP_CODE'')'
You can now also simplify your command1, since you know it will only be run against tables containing an EMP_CODE column. I'd probably take out the COUNT(*) condition also, since I don't see what value it's adding.
Updated based on your further work, and tested against one table:
DECLARE #EMPCODE AS VARCHAR(20)
SET #EMPCODE='HO081'
declare #sql nvarchar(2000)
set #sql = '
DECLARE #COUNT AS INT
SELECT #COUNT=COUNT(*) FROM ? WHERE EMP_CODE='''+#EMPCODE+'''
IF #COUNT>0
BEGIN
PRINT PARSENAME("?",1)+'' => ''+CONVERT(VARCHAR,#COUNT)+'' ROW(S)''
--PRINT ''DELETE FROM ''+PARSENAME("?",1)+'' WHERE EMP_CODE='''''+#EMPCODE+'''''''
END
'
EXEC sp_MSforeachtable
#command1=#sql,#whereand='AND O.ID IN (SELECT OBJECT_ID FROM SYS.COLUMNS C WHERE C.NAME=''EMP_CODE'')'
(I've reverted the #whereand to query for EMP_CODE, since you don't want to replace the value there).
The issue is that, you can pass parameters to a stored procedure, or literals, but you can't perform calculations/combining actions between them - so I moved the construction of the sql statement out into a separate action.
I guess you get an error of some kind, perhaps Invalid column name 'EMP_CODE'?
It's because the code is compiled before you check for the column.
You could do like this instead.
EXEC sp_MSforeachtable
#command1='
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=PARSENAME("?",2) AND TABLE_NAME=PARSENAME("?",1) AND COLUMN_NAME="EMP_CODE")
BEGIN
EXEC(''
IF (SELECT COUNT(*) FROM ? WHERE EMP_CODE="HO081")>0
BEGIN
SELECT * FROM ? WHERE EMP_CODE="HO081"
END
'')
END
'
We have a problem with our table schema falling out of sync with our view schema. I would like to know how I could have a stored procedure (for Sql Server) that gets all views in the database, and executes each one via select *
Here is what I imagined (pseudo):
Declare x
Set x = Select object from sysobjects where object = view
foreach view in x
sp_execute 'select * from view'
We could then have an automated test that calls this every night. A SqlException would indicated that something was out of sync.
should work in 2000 and up
select quotename(table_schema) +'.' + quotename(table_name) as ViewNAme,
identity(int,1,1) as ID
into #test
from information_schema.tables
where table_type = 'view'
declare #Loopid int,#MaxID int
select #LoopID =1,#MaxID =MAX(id)
from #test
declare #ViewName varchar(100)
while #LoopID <= #MaxID
begin
select #ViewName = ViewNAme
from #test
where id = #LoopID
exec ('select top 1 * from ' + #ViewName)
set #LoopID = #LoopID + 1
end
drop table #test
I mostly focused on one part of your question, see also how to make sure that the view will have the underlying table changes by using sp_refreshview
I'd really suggest you use WITH SCHEMABINDING to prevent this happening.
Or use sp_refreshview in a loop at least.
SELECT * FROM view is not reliable: how do know if the output is correct or not?
In SQL 2008 you can use the following to detect unresolved dependencies without having to actually select from the view.
SELECT *
FROM sys.views v
JOIN sys.sql_expression_dependencies e ON e.referencing_id = v.object_id
and referenced_id is null
In SQL Server, is there any way to check whether the changes in the schema will impact Stored Procedures (and/or Views)?
For example a change of the column name in one table, may break some Stored Procedures; how to check the impacted stored procs?
try using:
EXEC sp_depends 'YourTableName'
and/or
DECLARE #Search nvarchar(500)
SET #Search='YourTableName' --or anything else
SELECT DISTINCT
LEFT(o.name, 100) AS Object_Name,o.type_desc
FROM sys.sql_modules m
INNER JOIN sys.objects o ON m.object_id=o.object_id
WHERE m.definition Like '%'+#Search+'%'
ORDER BY 2,1
Use Visual Studio Database Edition for your T-SQL development. It will catch such problems during build, as it creates the deployment .dbschema file.
In SSMS (SQL Server Management Studio) right click on the object you are changing and click on View Dependencies. I don't think this will find references from another database.
You can also look for references in stored procedures if they are not encrypted. You would have to do this in each database you suspect might reference the object you are changing.
select objects.name
,sql_modules.definition
from sys.sql_modules sql_modules
join sys.objects objects on sql_modules.object_id = objects.object_id
where definition like '%some column name%';
I have found nothing that is 100.0000% accurate 100.000000% of the time.
Best way I can think to do this is to abstract your stored procedures from your actual tables using views, and to create those views with a "WITH SCHEMABINDING" clause which should prevent changes that will break your views...
Commercial tools such as Red Gate's SQL Refactor can do this.
I think that recent version of Visual Studio also include this kind of features, but I haven't tried.
To my knowledge, there are no built-in features of Microsoft SQL Server per-se which will do this. Correction: I just read about sp_depends in KM's answer to this post... Note that sp_depends's usage is deprecated; it is replaced by sys.dm_sql_referencing_entities and sys.dm_sql_referenced_entities
Also, if the underlying stored procedures use dynamic SQL, the task of detecting dependencies becomes more difficult and prone to "misses".
If you want to change the name of an object or column, then the Smart Rename feature of Red Gate Software's SQL Prompt 5 will generate a script that both performs the rename and updates references to the old name in other objects.
If you're just interested in what depends on a column name, then SQL Prompt 5 also has a Column Dependencies function, where hovering over the column name in a script pops up a window containing a list of objects that refer to the column.
You can download a 14-day trial for free, to see if either of these features works for you.
Paul Stephenson
SQL Prompt Project Manager
Red Gate Software
Have a look at these answers:
Refreshing metadata on user functions t-SQL
SQL Server relationships buried in stored procedures rather than schema
In SQL Server, how can I find everywhere a column is referenced?
How do I find all stored procedures that insert, update, or delete records?
Other than dynamic SQL, using SCHEMABINDING where possible and sp_refreshsqlmodule and sql_dependencies for everything else is very accurate.
If you use SQL Server
You can use this query after your change and find Stored Procedure Or View Or ...
that after your change might get error
USE <Your_DataBase_Name>;
SET NOCOUNT ON;
DECLARE #name NVARCHAR(MAX)
DECLARE #sql NVARCHAR(MAX)
DECLARE #type CHAR(2)
DECLARE #type_desc NVARCHAR(60)
DECLARE #params NVARCHAR(MAX)
DECLARE #tblInvalid TABLE
(
[type_desc] NVARCHAR(60) ,
[name] NVARCHAR(MAX) ,
[error_number] INT ,
[error_message] NVARCHAR(MAX) ,
[type] CHAR(2)
);
DECLARE testSPs CURSOR FAST_FORWARD
FOR
SELECT [name] = OBJECT_NAME(SM.[object_id]) ,
[type] = SO.[type] ,
SO.[type_desc] ,
[params] = ( SELECT (
SELECT CONVERT(XML, ( SELECT STUFF(( SELECT
', ' + [name]
+ '=NULL' AS [text()]
FROM
sys.parameters
WHERE
[object_id] = SM.[object_id]
FOR
XML
PATH('')
), 1, 1, '')
))
FOR XML RAW ,
TYPE
).value('/row[1]', 'varchar(max)')
)
FROM sys.sql_modules SM
JOIN sys.objects SO ON SO.[object_id] = SM.[object_id]
WHERE SO.[is_ms_shipped] = 0
AND SO.[type] = 'P'
OPEN testSPs
FETCH NEXT FROM testSPs INTO #name, #type, #type_desc, #params
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
BEGIN TRY
SET #sql = 'SET FMTONLY ON; exec ' + #name + ' ' + #params
+ '; SET FMTONLY OFF;'
--PRINT #sql;
EXEC (#sql);
END TRY
BEGIN CATCH
PRINT #type_desc + ', ' + #name + ', Error: '
+ CAST(ERROR_NUMBER() AS VARCHAR) + ', ' + ERROR_MESSAGE();
INSERT INTO #tblInvalid
SELECT #type_desc ,
#name ,
ERROR_NUMBER() ,
ERROR_MESSAGE() ,
#type;
END CATCH
FETCH NEXT FROM testSPs INTO #name, #type, #type_desc, #params
END
CLOSE testSPs
DEALLOCATE testSPs
SELECT [type_desc] ,
[name] ,
[error_number] ,
[error_message]
FROM #tblInvalid
ORDER BY CHARINDEX([type], ' U V PK UQ F TR FN TF P SQ ') ,
[name];