system stored procedures and table parameters - sql-server

I'm accustomed to doing this:
use MyDb
go
create type table_list as table (name sysname)
go
create proc rebuild_indexes
#ls table_list readonly
as
[...]
but now I want to create the proc in master and use it in other databases:
use master
go
create type table_list as table (name sysname)
go
create proc sp_rebuild_indexes
#ls table_list readonly
as
[...]
...it builds fine but I can't use it!
use MyDb
go
declare #ls table_list
yields:
Msg 2715, Level 16, State 3, Line 1 Column, parameter, or variable #1:
Cannot find data type table_list. Parameter or variable '#ls' has an
invalid data type.
so I tried creating a synonym, which doesn't complain during creation but doesn't help:
create synonym table_list for master.dbo.table_list
thoughts anyone?

Table types declared in different databases, even if they share the same name and structure, are not treated as being the same by SQL Server:
create database DB1
go
use DB1
go
create type TT1 as table (ID int not null)
go
create procedure Echo
#T TT1 readonly
as
select * from #T
go
create database DB2
go
use DB2
go
create type TT1 as table (ID int not null)
go
declare #T TT1
insert into #T(ID) values (1)
exec DB1..Echo #T
Result:
(1 row(s) affected)
Msg 206, Level 16, State 2, Procedure Echo, Line 0
Operand type clash: TT1 is incompatible with TT1
So far as I'm aware, there is no way to declare a variable in a database using a table type definition from another database. (e.g. anywhere where you see a user defined table type can be used, it can only be named as <table type> or <schema>.<table type>. 3 or 4 part names are not allowed)
(The above is true for 2008 and 2012; obviously, future versions may do something to address this)
As a work-around, you can do it the "poor man's" way - have your master defined stored procedure work against a temp table, rather than a user defined table type:
use Master
go
create procedure sp_Echo
as
select DB_NAME(),* from #t
go
create database DB1
go
use DB1
go
create table #t (ID int not null)
insert into #t (ID) values (1),(2)
exec sp_Echo
Results:
------------- -----------
DB1 1
DB1 2

AFAIK, user defined types can't be used outside the context of the database they were created in.
You may have to create the UDT in each database, but leave the sProc in master.
Is there a specific reason you're using the sp_ naming convention?
Peet

Related

Have a CREATE TABLE or CREATE PROCEDURE automatically determine column type in SSMS?

