Execute function on a remote database - sql-server

I would like to get the result of a function executed on an UNKNOWN remote database.
Server name and DB name are provided only at runtime.
I have tried this, but it didn't work:
DECLARE #result table (result BIT)
INSERT INTO #result
EXEC (N'SELECT [linkedserver].[remotedb].dbo.myudf(''myparameter'')')
SELECT * FROM #result

Turn on RPC on your linked server -
EXEC [master].dbo.sp_serveroption #server=N'linked_server', #optname=N'rpc out', #optvalue=N'true'
And try this one -
EXEC ('SELECT *
FROM AdventureWorks2012.sys.fn_helpcollations()') AT [linked_server]
Or this (more preferable) -
EXEC [linked_server].AdventureWorks2012.sys.sp_executesql N'SELECT DB_NAME()'

Related

Query to Copy Table Data from local database to linked server database

I am trying to copy all rows of a table from the database of my local machine to linked server database.
This is the query I am using:
DECLARE #Qry nvarchar(MAX)
DECLARE #Server nvarchar(50)
SET #Server = '[LINKEDSERVER]'
SET #Qry = '
DECLARE #Qry2 nvarchar(max)
SET #Qry2 = N''
SET IDENTITY_INSERT RDB.dbo.Type ON
insert into RDB.dbo.Type (id, Name) Select ID,Name From
[LOCALSERVER].localdb.dbo.Type
SET IDENTITY_INSERT RDB.dbo.Type OFF''
EXEC ' + #Server + '.RDB.dbo.sp_executesql #Qry2'
EXEC SP_EXECUTESQL #Qry
But I am getting this error - please help what to do here to make it work:
Could not find server 'LOCALSERVER' in sys.servers. Verify that the correct server name was specified. If necessary, execute the stored procedure sp_addlinkedserver to add the server to sys.servers.
and If remove the [LOCALSERVER] then I get:
Invalid object name 'localdb'.
Please help if any other dynamic query can work to copy table data from local server to linked server.
You are trying to run the sql statement in remote linked server. In that remote linked server, there is no linkedserver(entry in sys.servers) defined for your local server. Hence, you are getting the error.
You can define linked server for your server in the remote machine and execute statement.
DECLARE #Qry nvarchar(MAX)
DECLARE #Server nvarchar(50)
SET #Server = '[LINKEDSERVER]'
SET #Qry = '
DECLARE #Qry2 nvarchar(max)
SET #Qry2 = N''
SET IDENTITY_INSERT RDB.dbo.Type ON
insert into RDB.dbo.Type (id, Name) Select ID,Name From
[LOCALSERVER].localdb.dbo.Type
SET IDENTITY_INSERT RDB.dbo.Type OFF''
EXEC ' + #Server + '.RDB.dbo.sp_executesql #Qry2'
EXEC SP_EXECUTESQL #Qry

sp_addrolemember with WHERE clause - SQL Server

Is there a way to add a WHERE clause within the sp_addrolemember script so that I don't have to create the stored procedure in every single database?
For example in my stored procedure:
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #GrantSql INT
EXEC #GrantSql = sp_addrolemember 'db_owner', #LoginName WHERE DatabaseName = 'DBName'
IF #GrantSQL = 0
BEGIN
INSERT INTO TableName....
END
END
In documentation:
Adds a database user, database role, Windows login, or Windows group
to a database role in the CURRENT database
But you can try:
EXEC #GrantSql = DBName..sp_addrolemember 'db_owner', #LoginName
If DBName is a parameter, you should use parametrized dynamic sql
DECLARE #Sql NVARCHAR(MAX) =
N'EXEC #GrantSql = ' + QUOTENAME(#DBName) + '..sp_addrolemember #RoleName, #LoginName'
EXEC sp_executesql #Sql, N'#RoleName NVARCHAR(MAX), #LoginName NVARCHAR(255)', #RoleName, #LoginName
P.S.
Here is a good article about dynamic sql:
http://www.sommarskog.se/dynamic_sql.html
I think everyone who are about to write dynamic sql MUST read it

Service Broker: How can I detect the script is executed on the wanted machine?

My SQL scripts are generated from templates. They contain the IP address. There can be a lot of them and the operator can by mistake execute the incorrect script. How the script can check if it was launched on the correct machine? (Otherwise, I would like to print the message and exit.)
Is it possible at all?
Thanks,
Petr
Updated Answer
Here's a script that I found from http://weblogs.sqlteam.com/peterl/archive/2008/07/16/How-to-get-IP-address.aspx, with some modifications to better suit your needs:
DECLARE #TargetIpAddress varchar(15);
SET #TargetIpAddress = '127.0.0.1'; --<== The IP address of the server you want.
DECLARE #Interfaces TABLE
(
RowID int IDENTITY(0, 1)
,Interface char(38)
,IP varchar(15)
);
INSERT #Interfaces ( Interface )
EXEC master..xp_regenumkeys N'HKEY_LOCAL_MACHINE',
N'System\CurrentControlSet\Services\TcpIP\Parameters\Interfaces';
DECLARE #RowID int
,#IP varchar(15)
,#Key nvarchar(200);
SELECT #RowID = MAX(RowID)
FROM #Interfaces;
WHILE #RowID >= 0
BEGIN
SELECT #Key = N'System\CurrentControlSet\Services\TcpIP\Parameters\Interfaces\' + Interface
FROM #Interfaces
WHERE RowID = #RowID;
EXEC master..xp_regread N'HKEY_LOCAL_MACHINE', #Key, N'DhcpIPAddress', #IP OUTPUT;
IF #IP <> '0.0.0.0'
UPDATE #Interfaces
SET IP = #IP
WHERE RowID = #RowID;
SET #RowID = #RowID - 1;
END;
IF NOT EXISTS (SELECT IP FROM #Interfaces WHERE IP = #TargetIpAddress)
BEGIN
DECLARE #ErrorMessage varchar(2000);
SET #ErrorMessage = 'This is not the correct server. This server does not have an IP address of %s.';
SET #TargetIpAddress = ISNULL(#TargetIpAddress, 'NULL');
RAISERROR(#ErrorMessage, 16, 1, #TargetIpAddress);
END
-- The rest of the script...
Original Answer
It seems like using the Server\Instance name instead of the IP address would be easier to work with, and less likely to break if the server(s) were assigned different IP addresses at some later date.
-- You can get the instance name like this:
SELECT ##SERVERNAME +'\'+ ##SERVICENAME AS 'Instance';
-- Although, you might prefer this instead:
SELECT CAST(SERVERPROPERTY('MachineName') AS nvarchar(128))
+COALESCE('\'+CAST(SERVERPROPERTY('InstanceName') AS nvarchar(128)), '');
-- NetBIOS name of the local computer on which the instance of SQL Server
-- is currently running.
-- If the instance of SQL Server is in a failover cluster and you want to obtain
-- the name of the failover clustered instance, use the MachineName property.
SELECT SERVERPROPERTY('ComputerNamePhysicalNetBIOS');
You can find detailed information about the SERVERPROPERTY function at MSDN: SERVERPROPERTY (Transact-SQL). Although, this function doesn't provide any way to obtain the IP address of the server/instance - there is no built-in function that provides this information.
Create Procedure P_GetIPAddresses (#IPS varchar(4000) out)
as
begin
Select #IPS=''
Create TABLE #temp (Line varchar(200))
Insert #temp exec master..xp_cmdshell 'ipconfig'
Select #IPS = #IPS + Coalesce(RTRIM(Replace(SubString(Line,1,CharIndex(':',line)-1) ,'.','')) + SubString(Line,CharIndex(':',line) ,200),'')
from #temp
where upper (Line) like '%ADRESS%'
--SELECT * from #tmp --DEBUG
DROP TABLE #temp
end
Usage if xp_cmdshell is disabled, parsing Adresses is up to you ...
EXEC sp_configure 'show advanced options', 1
GO
-- To update the currently configured value for advanced options.
RECONFIGURE
GO
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
GO
-- To update the currently configured value for this feature.
RECONFIGURE
GO
Declare #IPS Varchar(4000)
exec P_GetIPAddresses #IPS out
Select #IPS
EXEC sp_configure 'show advanced options', 1
GO
-- To update the currently configured value for advanced options.
RECONFIGURE
GO
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 0
GO
-- To update the currently configured value for this feature.
RECONFIGURE
GO
I could provide a xp_GetIP.dll, yet only tested with SQL-Server 2005 32-bit
which you could register via
EXEC sp_addextendedproc xp_GetIP, 'C:\temp\xpGetIP.dll'
Call would be
Declare #IP varchar(100)
exec xp_GetIP #IP output
print #IP
output only : 192.168.69.69
EDIT:
Working to on SQL-Server 2008 R2 64-bit als 64bit DLL
Sorry for reposting, but annoyed of allways missing oportunities of accessing systemadata inside SQL-Server I decided to write a Extended Stored Procedure as Bridge to WMI.
Download http://bummisoft.de/download/XP_WMI.zip
the call would be:
exec xp_wmiv3 'Select * from Win32_NetworkAdapterConfiguration where IPEnabled=TRUE'
the DLL is available for 32bit and 64bit SQLServers and free for non comercial use.
Installation:
EXEC sp_addextendedproc xp_wmiv3, '<Your Path>\XP_WMIV3_DLL.dll'
eg: EXEC sp_addextendedproc xp_wmiv3, 'C:\DLLs\XP_WMIV3_DLL.dll'
Uninstallation
EXEC sp_dropextendedproc xp_wmiv3
Usage
e.g.
exec xp_wmiv3 'SELECT * FROM Win32_Volume'
exec xp_wmiv3 'SELECT * FROM CIM_Userdevice where Name like "%HID%"'
create table #tmp(
Domain varchar(255),
Name varchar(255),Sid varchar(255)
)
insert into #tmp
exec xp_wmiV2 'SELECT Domain, SID, Name FROM Win32_UserAccount where Status = "OK"'
select * from #tmp
drop table #tmp
Currently tested under SQL-Server 2005 and SQL-Server 2008.

Use database inside a stored procedure

I need to make a stored procedure which creates a user in more than one database. Something like this:
USE [database1]
CREATE USER [userLogin] FOR LOGIN [userLogin]
USE [database2]
CREATE USER [userLogin] FOR LOGIN [userLogin]
Since the CREATE USER statement does his job in the current database I need to use the USE statement to change between databases, but it can't be used inside stored procedures.
How can I do this?
Dynamic SQL
CREATE PROCEDURE spTestProc
AS
EXEC ('USE [database1]; CREATE USER [userLogin] FOR LOGIN [userLogin]')
EXEC ('USE [database2]; CREATE USER [userLogin] FOR LOGIN [userLogin]')
GO
SQL Server gives us a system stored procedure to do this. My understanding is that the recommended method would be to use sys.sp_grantdbaccess:
CREATE PROCEDURE usp_CreateTwoUSers
AS
BEGIN
-- Create a user for a login in the current DB:
Exec sp_grantdbaccess [userLogin], [name_in_db];
-- Create a user for a login in an external DB:
Exec ExternalDatabaseName.sys.sp_grantdbaccess [userLogin], [name_in_db];
END
I did it like below:
Alter Procedure testProc
#dbName varchar(50)
As
declare #var varchar(100)
set #var = 'Exec(''create table tableName(name varchar(50))'')'
Exec('Use '+ #dbName + ';' + #var)
Exec testProc 'test_db'
CREATE PROCEDURE spTestProc
AS
BEGIN
EXECUTE sp_executesql N'USE DB1 SELECT * FROM TABLE1'
EXECUTE sp_executesql N'USE DB2 SELECT * FROM Table2'
END
exec spTestProc
now it is worked.
If you're writing dynamic SQL with EXEC sp_executesql ('query1') or EXEC ('query2') this will return correct db which you want. If you're writing static SQL or your query outside of dynamic SQL quotes or parantheses it will work on master (where you create stored procedure(default is master)).
CREATE PROCEDURE master.dbo.mysp1
AS
EXEC ('USE model; SELECT DB_NAME()') -- or sp_executesql N'USE model; SELECT DB_NAME()'
--this returns 'model'
GO
CREATE PROCEDURE master.dbo.mysp2
AS
EXEC ('USE model;') -- or sp_executesql N'USE model;'
SELECT DB_NAME()
-- this returns 'master'
GO
It should be noted that if you want to use single quotes within a EXEC command, you will need to double the amount of single quotes
e.g.
EXEC ('USE [database1]; select * from Authors where name = ''John'' ')
In this example, John has 2 single quotes before and after it.
You cannot use double quotes for this type of query.
Using sp_executesql seems to work, for more info see http://msdn.microsoft.com/en-us/library/ms175170.aspx
I tested it using this and it worked fine:
CREATE PROCEDURE spTestProc
AS
BEGIN
EXECUTE sp_executesql N'USE DB1;'
SELECT * FROM TABLE1
EXECUTE sp_executesql N'USE DB2;'
SELECT * FROM Table2
END
exec spTestProc

How to pass parameter values to a T-SQL query

I am using the following T-SQL query in SQL server 2005 (Management Studio IDE):
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = #id';
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
I am not sure how to pass the input parameter #id to the EXEC query, and pass the count result out to #countVal. I saw some examples for Microsoft SQL server like:
EXEC (#sql, #id = #id)
I tried this for Oracle but I got a statement error:
OLE DB provider "OraOLEDB.Oracle" for linked server "oracleServer"
returned message "ORA-00936: missing expression"
Try this:
EXEC sp_executesql #sql, N'#id int', #id
More info at this great article: http://www.sommarskog.se/dynamic_sql.html
As for the output, your SELECT needs to look something like this:
SELECT #countVal = COUNT(id) FROM owner.myTable WHERE id = #id
I'm selecting 'id' instead of '*' to avoid pulling unnecessary data...
Then your dynamic sql should be something like this:
EXEC sp_executesql #sql,
N'#id int, #countVal int OUTPUT',
#id,
#countVal OUTPUT
This example is adapted from the same article linked above, in the section sp_executesql.
As for your Oracle error, you will need to find out the exact SQL that sp_executesql is sending to Oracle. If there is a profiler or query log in Oracle, that may help. I have limited experience with Oracle, but that would be the next logical step for troubleshooting your problem.
The quick and dirty way is to just build the string before using the EXEC statement, however this is not the recommended practice as you may open yourself up to SQL Injection.
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
The correct way to do this is to use the system stored procedure sp_executesql as detailed by magnifico, and recommended by Microsoft in Books Online is:
EXEC sp_executesql #sql, N'#id int', #id
I don't know why you are pass id separately.
You could do the following
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
Edit: Don't do the above. Use parameterized sql statements.

Resources