I've created a system stored procedure in the Master database which can be run in all databases. I want to run it in all databases at once, here what I use:
use Master
GO
declare #sql nvarchar(1000)
SET #sql = 'USE [?]; EXEC [dbo].[sp_procedure]'
EXEC sp_MSforeachdb #sql
The problem is - not all databases have have similar table and column structure. So inside procedure, I do this before doing any calculations:
if exists(select 1
from
INFORMATION_SCHEMA.TABLES b,
INFORMATION_SCHEMA.COLUMNS c
where
b.TABLE_TYPE = 'BASE TABLE'
and b.TABLE_NAME = 'tablename'
and c.TABLE_NAME = b.TABLE_NAME
and c.COLUMN_NAME = 'columnname')
So, if while running across all databases, there is no table named 'tablename' with column name 'columnname' it should skip procedure for that database and go on to the next database. I have a database which has 'tablename' table, but doesn't have 'columnname' column in this table, and it returns this error:
Invalid column name 'columnname'.
Why that if exists statement goes inside if statement? Shouldn't it terminate as soon as condition is not met? How can I handle this situation?
That is because SQL Engine tries to compile the code, so it checks the code inside IF block.
You have to use that comparison outside the stored procedure, e.g. in your SET #sql = '......' command, but it will not run that SP if the column does not exist.
So you can try something else - build the query (nvarchar variable) inside the SP and execute it as dynamic SQL.
Related
My office is changing our linked servers. As a result, I need to get a list of every single view from every database on our instance that points to the current linked server so we can know what needs replaced.
After doing some research online, I came up with this solution to get a list of all the views that reference the linked server:
Create a temp table:
CREATE TABLE [dbo].[#TMP]
(
[DBNAME] NVARCHAR(256) NULL,
[NAME] NVARCHAR(256) NOT NULL,
[DESC] NVARCHAR(MAX) NOT NULL
);
Then, I can take advantage of the sp_msforeachdb procedure to iterate through each database, and store this information in the temporary table:
DECLARE #command varchar(1000)
SELECT #command = 'INSERT INTO #TMP SELECT ''?'' as DBName, OBJECT_NAME(object_id), definition FROM sys.sql_modules WHERE definition LIKE ''%linkedservername%'''
EXEC sp_msforeachdb #command
When I do a SELECT * from #TMP, I see something fishy... the same 5 views are repeated for EVERY database. It's as if it took the first 5 views in a database that had by linked server name, and then just copied it for every database!
Things get even weirder if I modify my select command by changing sys.sql_modules to [?].sys.sql_modules; in this case, rather than getting 565 results, I only get 17!!!
Now, if I take out the INSERT INTO #TMP" part of the command, and run the following:
DECLARE #command varchar(1000)
SELECT #command = 'SELECT ''?'' as DBName, OBJECT_NAME(object_id), definition FROM sys.sql_modules WHERE definition LIKE ''%linkedservername%'''
EXEC sp_msforeachdb #command
The results get even weirder! In one of my databases named "DB_Jobs", in the column for views (there isn't a column name), 3 of the 4 results returns NULL, and the last results returns "SCHTYPEMismatch". Stranger yet, in the definition column, it returns accurate results!!!
Then, if I go to the database and run this:
SELECT OBJECT_NAME(object_id), definition
FROM [DB_Jobs].[sys].[sql_modules]
WHERE definition LIKE '%linkedservername%'
it returns the results perfectly!
What's going on? More importantly, what can I do in my original #command to utilize sp_msforeachdb and correctly return the results I want (and include the database name for each result)?
By the way, I'm on SQL Server 2014.
Sp_msforeachdb is basically a global cursor that gives you access to the each database in turn by referencing [?]. It doesn't execute your command on each db by default. You can see this if you run a simple
EXEC sp_msforeachdb 'select db_name()'
For your first example, you're getting the same views because you're running the command against the same database every time. When you switch to [?].sys.sql_modules you start querying the sys.sql_modules in that database referenced by [?].
The problem with NULLs can be seen by running something like this:
SELECT OBJECT_NAME(object_id), definition FROM [msdb].[sys].[sql_modules] WHERE definition LIKE '%name%'
Run it in MSDB and you'll have a column name full of object names and a column with definitions. Run it in Master and the object names are now NULL even though you have the definitions. OBJECT_NAME() runs in the context of the current database, so you get NULLs unless you happen to have an object_id that matches, but then you're displaying the wrong object name. Definitions is directly referencing the database you want, so you still get them.
To get your query to work as you want it you just need to USE [?] (I'm looking for a definition like %name% because I know it will be there for testing)
CREATE TABLE [dbo].[#TMP](
[DBNAME] NVARCHAR(256) NULL,
[NAME] NVARCHAR(256) NOT NULL,
[DESC] NVARCHAR(MAX) NOT NULL);
DECLARE #command varchar(1000)
SELECT #command = 'USE [?]; INSERT INTO #TMP SELECT ''?'' as DBName, OBJECT_NAME(object_id), definition FROM sys.sql_modules WHERE definition LIKE ''%name%'''
EXEC sp_msforeachdb #command
SELECT * FROM #TMP
I'm checking the validity of existing stored procedures, by obtaining their definition and running the ALTER statement on them.
The problem I have is that any stored procedure which doesn't compile (because a dependency has gone) isn't being flagged as such.
If I try to run the same ALTER command in SSMS I do get the error message.
EDIT: No, I don't....
DECLARE #def nvarchar(MAX)
BEGIN TRY
-- refresh the stored procedure
SELECT #def = REPLACE(definition,'CREATE PROCEDURE ','ALTER PROCEDURE ')
FROM sys.sql_modules
WHERE ... -- selecting/limiting clause
EXEC (#def);
END TRY
BEGIN CATCH
PRINT 'Validation failed : ' + ERROR_MESSAGE()
END CATCH
What do I have to do to trap the non-compile error? Thanks
SQL Server stored procedures use deferred name resolution:
When a stored procedure is created, the statements in the procedure are parsed for syntactical accuracy. If a syntactical error is encountered in the procedure definition, an error is returned and the stored procedure is not created. If the statements are syntactically correct, the text of the stored procedure is stored in the sys.sql_modules catalog view.
When a stored procedure is executed for the first time, the query processor reads the text of the stored procedure from the sys.sql_modules catalog view and checks that the names of the objects used by the procedure are present. This process is called deferred name resolution because table objects referenced by the stored procedure need not exist when the stored procedure is created, but only when it is executed.
So the behavior you observe is intentional. What you need is to find out what procedures depend on your missing tables. For this, see View the Dependencies of a Stored Procedure and the proper answer depends on your SQL Server version. SQL Server 2016 is somehow better at tracking this information and offers better views. Before that the process was notoriously difficult unreliable, read Keeping sysdepends up to date in SQL Server 2008.
Forget this - barking up the wrong tree ;-((
The stored proc will compile OK even if its dependencies have gone.
The editor in SSMS highlights the missing items, but doesn't stop the ALTER statement from working.
This query will identify all stored procs with missing dependencies:
-- table variable to store procedure names
DECLARE #v TABLE (RecID INT IDENTITY(1,1), spname sysname)
-- retrieve the list of stored procedures
INSERT INTO #v(spname)
SELECT
'[' + s.[name] + '].[' + sp.name + ']'
FROM sys.procedures sp
INNER JOIN sys.schemas s ON s.schema_id = sp.schema_id
WHERE is_ms_shipped = 0
AND sp.name like 'Get%'
-- counter variables
DECLARE #cnt INT, #Tot INT
SELECT #cnt = 1
SELECT #Tot = COUNT(*) FROM #v
DECLARE #spname sysname
DECLARE #ref nvarchar(MAX)
-- start the loop
WHILE #Cnt <= #Tot BEGIN
SELECT #spname = spname
FROM #v
WHERE RecID = #Cnt
BEGIN
SELECT #ref = referenced_entity_name
FROM sys.dm_sql_referenced_entities (#spname, 'OBJECT')
WHERE referenced_id IS NULL;
END
SET #Cnt = #cnt + 1
END
I would like to know how I can switch from one database to another within the same script. I have a script that reads the header information from a SQL Server .BAK file and loads the information into a test database. Once the information is in the temp table (Test database) I run the following script to get the database name.
This part works fine.
INSERT INTO #HeaderInfo EXEC('RESTORE HEADERONLY
FROM DISK = N''I:\TEST\database.bak''
WITH NOUNLOAD')
DECLARE #databasename varchar(128);
SET #databasename = (SELECT DatabaseName FROM #HeaderInfo);
The problem is when I try to run the following script nothing happens. The new database is never selected and the script is still on the test database.
EXEC ('USE '+ #databasename)
The goal is switch to the new database (USE NewDatabase) so that the other part of my script (DBCC CHECKDB) can run. This script checks the integrity of the database and saves the results to a temp table.
What am I doing wrong?
You can't expect a use statement to work in this fashion using dynamic SQL. Dynamic SQL is run in its own context, so as soon as it has executed, you're back to your original context. This means that you'd have to include your SQL statements in the same dynamic SQL execution, such as:
declare #db sysname = 'tempdb';
exec ('use ' + #db + '; dbcc checkdb;')
You can alternatively use fully qualified names for your DB objects and specify the database name in your dbcc command, even with a variable, as in:
declare #db sysname = 'tempdb';
dbcc checkdb (#db);
You can't do this because Exec scope is limited to dynamic query. When exec ends context is returned to original state. But context changes in Exec itself. So you should do your thing in one big dynamic statement like:
DECLARE #str NVARCHAR(MAX)
SET #str = 'select * from table1
USE DatabaseName
select * from table2'
EXEC (#str)
I have around 50 tables in my database. In all tables where there is userid column (Not all the tables contain this column), I need to change the value of it from "User1" to "User2". This query would be re-used many times with changing values of "User1" and "User2"
Probably create a stored procedure to do the same like
create procedure sp_update_table(#tbl_name varchar(30))
as
begin
DECLARE #sql AS NVARCHAR(MAX)
SET #sql = N'UPDATE ' + QUOTENAME(#tbl_name ) +
'SET userid='User2' WHERE userid='User1''
EXEC sp_executesql #sql
end
then just call your procedure as many times you want passing the table name like
exec sp_update_table('mytable')
EDIT:
You can easily find all tables which contains userid column from INFORMATION_SCHEMA.COLUMNS as below
Use [DatabaseName]
Select table_name From INFORMATION_SCHEMA.COLUMNS Where column_name = 'userid'
Write 50 update statements:
UPDATE <TABLE NAME>
SET userid='User2'
WHERE userid='User1'
It should be easy enough to generate these in a simple text editor and then paste into SQL Server Management Studio.
I have db A and db B. At the beginning of a stored procedure I want to back up all rows from B.mytable to B.mytablebackup. The rest of the stored procedure runs against tables on db A (which gathers data and writes it to B.mytable).
So I check to see if B.mytablebackup exists
IF EXISTS(SELECT 1 FROM B.dbo.mytablebackup)
and if it does, the stored procedure does an
INSERT INTO B..mytablebackup SELECT * FROM B..mytable
If it doesn't exist it does a
SELECT * INTO B..mytablebackup from B..mytable
But when I execute the stored procedure I get the error
There is already an object named 'mytablebackup' in the database
I added a Print statement and execution is taking the "does not exist" branch of the IF.
What am I doing wrong?
For SQL Server, you should use system view sys.tables to check if table exists.
IF EXISTS(SELECT 1 FROM B.sys.tables WHERE name = 'mytablebackup')
OBJECT_ID can be used too:
IF OBJECT_ID('B.dbo.mytablebackup') IS NOT NULL
You can directly check from the given DB,SCHEMA and TABLE parameters (For dynamic database, schema and table use)
DECLARE #targetdatabase NVARCHAR(MAX),
#SchemaName NVARCHAR(MAX),
#TableName NVARCHAR(MAX)
DECLARE #TempTableName NVARCHAR(MAX) = QUOTENAME(#targetdatabase) + '.' +
QUOTENAME(#SchemaName) + '.' + QUOTENAME(#TableName)
IF OBJECT_ID(#TempTableName) IS NULL
BEGIN
PRINT #TempTableName
END