Using the correct database when calling a system stored procedure in SQL Server 2008
-
22-10-2019 - |
Question
I think there's something I'm missing when it comes to which database is being used when running a custom system stored procedure. I have the following in my stored procedure (edited for brevity):
ALTER PROCEDURE sp_mysp
AS
IF (EXISTS (
SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'mytable'))
BEGIN
--Do stuff to the table
END
ELSE
BEGIN
PRINT 'Table mytable does not exist'
END
The problem is that when I call the procedure from mydb, the Master table is used for the initial check, rather than the current database. If I write mydb.INFORMATION_SCHEMA.TABLES it works, but that's not really a solution, as it's defeating the whole point of maintaining this as a single SP rather than one SP for each DB.
Any ideas? I tried passing the DB name as a parameter and starting the SP with 'use @db_name', but apparently stored procedures don't allow use statements.
Thanks in advance for all help.
Solution
You need to mark your stored procedure as a system object to get the behaviour that you want (full example below tested as working on 2008 SP3)
USE master;
GO
CREATE PROCEDURE dbo.sp_mysp
AS
SELECT *
FROM INFORMATION_SCHEMA.TABLES
GO
USE tempdb;
EXEC dbo.sp_mysp /*Returns tables from master*/
GO
USE master;
EXEC sys.sp_MS_marksystemobject sp_mysp
GO
USE tempdb;
EXEC dbo.sp_mysp /*Returns tables from tempdb*/
This is an undocumented approach and may not work in future versions.
OTHER TIPS
Alternative: using dynamic SQL. And no, in controlled admin functionality, dynamic SQL is not the evil thing it's often made out to be.
USE master;
GO
CREATE PROCEDURE dbo.sp_find_table
@db_name SYSNAME,
@table_name SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX);
SELECT @sql = 'SELECT QUOTENAME(s.name)
+ ''.'' + QUOTENAME(t.name)
FROM ' + @db_name + '.sys.tables AS t
INNER JOIN ' + @db_name + '.sys.schemas AS s
ON t.[schema_id] = s.[schema_id];';
EXEC sp_executesql @sql;
END
GO
Now your call from any other database can be:
DECLARE @db SYSNAME = DB_NAME(DB_ID());
EXEC master.dbo.sp_find_table @db_name = @db, @table_name = 'floob';
Or simply:
EXEC master.dbo.sp_find_table 'tempdb', @table_name = 'floob';
You can even create a synonym in model (and all existing databases) like follows:
CREATE SYNONYM dbo.sp_find_table FOR master.dbo.sp_find_table;
GO
(Now you can take the prefix off of the above calls.)
While your call is one step more complex, the benefits of this approach:
- you can put this procedure in any utility database you want, instead of mucking up master with your procedures. Dependence on non-system objects in master can be an issue if you migrate to a new server, for example.
- you don't rely on undocumented, unsupported, and not-guaranteed-to-be-forward-compatible functionality.
- you can call it for any database, from any database, without relying on the current database context.