I have a database creation script that sets up tables, stored procedures, views, etc. When I change the type of a column in a create table statement, I want this change to be reflected in the create stored procedures / views / etc statements that reference that table without having to go through and manually change each one.
In other words I want my stored procedures to automatically determine the column type based on another column's type on creation. I don't need this to work on a live database with data, just while I'm iterating over the design and prototyping.
Something like a TYPE_OF() in this (fictional) example:
create table Logs
(
id int identity(1, 1) primary key,
userName varchar(32),
logType int foreign key references LogType(id),
description varchar(128),
datestamp datetime
);
go
create procedure WriteLog
(
#userName TYPE_OF(Logs.userName), -- should be varchar(32),
#logType int,
#description TYPE_OF(Logs.description) -- should be varchar(128)
)
as
begin
insert into Logs
values(#userName, #logType, #description, SYSDATETIME());
end
go;
I think I remember something similar from Oracle / SQL Plus / PLSQL but I am having trouble finding it.
I'm using SQL Server Management Studio v18.4
Not sure if the TYPEOF feature you're looking for exitsts, but you could try and use a DDL Trigger to keep your procedure in sync with the column type changes.
This trigger would get fired every time a table is altered and you'd just have to parse the EVENTDATA() to see if the column types in the Logs table have changed. The body of your trigger would look something like this:
CREATE TRIGGER OnLogsChanged
ON DATABASE
FOR ALTER_TABLE
AS
BEGIN
-- 1. Parse EVENTDATA() to see if the Logs table was altered
-- 2. If it has, store the definition of the WriteLog procedure into a variable by reading it from sys.procedures
-- 3. Read the new types for the columns of the Logs table from sys.all_columns
-- 4. replace the parameter declarations in the procedure definition to match the new types in the Logs table
-- 5. alter the procedure with the new definition by building up the ALTER PROCEDURE statement as a string and executing it with sp_executesql
END
As long as the trigger stays enabled your procedure should stay in sync with the table column types.

calling a synonym of a procedure where input parameter is user-defined table type

On DatabaseA I have User-Defined Data Type which is used as input parameter in a stored procedure. On another database (DatabaseB) I am creating synonym to this procedure and I am creating same data type there.
When I call the sysnonym I am reciving an error "Operand type clash: Ids is incompatible with Ids"
Any suggestion how to resolve this problem?
use DatabaseA
go
CREATE TYPE [dbo].[Ids] AS TABLE(
[Id] [sql_variant] NULL
)
GO
create procedure dbo.proc1 #Ids [Ids] readonly
as
begin
select *
from #Ids;
end
declare #Ids dbo.Ids;
insert into #Ids values (1),(2)
exec dbo.proc1
#Ids = #Ids
use DatabaseB
CREATE TYPE [dbo].[Ids] AS TABLE(
[Id] [sql_variant] NULL
)
go
CREATE SYNONYM [Admiral_CentralSystem01].[dbo_proc1] FOR [Admiral_CentralSystem01].[dbo].[proc1]
GO
declare #Ids dbo.Ids;
insert into #Ids values (1),(2)
exec [Admiral_CentralSystem01].[dbo_proc1]
#Ids = #Ids
You cannot pass Table variables as parameter in another db.
XML variable with XPath can solve your problem
Basically tables variant are database scoped
In order to use a user-defined type (UDT) in Microsoft SQL Server, you
must register it. Registering a UDT involves registering the assembly
and creating the type in the database in which you wish to use it.
UDTs are scoped to a single database, and cannot be used in multiple
databases unless the identical assembly and UDT are registered with
each database. Once the UDT assembly is registered and the type
created, you can use the UDT in Transact-SQL and in client code. For
more information, see CLR User-Defined Types.
https://learn.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-types/registering-user-defined-types-in-sql-server

Using stored procedures of another database

I have some procedures in database1 that are used to fill some tables using INSERT statements.
The tables I want to fill are in database2. How can I write a query with EXEC procedures in order to fill those tables?
I don't want to add stored procedures in the same database of the tables (database2)
This is one of the the stored procedures in db1
USE [database1]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddSubject]
#SubjectCode nvarchar(20),
#Credits int,
#Hours int
AS
INSERT INTO [database2].[dbo].[Subject]
([Code]
,[Credits]
,[Hours])
VALUES
(#SubjectCode,
#Credits,
#Hours)
Return ##Identity
And here is where I execute the stored procedure:
EXEC #SubID = [database1].[dbo].[AddSubject] #SubjectCode='1234', #Credits=2, #Hours=50
And it gives me the error:
Msg 208, Level 16, State 1, Procedure AddSubject, Line 14
Invalid object name 'database2.dbo.Subject'.
You can refer to SQL Server tables with a four-part name:
server.database.schema.table
For a different database you only need the last three parts:
insert otherdb.dbo.yourtable
(col1, col2, ...)
values ('val1', 'val2', ...)
You can use this in a stored procedure to insert into a table in a different database.
Yes! you can acess the database2 into database1 for doing this both database should one server
If your scheema is dbo then you can access as below:-
Insert into database1..table_name (col1,col2)
select value1,value2
If your Scheema is other than dbo then :-
Insert into database1.Scheema_name.table_name (col1,col2)
select value1,value2

Passing Table Valued parameter to stored procedure across different databases

I'm using SQL Server 2008.
How can I pass Table Valued parameter to a Stored procedure across different Databases, but same server?
Should I create the same table type in both databases?
Please, give an example or a link according to the problem.
Thanks for any kind of help.
In response to this comment (if I'm correct and that using TVPs between databases isn't possible):
What choice do I have in this situation? Using XML type?
The purist approach would be to say that if both databases are working with the same data, they ought to be merged into a single database. The pragmatist realizes that this isn't always possible - but since you can obviously change both the caller and callee, maybe just use a temp table that both stored procs know about.
I don't believe it's possible - you can't reference a table type from another database, and even with identical type definitions in both DBs, a value of one type isn't assignable to the other.
You don't pass the temp table between databases. A temp table is always stored in tempdb, and is accessible to your connection, so long as the connection is open and the temp table isn't dropped.
So, you create the temp table in the caller:
CREATE TABLE #Values (ID int not null,ColA varchar(10) not null)
INSERT INTO #Values (ID,ColA)
/* Whatever you do to populate the table */
EXEC OtherDB..OtherProc
And then in the callee:
CREATE PROCEDURE OtherProc
/* No parameter passed */
AS
SELECT * from #Values
Table UDTs are only valid for stored procs within the same database.
So yes you would have to create the type on each server and reference it in the stored procs - e.g. just run the first part of this example in both DBs http://msdn.microsoft.com/en-us/library/bb510489.aspx.
If you don't need the efficency you can always use other methods - i.e. pass an xml document parameter or have the s.p. expect a temp table with the input data.
Edit: added example
create database Test1
create database Test2
go
use Test1
create type PersonalMessage as TABLE
(Message varchar(50))
go
create proc InsertPersonalMessage #Message PersonalMessage READONLY AS
select * from #Message
go
use Test2
create type PersonalMessage as TABLE
(Message varchar(50))
go
create proc InsertPersonalMessage #Message PersonalMessage READONLY AS
select * from #Message
go
use Test1
declare #mymsg PersonalMessage
insert #mymsg select 'oh noes'
exec InsertPersonalMessage #mymsg
go
use Test2
declare #mymsg2 PersonalMessage
insert #mymsg2 select 'oh noes'
exec InsertPersonalMessage #mymsg2
Disadvantage is that there are two copies of the data.
But you would be able to run the batch against each database simultaneously.
Whether this is any better than using a table table is really down to what processing/data sizes you have - btw to use a temp table from an s.p. you just access it from the s.p. code (and it fails if it doesn't exist).
Another way to solve this (though not necessarily the correct way) is to only utilize the UDT as a part of a dynamic SQL call.
USE [db1]
CREATE PROCEDURE [dbo].[sp_Db2Data_Sync]
AS
BEGIN
/*
*
* Presumably, you have some other logic here that requires this sproc to live in db1.
* Maybe it's how you get your identifier?
*
*/
DECLARE #SQL VARCHAR(MAX) = '
USE [db2]
DECLARE #db2tvp tableType
INSERT INTO #db2tvp
SELECT dataColumn1
FROM db2.dbo.tblData td
WHERE td.Id = ' + CAST(#YourIdentifierHere AS VARCHAR) '
EXEC db2.dbo.sp_BulkData_Sync #db2tvp
'
EXEC(#SQL)
END
It's definitely not a purist approach, and it doesn't work for every use case, but it is technically an option.

Can you create a CLR UDT to allow for a shared Table type across databases?

If I had a SQL statement such as this:
CREATE TYPE [dbo].[typeRateLimitVariables] AS TABLE(
[vchColumnName] [varchar](250) NULL,
[decColumnValue] [decimal](25, 10) NULL
)
And I used it as a table variable to a UDF in a database, I'd have sufficient scope. BUt let's say I wanted to call the scalar UDF from another database on the same server, then I'd end up with an unknown type error.
I've tried creating the type on the calling DB, but obv. then I get a type mismatch because although each of the UDTs have the same name, they have different scopes and therefore are different types.
I know you can create CLR types, register the assembly to SQL Server, and then access the custom type universally.
My idea is to create a CLR UDT of type "TABLE", however I can't see how this can be implemented, as I know it must be of CLR type "SqlDbType.Structured";
My questions are:
Is there a way without using the CLR to create global scope in SQL 2008 R2 for a table variable, and if not...
How can I define a UDT in C# CLR, in which the UDT is essentially a UDT "AS TABLE"
I know you can create CLR types, register the assembly to SQL Server,
and then access the custom type universally.
Are you sure about this? User-Defined Types are database-level objects, not server-level. The only way to access them "universally" is by loading the Assembly into each of the databases and creating the User-Defined Type in each database. This much is stated in the MSDN documentation for Registering User-Defined Types in SQL Server:
Using UDTs Across Databases
UDTs are by definition scoped to a single
database. Therefore, a UDT defined in one database cannot be used in a
column definition in another database. In order to use UDTs in
multiple databases, you must execute the CREATE ASSEMBLY and CREATE
TYPE statements in each database on identical assemblies. Assemblies
are considered identical if they have the same name, strong name,
culture, version, permission set, and binary contents.
Once the UDT is registered and accessible in both databases, you can
convert a UDT value from one database for use in another. Identical
UDTs can be used across databases in the following scenarios:
Calling stored procedure defined in different databases.
Querying tables defined in different databases.
Selecting UDT data from one database table UDT column and
inserting it into a second database with an identical UDT column.
In these situations, any conversion required by the server occurs
automatically. You are not able to perform the conversions explicitly
using the Transact-SQL CAST or CONVERT functions.
To answer your specific questions:
1) Is there a way without using the CLR to create global scope in SQL 2008 R2 for a table variable, and if not...
Neither Table Types nor User-Defined Types are accessible across databases, accept in the one case for CLR UDTs as noted above in the MSDN documenation.
2) How can I define a UDT in C# CLR, in which the UDT is essentially a UDT "AS TABLE"
You cannot as those are two separate things (i.e. a "Type" vs a "Table Type") as opposed to being just two different means of implementation (i.e. T-SQL UDF / Stored Proc vs SQLCLR UDF / Stored Proc).
EDIT:
On a purely technical level, it is possible to use Types (Table Types and User-Defined Types) across databases, but only by switching the current context via the USE command which is only usable in ad hoc / dynamic SQL. Hence, this usage has limited applicability on a practical level, but nonetheless it is still possible as the following example shows:
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
GO
USE [msdb];
GO
PRINT 'Creating [GlobalTableDef] Table Type in [msdb]...';
CREATE TYPE dbo.GlobalTableDef
AS TABLE
(
[ID] INT NOT NULL IDENTITY(17, 22),
[CreateDate] DATETIME NOT NULL DEFAULT (GETDATE()),
[Something] NVARCHAR(2000) NULL
);
GO
PRINT 'Creating [TotalBytes] Function in [msdb]...';
GO
CREATE FUNCTION dbo.TotalBytes
(
#TableToSummarize dbo.GlobalTableDef READONLY
)
RETURNS INT
AS
BEGIN
DECLARE #TotalBytes INT = 0;
SELECT #TotalBytes += (4 + 8 + DATALENGTH(COALESCE(tmp.Something, '')))
FROM #TableToSummarize tmp;
RETURN #TotalBytes;
END;
GO
PRINT 'Testing the Table Type and Function...';
DECLARE #TmpTable dbo.GlobalTableDef;
INSERT INTO #TmpTable (Something) VALUES (N'this is a test');
INSERT INTO #TmpTable (Something) VALUES (NULL);
INSERT INTO #TmpTable (Something) VALUES (N'still seems to be a test');
SELECT * FROM #TmpTable;
SELECT dbo.TotalBytes(#TmpTable) AS [TotalBytesUsed];
GO
USE [tempdb];
GO
PRINT 'Creating [TypeTest] Proc in [tempdb]...';
GO
CREATE PROCEDURE dbo.TypeTest
AS
SET NOCOUNT ON;
SELECT 1 AS [Step], DB_NAME() AS [CurrentDB];
EXEC('
SELECT 2 AS [Step], DB_NAME() AS [CurrentDB];
USE [msdb];
SELECT 3 AS [Step], DB_NAME() AS [CurrentDB];
DECLARE #TmpTable dbo.GlobalTableDef;
USE [tempdb];
SELECT 4 AS [Step], DB_NAME() AS [CurrentDB];
-- local query to prove context is tempdb
SELECT TOP 5 * FROM sys.objects;
INSERT INTO #TmpTable (Something) VALUES (N''this is a new test'');
INSERT INTO #TmpTable (Something) VALUES (NULL);
INSERT INTO #TmpTable (Something) VALUES (N''non-empty value'');
INSERT INTO #TmpTable (Something) VALUES (NULL);
INSERT INTO #TmpTable (Something) VALUES (N''woo-hoo!!!!!!!!!!!!!!!'');
SELECT * FROM #TmpTable;
SELECT [msdb].dbo.TotalBytes(#TmpTable) AS [TotalBytesUsed];
');
GO
USE [master];
GO
SELECT 5 AS [Step], DB_NAME() AS [CurrentDB];
EXEC tempdb.dbo.TypeTest;
--------------------------------
USE [tempdb];
GO
IF (OBJECT_ID(N'tempdb.dbo.TypeTest') IS NOT NULL)
BEGIN
PRINT 'Dropping [TypeTest] Proc from [tempdb]...';
DROP PROCEDURE dbo.TypeTest;
END;
GO
USE [msdb];
GO
IF (OBJECT_ID(N'dbo.TotalBytes') IS NOT NULL)
BEGIN
PRINT 'Dropping [TotalBytes] Function from [msdb]...';
DROP FUNCTION dbo.TotalBytes;
END;
GO
IF (EXISTS(
SELECT *
FROM sys.table_types stt
WHERE stt.name = N'GlobalTableDef'
))
BEGIN
PRINT 'Dropping [GlobalTableDef] Table Type from [msdb]...';
DROP TYPE dbo.GlobalTableDef;
END;
GO

Resources