Run Stored Procedure From Another Database in Current Database - sql-server-2008

I have one shared database and multiple client databases. The data is stored in the client database. We want to create a master set of stored procedures in the shared database and execute them from the client database. Given the following:
use shared;
go
create procedure GetInvoices as
print db_name() ' <- current database'
select * from invoices
go
use client1;
create table invoices(...columns...)
exec shared.dbo.GetInvoices
This returns the following error:
shared <- current database
Msg 208, Level 16, State 1, Procedure GetInvoices, Line 3
Invalid object name 'invoices'.
Without using dynamic SQL, how can I run the stored procedure in shared from client1 so that it executes in client1 and thus has access to all of the tables in client1?

You can run a stored procedure defined in master database in context of client1 database and see all client1 database tables, without dynamic SQL, but it uses undocumented stored procedure sp_ms_marksystemobject.
Your stored procedure name must start with sp_, for example sp_GetInvoices. Create it in master database, then call exec sp_ms_marksystemobject sp_GetInvoices to make it see the tables of the current database.
USE master
GO
CREATE OR ALTER PROCEDURE sp_GetInvoices
AS
BEGIN
SELECT ClientName from Invoice
END
GO
exec sp_ms_marksystemobject sp_GetInvoices
USE client1
GO
create table Invoice (ClientName varchar(100))
insert Invoice select 'Acme Client'
exec sp_GetInvoices
Result (running on SQL Server version 13.0.5081.1):
ClientName
------------
Acme Client

