Check if View exists before querying it - sql

I want to check if a specific View exists before querying it. I use dynamic SQL to create the query:
DECLARE #sqlCommand varchar(1000)
DECLARE #viewName varchar(1000)
DECLARE #otherDB varchar(1000)
SET #sqlCommand = 'IF EXISTS(SELECT 1 FROM ' + #otherDB + '.sys.views WHERE name=''' + #viewName + ''')
BEGIN
SELECT * FROM ' + #viewName + '
END'
EXEC (#sqlCommand)
So everything works fine as long as #viewName actually exists. However, if #viewName is a View that does not exist in sys.views, I get an error from the compiler:
The OLE DB provider "SQLNCLI11" for linked server "server" does not contain the table #viewName. The table either does not exist or the current user does not have permiossions on that table.
I would have thought that since an IF statement is used, it would just skip the querying of the View. However, seems like the View has to exist otherwise I get the above error.
I've tried alternate solutions, such as using strings for the View names, but no luck. I've also tried the solution in: How to check the existence of a view, but at some point I have to reference the View name in my query, and it would complain
Any info would be greatly appreciated!

Check for the existence of the view outside the dynamic SQL. You are trying to prevent the compile-time error of the view not existing in the select. There is no issue with the if:
IF EXISTS(SELECT 1 FROM sys.views WHERE name = #viewName)
BEGIN
SET #sqlCommand = 'SELECT * FROM ' + #viewName
EXEC(#sqlCommand)
END;
Although it doesn't make a difference in this case, if you are using dynamic SQL, learn about sp_executesql -- it is more powerful than exec() because you can pass variables in and out.
EDIT:
In that case, you essentially have to do dynamic SQL inside dynamic SQL. The following is not tested, so there could be a syntax error:
DECLARE #viewName varchar(1000);
DECLARE #otherDB varchar(1000);
declare #sql nvarchar(max) = '
IF EXISTS (SELECT 1 FROM #otherDB.sys.views WHERE name = ''#viewName'')
BEGIN
DECLARE #sqlCommand nvarchar(max);
SET #sqlCommand = ''SELECT * FROM #viewName'';
EXEC(#sqlCommand);
END;';
SET #sql = replace(replace(#ql, '#otherDB', #otherDB), '#viewName', #viewName);
EXEC(#sql);

What version of SQL Server are you using? I only have SQL Server 2014 available to test with, but the T-SQL below works for both missing and not missing views. I wonder whether the fact that you are checking for existence of the view in otherdb.sys.views but are not qualifying otherdb when selecting from the view is to blame?
declare #viewName varchar(50) = 'MissingView';
declare #sqlCommand nvarchar(1000);
declare #otherdb varchar(20) = 'MyTestDatabase';
set #sqlCommand = N'if exists
(
select 1
from ' + #otherdb + '.sys.views as v
where v.name = ''' + #viewName + '''
)
begin
select * from ' + #otherdb + '.dbo.' + #viewName + ';
end
else
begin
select ''Nah mate - missing view'';
end';
print #sqlCommand;
execute sp_executesql #sqlCommand;

You can use the Else condition when not exists to set error message
DECLARE #sqlCommand varchar(1000)
DECLARE #viewName varchar(1000)
SET #viewName = 'vwName'
SET #sqlCommand = 'IF EXISTS(SELECT 1 FROM sys.views WHERE name=''' + #viewName + ''')
BEGIN
SELECT * FROM ' + #viewName + '
END
ELSE
BEGIN
SELECT ''View not exists''
END
'
EXEC (#sqlCommand)

Beware if your view is in a different schema, because then you need to also check the SCHEMAS table:
SELECT 1 FROM SYS.VIEWS
INNER JOIN SYS.SCHEMAS ON SYS.SCHEMAS.schema_id = SYS.VIEWS.schema_id
WHERE SYS.VIEWS.TYPE='V'
AND SYS.SCHEMAS.NAME=#Your_Schema_Name
AND SYS.VIEWS.NAME=#Your_View_Name

Related

SQL Server view - bad naming convention?

DECLARE #TableName AS VARCHAR(250);
DECLARE #SQL AS VARCHAR(500);
DECLARE #ViewCheck as CURSOR;
SET #ViewCheck = CURSOR FOR
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'VIEW' AND TABLE_NAME LIKE 'V_WFC%'
OPEN #ViewCheck
FETCH NEXT FROM #ViewCheck INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
Set #SQL = 'SELECT TOP 10 * FROM ' + #TableName
PRINT(#SQL)
EXEC(#SQL)
FETCH NEXT FROM #ViewCheck INTO #TableName;
END
CLOSE #ViewCheck
I have a cursor that runs through all SQL views in a particular schema to sanity check that they continue to function, some are tied to reporting and some used as an application data source in ProSolution.
One of these views is named UnmarkedRegister(Today) the brackets used to differentiate it from a from a similar view, this one is used within an application to drive display data.
While the query runs as expected, returning the correct data - the cursor returns an error
Msg 208, Level 16, State 1, Line 1
Invalid object name 'V_WFC_UnmarkedRegister'
and I'm wondering why the bracketed section is omitted in the EXEC(SQL) section of the cursor?
Use quotename():
Set #SQL = 'SELECT TOP 10 * FROM ' + QUOTENAME(#TableName);
I truly detest cursors and there is no need for a cursor here at all. You can greatly simplify this code in a couple of ways. First I am using sys.views instead of the INFORMATION_SCHEMA views. And then I am using sql to build a dynamic sql string instead of a cursor. Look how simple this can be.
declare #SQL nvarchar(max) = '';
select #SQL = #SQL + 'select top 10 * from ' + QUOTENAME(v.name) + '; select ''('' + convert(varchar(2), ##ROWCOUNT) + '' rows affected'';'
from sys.views v
where v.name LIKE 'V_WFC%'
print #SQL
exec sp_executesql #SQL
Because brackets usually identify a function call
changing this line:
Set #SQL = 'SELECT TOP 10 * FROM ' + #TableName
to this should fix it:
Set #SQL = 'SELECT TOP 10 * FROM [' + #TableName + ']'

Iterate Through and Rename All Tables w/ Object Qualifiers MSSQL

I need to iterate through all of the tables that begin with a specific prefix to rename them. The code I've tried is below, but it ends with one of two results, either it crashes SSMS (sometimes), or I get the error message below for each table. I've tried with and with out dbo.
Can anyone tell me what I'm doing wrong or perhaps suggest a better way to do this?
No item by the name of 'dbo.prefix_TableName' could be found in the current database 'DatabaseName', given that #itemtype was input as '(null)'.
Here's the code I'm running...
SET NOCOUNT ON;
USE [DatabaseName];
DECLARE #oq NVARCHAR(5), #tableName NVARCHAR(128), #newTableName NVARCHAR(128);
SET #oq = N'prefix_';
/*
find and rename all tables
*/
DECLARE [tableCursor] CURSOR FOR
SELECT [TABLE_NAME] FROM INFORMATION_SCHEMA.TABLES
WHERE [TABLE_TYPE] = 'BASE TABLE' AND [TABLE_NAME] LIKE #oq + '%'
ORDER BY [TABLE_NAME];
OPEN [tableCursor]
FETCH NEXT FROM [tableCursor] INTO #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #newTableName = REPLACE(#tableName, #oq, N'');
EXEC('EXEC sp_rename ''dbo.' + #tableName + ''', ''' + #newTableName + '''');
END
CLOSE [tableCursor];
DEALLOCATE [tableCursor];
A simpler solution without cursors
declare #oq nvarchar(max) = N'prefix_'
declare #cmd nvarchar(max)
select #cmd = a from (
select 'EXEC sp_rename ''' + TABLE_NAME + ''', ''' + REPLACE(TABLE_NAME, #oq, N'') + ''' '
from INFORMATION_SCHEMA.TABLES
for xml path('')
) t(a)
exec sp_executesql #cmd
In your example nvarchar(5) causes truncation, you probably need nvarchar(7) or nvarchar(max).

Using variables in Transact-sql exists subquery

this seems like it should be extraordinarily simple, so I apologize in advance if this information is easily accessible on the transact-sql documentation pages. I searched myself, but couldn't seem to find anything.
I'm trying to modify a transact-sql statement that currently runs on our Windows server 2000 box. I want to check if a table in another database exists, and then do a bunch of stuff. The database name is given as a string argument, '#dbName'
CREATE PROCEDURE CopyTables
#dbName char(4)
AS
IF EXISTS (SELECT * FROM #dbName.INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME = N'MainTable')
BEGIN
--Do Stuff
In it's current state, it doesn't like using the bare #dbName variable within the select statement. Is there special syntax for doing this?
Thanks in advance.
The below code should do what you want. As was mentioned previously, the account running the query would need the privilege to query the INFORMATION_SCHEMAs in the target database.
To future-proof your stored procedure, I'd also suggest increasing the length of the database name parameter and declaring it as an nchar or nvarchar in stead of char.
CREATE PROCEDURE CopyTables
#dbName char(4)
AS
DECLARE
#SQLStr nvarchar (max),
#Params nvarchar (max),
#Count tinyint;
SET
#Count = 0;
SET #SQLStr = N'SELECT #qCount = 1 FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N''MainTable''';
SET #Params = N'#qdbName char (4), #qCount tinyint OUTPUT';
EXECUTE sp_executesql #SQLStr, #Params, #qdbName = #dbName, #qCount = #Count OUTPUT;
IF #Count = 1
BEGIN
--Do Stuff
END; -- if
GO
Try doing the following:
DECLARE #dbName NVARCHAR(MAX) = 'master', #TableName NVARCHAR(MAX) = N'spt_monitor';
DECLARE #sql NVARCHAR(MAX) = N'SELECT * FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''' + REPLACE(#TableName,N'''',N'''''') + N'''';
SET NOCOUNT OFF;
EXEC(#sql);
IF ##ROWCOUNT > 0 BEGIN;
-- DO STUFF
SELECT NULL;
END;
There are a few shortcomings to this solution:
1) It requires that the user executing the statement has SELECT access to the other database's INFORMATION_SCHEMA.TABLES
2) It has the side-effect of actually selecting the rows, so if you're using a reader to access the results, you'll have to call reader.NextResult() or await reader.NextResultAsync() because it actually outputs the results of the SELECT statement, rather than doing it in an IF EXISTS context.
By merging the two solutions, we get this:
DECLARE #dbName NVARCHAR(MAX) = 'master', #TableName NVARCHAR(MAX) = N'spt_monitor';
DECLARE #sql NVARCHAR(MAX) = N'SELECT #count = COUNT(*) FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''' + REPLACE(#TableName,N'''',N'''''') + N'''';
DECLARE #Count INT;
EXECUTE sp_executesql #sql, N'#Count INT OUTPUT', #Count OUTPUT;
IF #Count > 0 BEGIN;
-- Do stuff
SELECT 'the table exists';
END ELSE BEGIN;
-- Do stuff
SELECT 'the table does not exist';
END;
This solution requires that the user executing the statement has SELECT access to the other database's INFORMATION_SCHEMA.TABLES, but it does not have the side-effect of selecting rows, like my previous solution.

How to check which database exists and use it in the function? [duplicate]

This question already has answers here:
How to use a variable for the database name in T-SQL?
(4 answers)
Closed 8 years ago.
Given different machines with sql server. All of them have the same databases. The only difference between these db's is their name. It could be two names, let's say 'DB1' and 'DB2'.
I need to check, which name is used on the given machine and create a function over it. The function is pretty big, it has at least 50 spots where the name of the DB is needed.
I was trying to do something like
DECLARE #dbName VARCHAR(20)
SET #dbName = ISNULL(DB_ID('DB1'), 'DB2');
SELECT * FROM #dbName.dbo.testtable;
But it does not work.
Any help would be appreciated.
No, that won't work. With only two possible database you may be better off with an if:
DECLARE #dbName VARCHAR(20)
SET #dbName = CASE WHEN DB_ID('DB1') IS NULL THEN 'DB2' ELSE 'DB1' END;
IF #dbName = 'DB1'
SELECT * FROM DB1.dbo.testtable;
ELSE
SELECT * FROM DB2.dbo.testtable;
If you want to run ALL future queries in that scope against that database then dynamically run a USE statement instead:
IF #dbName = 'DB1'
USE DB1;
ELSE
USE DB2;
You can use dynamic SQL:
DECLARE #dbName VARCHAR(20)
SET #dbName = 'DB2'
IF DB_ID('DB1') IS NOT NULL
SET #dbName = 'DB1'
DECLARE #SQL NVARCHAR(100)
SET #SQL = N'SELECT * FROM ' + #dbName + N'.dbo.testtable'
EXEC sp_executesql #SQL
You may try like this using the DB_ID function:
IF DB_ID('DB1') IS NOT NULL
PRINT 'exists'
Try like this:
DECLARE #db VARCHAR(20)
SET #db = CASE WHEN DB_ID('DB1') IS NULL
THEN 'DB2'
ELSE 'DB1'
END;
IF #db = 'DB1'
SELECT * FROM DB1.dbo.testtable;
ELSE
SELECT * FROM DB2.dbo.testtable;
DECLARE #dbName NVARCHAR(128);
DECLARE #Sql NVARCHAR(MAX);
SET #dbName = 'DataBaseName'
IF db_id(#dbName) is not null
BEGIN
SET #Sql = N'SELECT * FROM ' + QUOTENAME(#dbName) + N'.[dbo].[TableName]'
END
ELSE
BEGIN
SET #Sql = N'SELECT * FROM ' + QUOTENAME(db2Name) + N'.[dbo].[TableName]'
END
EXECUTE sp_Executesql #Sql

Executing dynamic SQL in a SQLServer 2005 function

I will preface this question by saying, I do not think it is solvable. I also have a workaround, I can create a stored procedure with an OUTPUT to accomplish this, it is just easier to code the sections where I need this checksum using a function.
This code will not work because of the Exec SP_ExecuteSQL #SQL calls. Anyone know how to execute dynamic SQL in a function? (and once again, I do not think it is possible. If it is though, I'd love to know how to get around it!)
Create Function Get_Checksum
(
#DatabaseName varchar(100),
#TableName varchar(100)
)
RETURNS FLOAT
AS
BEGIN
Declare #SQL nvarchar(4000)
Declare #ColumnName varchar(100)
Declare #i int
Declare #Checksum float
Declare #intColumns table (idRecord int identity(1,1), ColumnName varchar(255))
Declare #CS table (MyCheckSum bigint)
Set #SQL =
'Insert Into #IntColumns(ColumnName)' + Char(13) +
'Select Column_Name' + Char(13) +
'From ' + #DatabaseName + '.Information_Schema.Columns (NOLOCK)' + Char(13) +
'Where Table_Name = ''' + #TableName + '''' + Char(13) +
' and Data_Type = ''int'''
-- print #SQL
exec sp_executeSql #SQL
Set #SQL =
'Insert Into #CS(MyChecksum)' + Char(13) +
'Select '
Set #i = 1
While Exists(
Select 1
From #IntColumns
Where IdRecord = #i)
begin
Select #ColumnName = ColumnName
From #IntColumns
Where IdRecord = #i
Set #SQL = #SQL + Char(13) +
CASE WHEN #i = 1 THEN
' Sum(Cast(IsNull(' + #ColumnName + ',0) as bigint))'
ELSE
' + Sum(Cast(IsNull(' + #ColumnName + ',0) as bigint))'
END
Set #i = #i + 1
end
Set #SQL = #SQL + Char(13) +
'From ' + #DatabaseName + '..' + #TableName + ' (NOLOCK)'
-- print #SQL
exec sp_executeSql #SQL
Set #Checksum = (Select Top 1 MyChecksum From #CS)
Return isnull(#Checksum,0)
END
GO
It "ordinarily" can't be done as SQL Server treats functions as deterministic, which means that for a given set of inputs, it should always return the same outputs. A stored procedure or dynamic sql can be non-deterministic because it can change external state, such as a table, which is relied on.
Given that in SQL server functions are always deterministic, it would be a bad idea from a future maintenance perspective to attempt to circumvent this as it could cause fairly major confusion for anyone who has to support the code in future.
Here is the solution
Solution 1:
Return the dynamic string from Function then
Declare #SQLStr varchar(max)
DECLARE #tmptable table (<columns>)
set #SQLStr=dbo.function(<parameters>)
insert into #tmptable
Exec (#SQLStr)
select * from #tmptable
Solution 2:
call nested functions by passing parameters.
You can get around this by calling an extended stored procedure, with all the attendant hassle and security problems.
http://decipherinfosys.wordpress.com/2008/07/16/udf-limitations-in-sql-server/
http://decipherinfosys.wordpress.com/2007/02/27/using-getdate-in-a-udf/
Because functions have to play nicely with the query optimiser there are quite a few restrictions on them. This link refers to an article that discusses the limitations of UDF's in depth.
Thank you all for the replies.
Ron: FYI, Using that will throw an error.
I agree that not doing what I originally intended is the best solution, I decided to go a different route. My two choices were to use sum(cast(BINARY_CHECKSUM(*) as float)) or an output parameter in a stored procedure. After unit testing speed of each, I decided to go with sum(cast(BINARY_CHECKSUM(*) as float)) to get a comparable checksum value for each table's data.