How to query against multiple linked servers? - sql

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.

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

Issue with Bracketed Identifier inside SQL Server OPENQUERY

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;

How to form a table name from concatenating strings in Select Statement in SQL 2012

I want to achieve this -
SELECT * FROM A1234
I have the the ID 1234 saved in another table called Aliases which has two columns Alias,ID with one record like this.
Alias = TestTable, ID = 1234
So I am trying something like this
SELECT * FROM ('A'+ (SELECT ID FROM Aliases WHERE Alias = 'TestTable'))
Any help would be appreciated
You should use dynamic sql.
DECLARE #Q VARCHAR(MAX),#ID INT
SET #ID=(Select DISTINCT ID from Aliases where Alias = 'TestTable') -- CHECK TO RETURN JUST ON RESULT
SET #Q='SELECT * FROM A'+CAST(#ID AS VARCHAR(10))
EXEC(#Q)
You need dynamic SQL for this.
DECLARE #sql NVARCHAR(MAX);
SELECT TOP (1) #sql = N'SELECT * FROM A' + RTRIM(ID) + ';'
FROM dbo.Aliases WHERE Alias = 'TestTable';
EXEC sp_executesql #sql;
To build a set of statements that selects all of them, you can say:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'
SELECT *, ''A' + RTRIM(ID) + ''' FROM A' + RTRIM(ID) + ';'
FROM dbo.Aliases;
PRINT #sql;
-- EXEC sp_executesql #sql;

How to query a table with wildcard in the name

I have a bunch of tables, that have the same first few characters in the names, but the tables have random numbers (equal in length) at the end of the names.
They have the same structure.
I want to union them into one table, dynamically.
This is in SQL Server 2008 Express.
I have no real idea how to do this, but I'm guessing I have to loop thru a list of the tables names, maybe using a list in the system tables?
Example (that illustrates my simple minded thinking, as I'm sure this make no real technical sense)
SELECT * FROM TABLE0*
UNION ALL
SELECT * FROM TABLE0*
Note '*' is a a number with 8 digits.
A quick dynamic SQL script should do it:
declare #sql varchar(max)
set #sql = ''
select #sql = #sql + case len(#sql) when 0 then '' else ' UNION ALL ' end + '
SELECT * FROM [' + table_name + ']'
from
information_schema.tables where table_name like 'TABLE0%'
exec (#sql)
You could use a simple query like this to construct your large query:
SELECT 'SELECT * FROM '+name+ ' UNION '
FROM sys.tables
WHERE name LIKE '%yourtable%'
Or you could use dynamic SQL to build it and run it:
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql +'
UNION ALL
SELECT * FROM ['+name+']'
FROM sys.tables
WHERE name LIKE '%yourtable%'
SET #sql = STUFF(#sql,1,15,'')
EXEC(#sql)

how do I select records that are like some string for any column in a table?

I know that I can search for a term in one column in a table in t-sql by using like %termToFind%. And I know I can get all columns in a table with this:
SELECT *
FROM MyDataBaseName.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'MyTableName`
How can I perform a like comprparison on each of the columns of a table? I have a very large table so I can't just spell out LIKE for each column.
As always, I'll suggest xml for this (I'd suggest JSON if SQL Server had native support for it :) ). You can try to use this query, though it could perform not so well on large number of rows:
;with cte as (
select
*,
(select t.* for xml raw('data'), type) as data
from test as t
)
select *
from cte
where data.exist('data/#*[local-name() != "id" and contains(., sql:variable("#search"))]') = 1
see sql fiddle demo for more detailed example.
Important note by Alexander Fedorenko in comments: it should be understood that contains function is case-sensitive and uses xQuery default Unicode code point collation for the string comparison.
More general way would be to use dynamic SQL solution:
declare #search nvarchar(max)
declare #stmt nvarchar(max)
select #stmt = isnull(#stmt + ' or ', '') + quotename(name) + ' like #search'
from sys.columns as c
where c.[object_id] = object_id('dbo.test')
--
-- also possible
--
-- select #stmt = isnull(#stmt + ' or ', '') + quotename(column_name) + ' like #search'
-- from INFORMATION_SCHEMA.COLUMNS
-- where TABLE_NAME = 'test'
select #stmt = 'select * from test where ' + #stmt
exec sp_executesql
#stmt = #stmt,
#params = N'#search nvarchar(max)',
#search = #search
sql fiddle demo
I'd use dynamic SQL here.
Full credit - this answer was initially posted by another user, and deleted. I think it's a good answer so I'm re-adding it.
DECLARE #sql NVARCHAR(MAX);
DECLARE #table NVARCHAR(50);
DECLARE #term NVARCHAR(50);
SET #term = '%term to find%';
SET #table = 'TableName';
SET #sql = 'SELECT * FROM ' + #table + ' WHERE '
SELECT #sql = #sql + COALESCE('CAST('+ column_name
+ ' as NVARCHAR(MAX)) like N''' + #term + ''' OR ', '')
FROM INFORMATION_SCHEMA.COLUMNS WHERE [TABLE_NAME] = #table
SET #sql = #sql + ' 1 = 0'
SELECT #sql
EXEC sp_executesql #sql
The XML answer is cleaner (I prefer dynamic SQL only when necessary) but the benefit of this is that it will utilize any index you have on your table, and there is no overhead in constructing the XML CTE for querying.
In case someone is looking for PostgreSQL solution:
SELECT * FROM table_name WHERE position('your_value' IN (table_name.*)::text)>0
will select all records that have 'your_value' in any column. Didn't try this with any other database.
Unfortunately this works as combining all columns to a text string and then searches for a value in that string, so I don't know a way to make it match "whole cell" only. It will always match if any part of any cell matches 'your_value'.