Issue with Bracketed Identifier inside SQL Server OPENQUERY - sql

Not sure, if this should go on Stack Overflow, or the DBA section.
Having an issue with a procedure I'm writing. I have a variable, lets say:
SET #name sysname --also tried to make as varchar
This is used inside a cursor, and will basically contain SQL Server names. Several of the server names are followed by instances names. For example
DECLARE #name = 'SERVER1\INSTANCE1'
Inside the cursor, I have this query.
SELECT #name, * FROM OPENQUERY(#name,
'SELECT
i.Name,
i.database_id,
b.mirroring_state
FROM msdb.sys.databases i
INNER JOIN msdb.sys.database_mirroring b
ON i.database_id = b.database_id
WHERE b.mirroring_state IS NOT NULL')
which doesn't work because of the \ inside the #name
However, if I try this, it works perfectly.
SELECT 'SERVER1\INSTANCE1', * FROM OPENQUERY([SERVER1\INSTANCE1],
The issue I'm having is trying to use the bracketed identifier with the #name inside the OPENQUERY.
I have tried several things, including various combinations of OPENQUERY('['+#name+']',
If you just try FROM OPENQUERY([#name], SQL Server parses it literally as #name.
Any ideas on how to use the servername\instance name without having these issues?
Edit, full section of the code:
DECLARE #name sysname,
#sql nvarchar(4000)
DECLARE c1 CURSOR FOR
SELECT SUBSTRING (Servername, 2, LEN(Servername)-2)
FROM AllServers
OPEN c1
FETCH NEXT FROM c1
INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
set #sql = 'INSERT INTO MirrorResults
SELECT ''[' + #name + ']'', * FROM OPENQUERY([' + #name + '],
''
SELECT
i.Name,
i.database_id,
b.mirroring_state
from msdb.sys.databases i
INNER JOIN msdb.sys.database_mirroring b
ON i.database_id = b.database_id
WHERE b.mirroring_state IS NOT NULL
'')'
EXECUTE sys.sp_executesql #sql;
FETCH NEXT FROM c1
END
CLOSE c1
DEALLOCATE c1

OPENQUERY can only be used with a valid linked server reference. As per MSDN:
OPENQUERY ( linked_server ,'query' )
linked_server
Is an identifier representing the name of the linked server.
So you can't use servername\instance as the first parameter. You'll have to create a linked server first, and then use the linked server name as the first parameter.
An example using dynamic SQL:
DECLARE #linked_server VARCHAR(20), #sql NVARCHAR(512);
SET #linked_server = 'SERVER1\INSTANCE1';
SET #sql = 'SELECT * FROM OPENQUERY([' + #linked_server + '], ''SELECT * FROM SomeTable;'');';
EXEC sys.sp_executesql #sql;

Related

SQL - Search for table name across all databases on server

I thought this would be pretty straightforward, but I have about 80 databases in the server I am looking at, each database has 5-500 tables.
I am wondering how i can search for a TABLE NAME across everything. I tried a basic
SELECT
*
FROM sys.tables
but I only get 6 results.
This is a bit of a hack, but I think it should work:
sp_msforeachdb 'select ''?'' from ?.information_schema.tables where table_name=''YourTableName''';
It will output the names of the DBs that contain a table with the given name.
Here's a version using print that is a little better IMHO:
sp_msforeachdb '
if exists(select * from ?.information_schema.tables where table_name=''YourTableName'')
print ''?'' ';
The above queries are using ms_foreachdb, a stored procedure that runs a given query on all databases present on the current server.
This version uses FOR XML PATH('') instead of string concatenation, eliminates the default system databases, handles databases with non-standard names and supports a search pattern.
DECLARE #pattern NVARCHAR(128) = '%yourpattern%';
DECLARE #sql NVARCHAR(max) = STUFF((
SELECT 'union all select DatabaseName = name from ' + QUOTENAME(d.name) + '.sys.tables where name like ''' + #pattern + ''' '
FROM sys.databases d
WHERE d.database_id > 4
FOR XML path('')
), 1, 10, '');
EXEC sp_executesql #sql;
You might need to write:
select DatabaseName = name collate Latin1_General_CI_AS
I know I did.
Just because I really dislike loops I wanted to post an alternative to answers already posted that are using cursors.
This leverages dynamic sql and the sys.databases table.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select DatabaseName = name from [' + name + '].sys.tables where name = ''YourTableName'' union all '
from sys.databases
set #SQL = stuff(#SQL, len(#SQL) - 9, 11, '') --removes the last UNION ALL
exec sp_executesql #SQL
Here's a bit of a simpler option using dynamic sql. This will get you the name of all tables in every database in your environment:
declare #table table (idx int identity, name varchar(max))
insert #table
select name from master.sys.databases
declare #dbname varchar(max)
declare #iterator int=1
while #iterator<=(select max(idx) from #table) begin
select #dbname=name from #table where idx=#iterator
exec('use ['+#dbname+'] select name from sys.tables')
set #iterator=#iterator+1
end
select * from #table
Dim sql As String = ("Select * from " & ComboboxDatabaseName.Text & ".sys.tables")
use this key

Dynamic query inside if exists is not working

I am writing Dynamic SQL inside If exists. I have a query on sys.columns to check that department Id column exists or not. If it exists I have to check row count and go inside if statement. I am not able to get mistake in my query. Someone please point out.
DECLARE #TableName VARCHAR(100) = '[Student]'
DECLARE #ColName VARCHAR(10) = 'DeptId'
DECLARE #Query NVARCHAR(1000) = '
SELECT *
FROM sys.columns
WHERE Name =' + #ColName + 'AND Object_ID = Object_ID(' + #TableName + ')'
DECLARE #rowcnt INT
EXEC sp_executesql #query
SELECT #rowcnt = ##ROWCOUNT
IF (#rowcnt > 0)
BEGIN
PRINT 'row present'
END
ERROR - Incorrect syntax near 'Object_ID'.
And it always returns row count as 1.
I'm going to tell what's wrong, but I'll let you fix the query because there are a lot of options. The query returns an error. I'm not sure why ##ROWCOUNT is returned as 1, but you are missing single quotes for the Name. So, in all likelihood, you are going to get an error that DEPTID is not a valid column.
You can naively fix this by putting in the single quotes. But, you should be using that arguments to sp_execute_sql to pass arguments in.
And, I'll add that this is probably unnecessary, because normally one would do:
if (exists (select 1
from information_schema.columns
where table_name = #table_name and column_name = #column_name
)
)
begin
. . .
end;
Of course, this doesn't do the object resolution from the name, but that is rarely needed, especially if you are not putting strange characters in table and column names.
Fortunately dynamic SQL isn't needed in this case. You can use a parameterized query.
SELECT #rowcnt = COUNT(*)
FROM sys.columns
WHERE [name] = #ColName
AND [object_id] = OBJECT_ID(#TableName)
DECLARE #TableName SYSNAME = 'dbo.test',
#ColName SYSNAME = 'col1'
IF COL_LENGTH(#TableName, #ColName) IS NOT NULL
PRINT 'column present'

Check if View exists before querying it

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

Script -> Query for multiple databases

I have the following problem: I want to execute a query on multiple databases on my SQL Server. Every customer has a separate database. Those all have exactly the same table and their names are similar. So there is a database kde_01_Miller, then a kde_02_Mueller and so on ...
I want to execute a query in every one of those databases.
Here's what I have tried:
DECLARE #name VARCHAR(100) -- database name
DECLARE #dothis nvarchar(200)
DECLARE db_cursor CURSOR FOR
SELECT name
FROM master.dbo.sysdatabases
WHERE name like 'kde_0%'
order by name
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
set #dothis = 'use [' + #name + ']'
exec sp_executesql #dothis
/* Start query */
select description from dbo.basicdata
/* End query */
FETCH NEXT FROM db_cursor INTO #name
END
CLOSE db_cursor
DEALLOCATE db_cursor
The problem is that the query does not work properly. The use statement seems not to be working. I get a result for every database I have, but the result is always the same one, dependent on the database I'm currently doing a query for.
I've also tried the following and it worked: Instead of my while-loop I did this:
WHILE ##FETCH_STATUS = 0
BEGIN
set #dothis= 'select description from ' + QUOTENAME(#name) + '.dbo.basicdata'
exec sp_executesql #dothis
FETCH NEXT FROM db_cursor INTO #name
END
But I don't like this way, because you need the quotename(#name) for every table.
How do I make the first example work?
That's not possible, since sp_executesql is executed as its own self-contained batch, that mean you did actually "use" other databases, but only in those batchs i mentioned earlier
I'll try to be more clear, this code of you is a batch, since there's no "GO" command inside (read my sql comments) :
DECLARE #name VARCHAR(100) -- database name
DECLARE #dothis nvarchar(200)
DECLARE db_cursor CURSOR FOR
SELECT name
FROM master.dbo.sysdatabases
WHERE name like 'kde_0%'
order by name
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
set #dothis = 'use [' + #name + ']'
-- this will create another batch and execute the #dothis
-- it'll have nothing todo with your current executing batch,
-- which is calling the sp_executesql
exec sp_executesql #dothis
/* Start query */
select description from dbo.basicdata
/* End query */
FETCH NEXT FROM db_cursor INTO #name
END
CLOSE db_cursor
DEALLOCATE db_cursor
So, there's only one way left, write whatever you want to do with the database inside the #dothis :
declare #dothis nvarchar(max)
set #dothis = '
use [' + #name + ']
-- query start
Select description from dbo.basicdata
-- query end
'
exec sp_executesql #dothis
While this question has already been answered, I thought I'd provide a second answer I believe is better. Instead of using a Cursor you can generate dynamic SQL to query multiple databases.
DECLARE #sql NVARCHAR(Max);
SELECT #sql = COALESCE(#sql, '') + 'SELECT * FROM ' + [name] + '.sys.tables' + CHAR(13)
FROM sys.databases
PRINT #sql
EXEC sp_executesql #sql
The above SQL will Generate the following SQL.
SELECT * FROM master.sys.tables
SELECT * FROM tempdb.sys.tables
SELECT * FROM model.sys.tables
SELECT * FROM msdb.sys.tables
SELECT * FROM StackOverflow.sys.tables
SELECT * FROM AdventureWorks2012.sys.tables
SELECT * FROM AdventureWorksDW2012.sys.tables
As you see I was able to run a query against multiple databases. I could even UNION the data together if I'd like.
though already answered in finding a solution for the same issue I wrote this query.
it provides the data and it shows the source of the database which provides the answer.
note the order by is only to show double values but slows down the result
declare #results table (
name varchar(50),
clientnr numeric(20) ,
dbname_source varchar(20)
);
insert #results
exec sp_msforeachdb N'
use [?]
if left(''?'',3) = ''ons'' -- only execute the query against databases that match the naming pattern in this example starting with ons (use your own database names make sure to exclude systemdatabases
--and ''?'' <> ''MIS_MTA''
begin
--select script that to be executed over multiple databases
select distinct
c.name,
c.identificationNo as numeric,
d.table_catalog
from [?].dbo.clients as c , [?].INFORMATION_SCHEMA.COLUMNS as d
where 1=1
and isnumeric(c.identificationNo) = 1
end;
';
select * from #results
where
isnumeric(clientnr) = 1
order by 2
;
example result:
name clientnr dbname_source
TestclientA 9000 OnsDB
TestclientA 9000 OnsDB_Fixed
Storcken 9999 OnsDB_Fixed
Storcken 9999 OnsDB

How to query against multiple linked servers?

After linking some SQL Server 2008 Servers / instances, I would like to do a more generic query against these servers. I know that I must specify the destiny of the query like that:
select *
from [SRV\INSTANCE].dbname.dbo.foo
But, I would run this query against more than one linked server. I know also that this select statement returns exactly the SRV\INSTANCE that I need:
select ss.name
from sys.servers ss
where ss.server_id > 0
This one, returns all servers\instances from where I want query against to.
At this scenario, all databases have the same structure, so I wanted to do something like this:
select *
from [select ss.name from sys.servers ss where ss.server_id > 0].DBNAME.dbo.foo
Any ideas?
Thanks in advance.
You can dynamically create SQL statement on the fly and then run that command. In this scenario in #dml variable with help += operator the whole command dynamically is created
DECLARE #dml nvarchar(max) = N''
SELECT #dml += 'UNION ALL SELECT * FROM ' + QUOTENAME(ss.name) +
'.[DBNAME].[dbo].foo '
FROM sys.servers ss
WHERE ss.server_id > 0
SELECT #dml = STUFF(#dml, 1, 10, '')
EXEC sp_executesql #dml
That requires a dynamic query, like:
declare #servers table (name sysname)
insert #servers
(name)
select  name
from    sys.servers
where   server_id > 0
declare #query nvarchar(max) = ''
while 1=1
begin
declare #server sysname
select top 1 #server = name
from #servers
if ##rowcount = 0
break
if #query <> ''
#query = #query + ' union all ' + char(13) + char(10)
set #query = #query +
' select * from ' + quotename(#server) + '.dbname.dbo.foo ' +
char(13) + char(10)
delete #server
where name = #server
end
print #query -- For debugging
exec (#query)
I have done some work where I have had to join results from two linked servers. One of the linked servers is to a redbrick database and to make a long story short, I had to use openquery.
The approach that I used was to create temporary tables in ms sql. Then I populated them with the results from the openqueries to the linked servers and used normal tsql to put it all together.