Try this on your "Master" database:
CREATE PROCEDURE [dbo].[GetDataFromClient]
#DB VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #STMT VARCHAR( 300 );
DECLARE #SP VARCHAR( 500 );
SET #SP = 'dbo.GetData';
SET #STMT = 'EXEC(''' + #SP + ''')';
EXEC('USE '+ #db + ';' + #STMT)
END
Now on the "Client" database:
CREATE TABLE [dbo].[TestClient](
[ID] [int] NOT NULL,
[Description] [varchar](10) NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[ID] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY].
Create the stored procedure to retrieve data from table TestClient
CREATE PROCEDURE [dbo].[GetData]
AS
BEGIN
SELECT *
FROM TestClient;
END
Now you can retrieve the columns from the TestClient Database using:
USE [TestMaster]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[GetDataFromClient]
#DB = N'TESTCLIENT'
SELECT 'Return Value' = #return_value
GO

You can call stored procedure using four part name after creating link server.
Or it can be called by openquery option.
LinkSerevr:
EXEC [ServerName] .dbname.scheme.StoredProcedureName
openquery : SELECT * FROM
OPENQUERY( [ServerName] .dbname.scheme.StoredProcedureName)

Related

How to capture JSON data from a stored procedure for testing with tSQLt

I'm trying to use tSQLt to test a stored procedure that returns JSON data. The database is running under SQL Server 2016. The stored procedure is as follows (simplified considerably):
CREATE PROCEDURE [SearchForThings]
#SearchText NVARCHAR(1000),
#MaximumRowsToReturn INT
AS
BEGIN
SELECT TOP(#MaximumRowsToReturn)
[Id],
[ItemName]
FROM
[BigTableOfThings] AS bt
WHERE
bt.[Tags] LIKE N'%' + #SearchText + N'%'
ORDER BY
bt.[ItemName]
FOR JSON AUTO, ROOT(N'Things');
END;
This can't be tested in the same way XML can - I've tried a test table, as below, which was suggested in this related answer here -
CREATE TABLE #JsonResult (JsonData NVARCHAR(MAX))
INSERT #JsonResult (JsonData)
EXEC [SearchForThings] 'cats',10
The above code produces this error:
The FOR JSON clause is not allowed in a INSERT statement.
I cannot alter the stored procedure under test. How can I capture the JSON result?
Without being able to modify the stored proc, your last ditch effort would be to use OPENROWSET. Here's how you would call it in your case:
INSERT INTO #JsonResult
SELECT *
FROM OPENROWSET('SQLNCLI', 'Server=[ServerNameGoesHere];Trusted_Connection=yes;','EXEC SearchForThings ''cats'',10')
If you get an error, you can use the following to enable ad hoc distributed queries:
sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO
I know this is two years on but I stumbled on this today when trying to solve a different tSQLt problem.
Your issue occurs because the column being returned from your stored procedure is not explicity named. If you provide a column name for the JSON data you can insert the data into a #temp table, e.g.:
create table BigTableOfThings (
Id int not null,
ItemName nvarchar(50) not null,
Tags nvarchar(50) not null
);
insert BigTableOfThings values
(1, 'Whiskers', 'Cool for Cats'),
(2, 'Barkley', 'Dogs Rule!');
GO
create procedure SearchForThings
#SearchText nvarchar(1000),
#MaximumRowsToReturn int
as
begin
select [JsonData] = (
select top(#MaximumRowsToReturn)
Id,
ItemName
from
BigTableOfThings as bt
where
bt.Tags like N'%' + #SearchText + N'%'
order by
bt.ItemName
for json auto, root(N'Things')
);
end
go
create table #JsonResult (JsonData nvarchar(max));
insert #JsonResult (JsonData)
exec SearchForThings 'cats',10;
select * from #JsonResult;
go
Which yields...
{"Things":[{"Id":1,"ItemName":"Whiskers"}]}

What's wrong with the stored procedure

I have a table called Std_Components which acts like an index for list of components with associated tables. The column AssociatedTable holds the name of table that actually contains the component data.
Please check images below -
Here is table data for Std_SteeringPumps
I am trying to create a stored procedure that will copy Std_Components table as well as all associated tables with new name. For ex. Lets say if i provided 001 as a parameter to this stored procedure i should be able create new tables like C001_Components, C001_SteeringPumps and so on.
This is what I have done so far:
ALTER PROCEDURE [dbo].[sgi_sp_CreateTablesForNewCompany]
-- Add the parameters for the stored procedure here
#CompanyId varchar(5)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- declare variables
declare #qry as varchar(2000)
declare #compTblName as varchar(100)
set #compTblName = 'C'+#companyId +'_Components'
-- Check if table already exists
IF object_id(#compTblName) is not null
return
-- Create main component index table by copying standard component table --
set #qry = 'Select * into '+#compTblName+' From Std_Components;';
--print #qry
--execute (#qry)
set #qry =#qry + 'Update C'+#companyId +'_Components Set AssociatedTable=''C'+#companyId +'''+substring(AssociatedTable,4,200);';
--print #qry
--exec #qry
-- Create all child tables --
Select * Into #TempTbl From dbo.Std_Components
Declare #Id int
While (Select Count(*) From #TempTbl) > 0
Begin
declare #rowTableName as varchar(50)
declare #compNewTbl as varchar(50)
Select Top 1 #rowTableName=AssociatedTable, #Id = Id From #TempTbl
set #compNewTbl = 'C'+#companyId + substring(#rowTableName,4,200);
set #qry = #qry + 'Select * into '+#compNewTbl+' From ' + #rowTableName + ';'
--print #qry
--exec #qry
Delete #TempTbl Where Id = #Id
End
print #qry
exec #qry
END
Here is the output of the print statement for the query it generates -
Select * into C001_Components From Std_Components;
Update C001_Components Set AssociatedTable='C001'+substring(AssociatedTable,4,200);
Select * into C001_SteeringPumps From Std_SteeringPumps;
But when the stored procedure is executed, I get the following error -
Msg 203, Level 16, State 2, Procedure sgi_sp_CreateTablesForNewCompany, Line 56
The name 'Select * into C001_Components From Std_Components;Update C001_Components Set AssociatedTable='C001'+substring(AssociatedTable,4,200);Select * into C001_SteeringPumps From Std_SteeringPumps;' is not a valid identifier.
Can anybody help me out resolve this issue.
Thanks for sharing your time and wisdom.
The error you're getting is because the EXEC statement (the last line of the stored procedure) needs to have brackets around the #qry variable so that it becomes
exec(#qry)
Without the brackets it's treating the entire SQL string as stored procedure name.
The non valid indentifier is around the AssociatedTable part
Set AssociatedTable='C001'+substring(AssociatedTable,4,200); will not run as there is no scope for AssociatedTable to substring - the string needs to contain the name of the table completely to be able to be executed
Instead of
exec #qry;
You need
exec sp_executesql #qry;
You'll also need to change the type of #qry to NVARCHAR. Note that because of the dynamic sql, the proc is prone to SQL Injection and other escaping issues (i.e. ensure that #CompanyId is validated)

SQL Server 2008 modify system stored procedure

I have inherited maintenance of a SQL Server (2008), and I want to modify some of the system stored procedures. These are user-defined system stored procedures (for example: sys.sp_customproc). I can only assume they were created as system procedures so they could be shared across multiple databases? But regardless, I need to modify them.
Here is an example of one of them.
USE [msdb]
GO
/****** Object: StoredProcedure [sys].[sp_dbmmonitorhelpmonitoring] Script Date: 06/12/2013 13:16:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [sys].[sp_dbmmonitorhelpmonitoring]
as
begin
set nocount on
if (is_srvrolemember(N'sysadmin') <> 1 )
begin
raiserror(21089, 16, 1)
return (1)
end
declare #freq_type int, -- 4 = daily
#freq_interval int, -- Every 1 days
#freq_subday_type int, -- 4 = based on Minutes
#freq_subday_interval int, -- interval
#job_id uniqueidentifier,
#schedule_id int,
#retention_period int,
#jobname nvarchar( 256 )
select #jobname = isnull( formatmessage( 32047 ), N'Database Mirroring Monitor Job' )
select #job_id = job_id from msdb.dbo.sysjobs where name = #jobname
if (#job_id is null) -- if the job does not exist, error out
begin
raiserror( 32049, 16, 1 )
return 1
end
select #schedule_id = schedule_id from msdb.dbo.sysjobschedules where job_id = #job_id
select #freq_type = freq_type,
#freq_interval = freq_interval,
#freq_subday_type = freq_subday_type,
#freq_subday_interval = freq_subday_interval
from msdb.dbo.sysschedules where schedule_id = #schedule_id
-- If the frequency parameters are not what we expect then return an error
-- Someone has changed the job schedule on us
if (#freq_type <> 4) or (#freq_interval <> 1) or (#freq_subday_type <> 4)
begin
raiserror( 32037, 16, 1)
return 1
end
select #freq_subday_interval update_period
return 0
end
When I try to execute it, I get the error:
Msg 208, Level 16, State 6, Procedure sp_dbmmonitorhelpmonitoring, Line 46
Invalid object name 'sys.sp_dbmmonitorhelpmonitoring'.
My login is 'sa', I am mapped to the user 'dbo' in the [msdb] database. How do I modify this stored procedure?
You cannot alter a SP once you have marked it as a "system stored procedure". Instead, you have to drop it, recreate it and mark it as a system stored procedure again (using sp_ms_marksystemobject).
I'm sure you already realize how very dangerous it is to mess with anything that has been marked as "system". I feel obliged to strongly recommend that you make plenty of backups before you attempt any of this. Namely, back up: master, model and MSDB.

SQL Azure as a linked server -> get identity of inserted row

I've set up our Azure cloud DB to be a linked server to our 'SQL server 2008 R2'-server like this post described: http://blogs.msdn.com/b/sqlcat/archive/2011/03/08/linked-servers-to-sql-azure.aspx
I've enabled RPC and RPC Out because I read that somewhere.
Now the problem is I cannot get the ID of the just inserted record. Please take a look at this test table:
CREATE TABLE dbo.TEST
(
ID INT IDENTITY(1, 1) NOT NULL
CONSTRAINT PK_TEST_ID PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
I've also created this stored procedure:
CREATE PROCEDURE test_create #ID INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT INTO TEST
DEFAULT VALUES
SELECT #ID = SCOPE_IDENTITY()
END
I've tried to get the last inserted value through multiple ways but none of them are working:
DECLARE #ID INT
EXEC AZURE01.TestDB.dbo.test_create #ID OUTPUT
SELECT #ID
INSERT INTO AZURE01.TestDB.dbo.TEST DEFAULT VALUES
SELECT #ID = SCOPE_IDENTITY();
SELECT #ID
INSERT INTO AZURE01.TestDB.dbo.TEST DEFAULT VALUES
SELECT #ID = ##IDENTITY
SELECT #ID
SELECT * FROM OPENQUERY(AZURE01, 'INSERT INTO TestDB.dbo.TEST DEFAULT VALUES; SELECT SCOPE_IDENTITY() AS ID');
DECLARE #ScopeIdentity TABLE (ID int);
INSERT INTO #ScopeIdentity
EXEC AZURE01.master..sp_executesql N'
 INSERT TestDB.dbo.TEST DEFAULT VALUES;
 SELECT SCOPE_IDENTITY()';
SELECT * FROM #ScopeIdentity;
INSERT AZURE01.TestDB.dbo.TEST
OUTPUT inserted.ID
INTO #ScopeIdentity
DEFAULT VALUES
SELECT * FROM #ScopeIdentity
I understand why SCOPE_IDENTITY() and ##IDENTITY don't work (because they are local functions/variables which don't have information from the linked server) but the stored procedure with the output parameter should work, right? (locally on the server it works)
Anyone? :-)
Have you considered using a GUID (uniqueidentifier) field instead of or as well as int?
You can then generate the ID client-side (there's a multitude of tools to generate GUIDs) and pass that straight in your insert.
You then have the choice of re-selecting the row based on the GUID column to get the new int value or just use the GUID field as your PK and be done with it.
--create proc on azure database
create proc xxp_GetId
as
begin
--exec xxp_GetId
DECLARE #ID INT
INSERT INTO dbo.bit_CubeGetParameter DEFAULT VALUES
SELECT #ID = SCOPE_IDENTITY();
SELECT #ID
end
-- now run this query on your sql server
exec <"Link Server">.<"Azure Database Name">.dbo.xxp_GetId
The issue is the remote server execution.
What you can try is :
EXEC #TSqlBatch AT LinkedServer
What this does is tell the database at the other side to execute the tsql locally.
This has many uses. Maybe it can serve in this case as well, as the Scope_Identity() should be executed locally along with the insert.

how to create a global variable in sql server 2008

I have a view that returns users projects and also their windows login. An example of the data is below:
project | Login
------------------
project 1 | richab
project 2 | stevej
I need to append the domain to the login. I could put this in the code but i dont want to do that in every view I ever create that pulls users logins.
Can I create a global variable that I can reference in the views code. How can I achieve this? What's best practice for this?
I don't know if the SQL Server has global variables, but you can use a user defined function as follows:
CREATE FUNCTION dbo.fn_GetDomainName()
RETURNS STRING
AS
BEGIN
RETURN 'domain_name\\'
END
and do a SELECT dbo.fn_GetDomainName() + Login FROM table WHERE ... at the corresponding locations in your views.
There's no such thing as a global variable in SQL Server.
You can't just do:
DECLARE ##GlobalVar int
You can fake it with CONTEXT_INFO but to use something that would last beyond a session or restart you need to do something like this:
USE master
IF OBJECT_ID('dbo.sp_GlobalVariables') IS NOT NULL
DROP TABLE dbo.sp_GlobalVariables
GO
CREATE TABLE dbo.sp_GlobalVariables
(
varName NVARCHAR(100) COLLATE Latin1_General_CS_AI,
varValue SQL_VARIANT
)
GO
IF OBJECT_ID('dbo.sp_GetGlobalVariableValue') IS NOT NULL
DROP PROC dbo.sp_GetGlobalVariableValue
GO
CREATE PROC dbo.sp_GetGlobalVariableValue
(
#varName NVARCHAR(100),
#varValue SQL_VARIANT = NULL OUTPUT
)
AS
SET NOCOUNT ON
-- set the output parameter
SELECT #varValue = varValue
FROM sp_globalVariables
WHERE varName = #varName
-- also return it as a resultset
SELECT varName, varValue
FROM sp_globalVariables
WHERE varName = #varName
SET NOCOUNT OFF
GO
IF OBJECT_ID('dbo.sp_SetGlobalVariableValue') IS NOT NULL
DROP PROC dbo.sp_SetGlobalVariableValue
GO
CREATE PROC dbo.sp_SetGlobalVariableValue
(
#varName NVARCHAR(100),
#varValue SQL_VARIANT,
#result CHAR(1) = NULL OUTPUT
)
AS
SET NOCOUNT ON
UPDATE dbo.sp_GlobalVariables
SET varValue = #varValue
WHERE varName = #varName;
-- if it doesn't exist yet add it
IF ##rowcount = 0
BEGIN
INSERT INTO dbo.sp_GlobalVariables(varName, varValue)
SELECT #varName, #varValue
-- return it as inserted
SELECT #result = 'I'
END
-- return it as updated
SELECT #result = 'U'
SET NOCOUNT OFF
GO
DECLARE #dt DATETIME
SELECT #dt = GETDATE()
EXEC sp_SetGlobalVariableValue 'GlobalDate', #dt;
EXEC sp_SetGlobalVariableValue 'GlobalInt', 5;
EXEC sp_SetGlobalVariableValue 'GlobalVarchar', 'This is a very good global variable'
EXEC sp_SetGlobalVariableValue 'GlobalBinary', 0x0012314;
GO
EXEC sp_GetGlobalVariableValue 'GlobalDate'
EXEC sp_GetGlobalVariableValue 'GlobalInt'
EXEC sp_GetGlobalVariableValue 'GlobalVarchar'
EXEC sp_GetGlobalVariableValue 'GlobalBinary'
GO
-- update value in master
EXEC sp_SetGlobalVariableValue 'GlobalVarchar', 'New varchar value'
USE AdventureWorks
EXEC sp_GetGlobalVariableValue 'GlobalDate'
EXEC sp_GetGlobalVariableValue 'GlobalInt'
EXEC sp_GetGlobalVariableValue 'GlobalVarchar'
EXEC sp_GetGlobalVariableValue 'GlobalBinary'
-- update value in AdventureWorks
EXEC sp_SetGlobalVariableValue 'GlobalInt', 6
EXEC sp_GetGlobalVariableValue 'GlobalDate'
EXEC sp_GetGlobalVariableValue 'GlobalInt'
EXEC sp_GetGlobalVariableValue 'GlobalVarchar'
EXEC sp_GetGlobalVariableValue 'GlobalBinary'
You can use a temp table.
My scenario is that when data is updated via a known process, it adds a note to the audit table stating that 'this was done on purpose'.
When that proc fires, it inserts a single value into #auditnote (which is a temp table I create on the fly).
The trigger checks for that table. If it exists, it pulls off the note and puts it on the audit table.
If it doesn't, then it goes about it's business.
I looked at using an ## variable, but the trick there is determining if the variable exists. I don't see a way.
EXAMPLE:
Stored Procedure:
SELECT 'Alrighty Then' AS NOTE INTO #AuditNote
Trigger:
DECLARE #noteExists BIT
DECLARE #note NVARCHAR(500)
IF OBJECT_ID('tempdb..#auditnote') IS NOT NULL
BEGIN
SELECT
TOP (1)
#noteExists = 1,
#note = Note
FROM
#auditNote
END ELSE BEGIN
SELECT #noteExists = 0
END
-- do something with the note
IF #noteExists = 1
BEGIN
DROP TABLE #AuditNotes
END
I'm using #noteExists rather than a null check because someone could insert a null as the note, so we don't know if null means TABLE DOESN'T EXIST or NOTE IS NULL.