quering remote database using sql server? - sql-server

I have used sp_addlinkedserver to access the remote machines db now i am writing queries explicitly on database like,
select * from [server\instance].database.owner.tablename
Now with this,
[Server\instance] : this has to be provided explicitly
[database] : can we find databases on specified instance using query like ms_ForEachDB ?
[owner] : Can we find the database owner name using query ?
If these values are found using queries do we need to use EXEC() to execute this or we can still achieve it using nice queries ?
Thanks all,

The "nice" format you mention is simply a 4 part object reference.
select * from [server\instance].database.owner.tablename
3 part
select * from database.owner.tablename
2 part
select * from owner.tablename
If you want to dynamically change any of the server, db or schema values then you have one option:
EXEC (#sqlstring)
However, if you only access stored procs remotely...
DECLARE #RemoteSP varchar(500)
SET #RemoteSP = '[server\instance].database2.schema.proc2'
EXEC #RemoteSP #p1, #p2, #p3 OUTPUT
SET #RemoteSP = '[server\instance].database1.schema.proc1'
EXEC #RemoteSP #p4, #p5, #p6 OUTPUT
However, changing the components of the object reference makes no sense arguably: if you know you're going to query a table then just call that table in that database...

you should make a query string and then run it by exec() function.
getting server name :
SELECT ##SERVERNAME
getting current db name :
SELECT DB_NAME() AS DataBaseName

You do not have to use EXEC you could use something like select * from openquery(MyLinkedServer,#sql) THough i prefer EXEC(#sql) AT MyLinkedServer
But all work

If it happens that you need to use some sort of variable in your arguments(e.g. collect remote's server updates since yesterday):
DECLARE #yesterday NVARCHAR(20) = '2016-09-23 08:16:20';
DECLARE #sql NVARCHAR(MAX) = N'SELECT * FROM database.targetTable AS origin
WHERE origin.columnWithDateTime >'''+#yesterday+''';';
PRINT #sql;
EXEC(#sql) AT linkedServer
______
Where:
database.targetTable : For some reason SSMS 2008 R2 returns error if you describe it as [database].[targetTable], and i don't know why that happens.
#yesterday: Is the variable you want to insert (this case, a string containing datetime-like element)
PRINT #sql: Just to verify if the quotes are correctly placed.
columnWithDateTime: Should be a column with datetime format (e.g. "timestamp", or similar to the #yesterday variable format.
"OPENQUERY does not accept variables for its arguments.": See Here (MSDN: OPENQUERY (Transact-SQL)).

Related

SQL Server 2014 sp_msforeachdb gives different results than running against individual database

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

Pass Linked Server name as parameter to Stored Procedure (not using Dynamic TSQL)

I need to pass linked server name as variable to stored procedure right now after testing and research they all suggest to using dynamic sql and open query which I am using now. however I am not comfortable using it(sql injection) plus I need to call other user defined function to the query. I am looking for a more secure and direct call. Here is my SP
ALTER PROCEDURE [dbo].[GetBackUpStatus]
-- Add the parameters for the stored procedure here
#linkedServerName AS VARCHAR(100),
#exemptDB as VARCHAR(100)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #Sql varchar(8000)
SET NOCOUNT ON;
set #Sql = 'select * from openquery (' + #linkedServerName + ' , ''SELECT SERVERPROPERTY(''''SERVERNAME'''') AS "SERVERNAME",
T1.NAME AS DatabaseName,
MAX(T2.backup_finish_date) AS last_db_backup_date,
CAST(COALESCE(DATEDIFF(second, MAX(T2.backup_finish_date) , CURRENT_TIMESTAMP)/ 3600.0, 9999.0) as NUMERIC(6,2)) AS [Hours Since Backup]
FROM master.sys.databases T1
LEFT OUTER JOIN msdb.dbo.backupset T2 ON T2.database_name = T1.NAME
WHERE T1.NAME NOT IN (''''tempdb'''')
GROUP BY T1.NAME
ORDER BY T1.NAME'')'
Exec (#Sql)
END
the purpose of this query is to get the server status and its database, I don't like this because of that confusing single quotes, this query will eventually grow as I develop and add function calls.
I tried this and something like this is what I wanted, since it is direct query and cleaner without those quotes. That's how I typically use linked server.
Select * from [' + #linkedservername + '].[schema].table
thanks
Solution for a large scale data model with hundreds of tables / objects.
Dynamic modification and cloning of a stored procedure for every linked-server.
It is kinda hiding any dynamic SQL under the hood.
How to
Create a stored procedure which interacts with an existing linked-server.
During a database deployment process:
Obtain the source code of the stored procedure.
Replace the name of the linked-server in the code.
If you want to create a new stored procedure (cloned), replace the name of the initial stored procedure in the code.
Create a cloned stored procedure or modify the current.
Repeat all steps for each required linked-server.
There are another variations for it.
Now, any external logic may decide which procedure to use.
You can check the existence of a linked-server or its related stored procedure.
For modifications and cloning, it is possible to use SQL Server or external tools, such as C#, CMD, etc.
For creation under SQL Server.

How to get database name automatically into script to use in stored procedure execution?

I need to TRIM databases as per requirement. So, I'm using below script and giving database names manually. All I need is to automate the script to get database names automatically. Can anyone please suggest how to get the database name automatically.
Use [Sales_backup_2015_05_31_230001_7137975]
Exec [spMaint_TrimTestDB] 1
Go
for Eg:
instead of giving manually Sales_backup_2015_05_31_230001_7137975 I need to get db name automatically
Thanks.
There is a function DB_NAME() that would return the name of the current database if no parameters are passed. Check this.
I guess dynamic SQL might help you to run SP in different databases:
DECLARE #sql nvarchar(max)
SELECT #sql = (
SELECT N'Use '+QUOTENAME([name]) +' Exec [spMaint_TrimTestDB] 1;'
FROM sys.databases
WHERE database_id >= 5 AND [name] like 'Sales_backup%'
FOR XML PATH('')
)
EXEC sp_executesql #sql
This script will create and execute dynamic statement like:
Use [sales_backup_2015] Exec [spMaint_TrimTestDB] 1;
Use [sales_backup_2016] Exec [spMaint_TrimTestDB] 1;
etc...

How to Select from a database using a dynamic variable

I have a SQL 2008 database that is stored on the same instance, but this database is created by the user and name is stored in SQL table. How do I write a select statement using dynamic sql or is there a another way
So for example:
Main database - myDB
User database - userDB (this is stored in a myDB.dbo.tblUserDatabase)
userDB has a table called tblUserReports
I want to write something like this in dynamic sql:
SELECT * FROM userDB.dbo.tblUserReports
So tried:
declare #dbUser varchar(50)
set #dbUser = (SELECT strDBName FROM myDB.dbo.tblUserDatabase)
SELECT * FROM #dbUser.dbo.tblUserReports
You can do this... dynamic sql can become unmanageable very quickly so be careful.
declare #dbUser varchar(50)
set #dbUser = (SELECT strDBName FROM myDB.dbo.tblUserDatabase)
DECLARE #sql NVARCHAR(1000)
SET #sql = 'SELECT * FROM ' + QUOTENAME(#dbUser) + '.dbo.tblUserReports'
EXEC sp_executesql #sql
You cannot parameterise the table name. You will have to use dynamic SQL in your client or stored procedures. It's a very unusual thing to want to do so think long & hard about if this is a good design. Maybe if you share what you are doing then you'll get some additional ideas as to how to approach your problem.

Access SQL Server temporary tables created in different scope

I am writing a stored procedure for SQL Server 2008 in which I need to extract information from a set of tables. I do not know ahead of time the structure of those tables. There is another table in the same database that tells me the names and types of the fields in this table.
I am doing this:
declare #sql nvarchar(max)
set #sql = 'select ... into #new_temporary_table ...'
exec sp_executesql #sql
Then I iterate doing:
set #sql = 'insert into #another_temporary_table ... select ... from #new_temporary_table'
exec sp_executesql #sql
After that I drop the temporary table. This happens in a loop, so the table with be created, populated and dropped many times, each time with different columns.
This fails with the error:
Invalid object name: #new_temporary_table.
After some googling I have found that:
The table #new_temporary_table is being created in the scope of the call to exec sp_executesql which is different from the one of my stored proc. This is the reason the next exec sp_executesql cannot find the table. This post explains it:
http://social.msdn.microsoft.com/forums/en-US/transactsql/thread/1dd6a408-4ac5-4193-9284-4fee8880d18a
I could use global temporary tables, which are prepended with ##. I can't do this because multiple stored procs could run at the same time and they would be affecting each other's state
In this article it says that if I find myself in this situation I should change the structure of the database. This is not an option for me:
http://www.sommarskog.se/dynamic_sql.html
One workaround I have found was combining all the select into #new_temporary_table.. and all the insert into ... scripts into one gigantic statement. This works fine but it has some downsides.
If I do print #sql to troubleshoot, the text gets truncated, for example.
Do I have any other option? All ideas are welcome.
You could use global temp tables, but use a context id (such as newid()) as part of the global temp table name.
declare #sql varchar(2000)
declare #contextid varchar(50) = convert(varchar(20), convert(bigint, substring(convert(binary(16), newid()), 1, 4)))
set #sql = 'select getdate() as stuff into ##new_temporary_table_' + #contextid
exec (#sql)
I think it's best to use one single script.
You can change how many characters will print in Tools > Options > Query Results > SQL Server > Results to Text - change "Maximum number of characters..." from 256 to the max (8192).
If it's bigger than 8192, then yes, printing is difficult. But you could try a different option in this case. Instead of PRINT #sql; instead use the following (with Results to Grid):
SELECT sql FROM (SELECT #sql) AS x(sql) FOR XML PATH;
Now you can click on the result, and it opens in a new query window. Well, it's an XML file window, and you can't execute it or see color-coding, and you have to ignore that it changes e.g. > to > to make it valid as XML data, but from here it's easy to eyeball if you're just trying to eyeball it. You can copy and paste it to a real query editor window and do a search and replace for the entitized characters if you like. FWIW I asked for them to make such XML windows real query windows, but this was denied:
http://connect.microsoft.com/SQLServer/feedback/details/425990/ssms-allow-same-semantics-for-xml-docs-as-query-windows
#temp tables (not global) are available in the scope they were created and below.
So you could do something like...
while (your_condition = 1) begin
set #sql = 'select ... into #temp1 ...from blah
exec sp_do_the_inserts'
exec(#sql)
end
The sp_do_the_inserts might look like...
select * into #temp2 from #temp1
....your special logic here....
This assumes you create sp_do_the_inserts beforehand, of course.
Don't know if that serves your need.

Resources