How to fix that procedure in sql - sql

I created procedure which count not null rows in the column, but query throws errors: #tableName is not declared and invalid object name tempTable. I don't know why code throws that errors, because all variables are declared.
Msg 1087, Level 16, State 1, Procedure getLenCol, Line 7 [Batch Start Line 0]
Must declare the table variable "#tableName".
Msg 208, Level 16, State 1, Line 11
Invalid object name 'tempTable'.
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255));
DECLARE #query varchar(255)
insert into #tempTable(smth) select #colName from #tableName where #colName is not null
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Also when I make that program in another way, that code throw
Msg 1087, Level 15, State 2, Line 11
error.
Must declare the table variable "#tempTable".
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255))
DECLARE #query varchar(255)
SET #query = concat('insert into #tempTable(smth) select ',#colName,' from ',#tableName,' where ',#colName,' is not null');/*#colName from #tableName where #colName is not NULL*/
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Is it a way to fix that error?

Obviously, your code is subject to SQL injection attacks -- as the comments on the question have explained.
But your issue is the scoping rules around your table variable. You can fix that by using:
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
I don't think there is any way around the SQL injection vulnerabilities for the logic you have suggested. However, your code is so non-sensical that I doubt that it is really representative of your actual code.

As it seems that many are not aware of the dangers of SQL Injection, including Gordon, I wanted to expand on that first. Let's, take the accepted answer (at time of writing), which gives the following:
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #query varchar(255)
DECLARE #tempTable Table(smth varchar(255))
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
GO
Now, let's be someone malicious:
EXEC dbo.getLenCol #colName = N'1; CREATE LOGIN NewLogin WITH PASSWORD = ''1'', CHECK_POLICY = OFF;/*',
#tableName =N'*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--';
So, what does the above, in the dynamic SQL run? Let's find out by adding PRINT #query; to the SP's definition:
select 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* from */ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
And, with a little formatting for ease of reading:
select 1;
CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;
/* from */
ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;
-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
OH. OHHHHHHHHHHH. Congratulations you are the new proud owner of a SQL Server that has a new sysadmin LOGIN!
NEVER, inject unsanitised string into a string in SQL. NEVER.
Rather than repeating myself, I'm going to link to my article Dos and Don'ts of Dynamic SQL, however, you can easily make the above query secure with a few of uses of QUOTENAME:
CREATE OR ALTER PROC getLenCol
#schemaName sysname = N'dbo', --You should define the schema too
#tableName sysname, --An object can't be longer than 128 characters, so sysname is best
#colName sysname
AS
BEGIN
DECLARE #query nvarchar(MAX);
DECLARE #tempTable Table(smth varchar(255));
SET #QUERY = CONCAT(N'SELECT ', QUOTENAME(#colName),N' FROM ', QUOTENAME(#schemaName), N'.', QUOTENAME(#tableName), N' WHERE ', QUOTENAME(#colName), N' IS NOT NULL;');
PRINT #query;
INSERT INTO #tempTable (smth)
EXEC sys.sp_executesql #query;
END;
GO
And what happens if we run the above EXEC statement before? Well you get the statement below (with added formatting):
SELECT [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*]
FROM [dbo].[*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--]
WHERE [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*] IS NOT NULL;
And no surprised, that generated the error
Invalid object name 'dbo.*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--'.
Now your dynamic statement is safe from injection.

I would highly recommend against this approach, firstly calling this procedure is as much, if not more typing that just doing a count. Compare the two
EXECUTE dbo.getLenCol #tableName = 'dbo.SomeTable', #colName = 'ID';
SELECT COUNT(ID) FROM dbo.SomeTable;
Even with the shortened exec, and not using named parameters it is longer:
EXEC dbo.getLenCol dbo.SomeTable', 'ID';
It is very, very rare that a catch all query like this, with object names being passed as parameters is going to be the correct approach. There are some maintenance queries where it is useful, but these are the exception, not the rule.
If you must do this though, you should do a little bit of validation first, and check that both the table name and column name are valid before executing any dynamic SQL by using COL_LENGTH(#tableName, #ColName). e.g
CREATE OR ALTER PROC getLenCol #tableName SYSNAME, #colName SYSNAME
AS
BEGIN
IF COL_LENGTH(#tableName, #ColName) IS NOT NULL
BEGIN
DECLARE #SQL NVARCHAR(MAX) = CONCAT('SELECT COUNT(', #colName, ') FROM ', #tableName, ';');
EXECUTE sp_executesql #SQL;
RETURN;
END
-- TABLE OR COLUMN WAS NOT VALID RETURN -1 TO INDICATE THAT
SELECT -1;
END

Related

Assigining value to parameter using variable in stored procedure in SQL Server

I have a simple stored procedure with one parameter #Name which I want to replace with another variable.
I am actually looking for SQL injection character and if name contains -- then it should replace it with blank. The stored procedure shown below, it is executing without an error, but not replacing the string for example let is say user searches for EXEC John'''select * FROM TEST2 -- which has SQL injection statement in it
CREATE PROCEDURE GetStudentDetails
#Name nvarchar(300)
AS
BEGIN
SET NOCOUNT ON;
SELECT #Name = REPLACE(#Name ,'--','');
SET #Name = REPLACE(#Name ,'--','');
SELECT *
FROM TABLENAME
WHERE Name LIKE N'%'+ #Name +'%'
END
Updated stored procedure:
CREATE PROCEDURE GetStudentDetails
#Name nvarchar(300)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SafeSearchItem nvarchar(30);
SELECT #SafeSearchItem = REPLACE(#Name ,N'--',N'')
SET #SafeSearchItem = REPLACE(#Name ,N'--',N'')
SELECT *
FROM TABLENAME
WHERE Name LIKE N'%'+ #SafeSearchItem +'%'
END
EXEC
EXEC John'''select * FROM TEST2 --
In the second stored procedure, I am always able to inject SQL - not sure it is my system?
As it stands, we can't answer the question, as, well there isn't a question applicable for information we're been provided. There is no risk of injection in the SP we have, thus, there is not answer on how to avoid it.
Anyway, instead, what i'm going to do is show firstly why that SP isn't subject to injection and then change it so it would be, and how the limited "fix" in it could easily be avoided.
Firstly, let's start with a simple table and data (I strongly suggest running any following scripts in a Sandbox environment!):
USE Sandbox;
GO
CREATE TABLE InjectionReady (ID int IDENTITY(1,1), SomeText varchar(500));
INSERT INTO InjectionReady
VALUES ('Here is some text'),
('Life is like a box a chocolates'),
('Milk Chocolate is my favourite'),
('Cheese is dairy product'),
('Chocolate is a dairy product'),
('Cows say "moo"!'),
('English Cat says "Meow"'),
('Japanese Cat says "Nyaa"');
GO
OK, and now let's create your SP (amended for our object). and then do some tests:
CREATE PROCEDURE NonInjectionSearch #Wildcard nvarchar(100) AS
SELECT #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SET #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N'%'+ #Wildcard +N'%';
GO
EXEC NonInjectionSearch 'Chocolate';
EXEC NonInjectionSearch '''; DROP TABLE InjectionReady;--';
EXEC NonInjectionSearch '''; DROP TABLE InjectionReady; SELECT ''';
No injection. Great! Ok, now for an SP that could suffer injection:
CREATE PROCEDURE InjectionSearch #Wildcard nvarchar(100) AS
SELECT #Wildcard = REPLACE(#Wildcard ,N'--',N'');
SET #Wildcard = REPLACE(#Wildcard ,N'--',N'');
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N''%'+ #Wildcard + N'%'';'; --Yes, intentional non parametrisation
PRINT #SQL;
EXEC (#SQL);
GO
EXEC InjectionSearch 'Chocolate';
GO
EXEC InjectionSearch '''; CREATE TABLE Injection1(ID int);--'; --This'll fail
GO
EXEC InjectionSearch '''; CREATE TABLE Injection2(ID int); SELECT '''; --Oh! This worked!
GO
So, how could you avoid this? Well, Parametrise your dynamic SQL:
CREATE PROCEDURE ParamSearch #Wildcard nvarchar(100) AS
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'
SELECT *
FROM InjectionReady
WHERE SomeText LIKE N''%'' + #pWildCard +''%'';'; --Yes, intentional non parametrisation
PRINT #SQL;
EXEC sp_executesql #SQL, N'#pWildcard nvarchar(500)', #pWildCard = #Wildcard;
GO
EXEC ParamSearch 'Chocolate';
GO
EXEC ParamSearch '''; CREATE TABLE Injection1(ID int);--'; --Won't inject
GO
EXEC ParamSearch '''; CREATE TABLE Injection2(ID int); SELECT '''; --Oh! this didn't inject either
Dynamic objects bring another level to this, however, I'll only cover this if required; as it stands (like I said at the start) the question asked can't happen for the scenario we have.
Clean up:
DROP TABLE Injection2;
DROP PROC ParamSearch;
DROP PROC InjectionSearch;
DROP PROC NonInjectionSearch;
DROP TABLE InjectionReady;

Execute openrowset stored procedure, parameter use

Little bit of background information:
I have a stored procedure, lets call it SP1. SP1 calls another stored procedure: SP2. SP2 calls another stored procedure: SP3.
Now, the first stored procedure (SP1) returns a resultset. The resultset are parameters for SP2, this is done with a cursor.
Because of these nested inserts and executes, i have to use an openrowset dynamic SQL string to execute my stored procedures.
This is my query:
DECLARE #P_Source varchar(255) = 'test'
DECLARE #P_Location varchar(255) = 'test'
DECLARE #sql varchar(max)
SET #sql = 'INSERT INTO #tmp
SELECT *
FROM OPENROWSET (
''SQLOLEDB'',
''Server=(local);TRUSTED_CONNECTION=YES;'',
''set fmtonly off
EXECUTE dbo.SP1
#P_Source = '''''+#P_Source+'''''''
,#P_Location = '''''+#P_Location+'''''''
)'
exec(#sql)
(I have ofcourse created the table #tmp). I have more parameters to be exact (12), all varchar, but I left them out to not make it messy.
I'm getting the following error
Msg 102, Level 15, State 1, Line 12
Incorrect syntax near ','.
am I using the openrowset command in the correct way with the corresponding procedure parameters?
All those quotes get confusing. By doing select #sql prior to the exec you can see what SQL Server is going to try and do. Based on the query you've provided #sql currently contains:
INSERT INTO #tmp
SELECT *
FROM OPENROWSET (
'SQLOLEDB',
'Server=(local);TRUSTED_CONNECTION=YES;',
'set fmtonly off
EXECUTE dbo.SP1
#P_Source = ''test'''
,#P_Location = ''test'''
)
To help you build up to the final solution you could try having a dummy SP1 that takes two numeric parameters - that'll eliminate some quotes for you to worry about. Once you have that working you can proceed to add string parameters until you get what you want.
For sql linked server use OPENQUERY
https://learn.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql
and sp_executesql
DECLARE #P_Source varchar(255) = 'test'
DECLARE #P_Location varchar(255) = 'test'
DECLARE #SQL NVARCHAR(MAX) = '',
#QUERY NVARCHAR(MAX) = '',
#Params NVARCHAR(500) = N'DECLARE #P_Source VARCHAR(255),#P_Location VARCHAR(255); ',
#ParamsValue NVARCHAR(500) = N'SELECT #P_Source = '''''+#P_Source+''''', #P_Location = '''''+#P_Location+''''';'
SET #Query = N'set fmtonly off; EXECUTE dbo.SP1 #P_Source, #P_Location'
SET #SQL = 'SELECT * FROM OPENQUERY([Local],'' sys.sp_executesql ' + #Params + #ParamsValue + #Query +''' )'
INSERT INTO #Tmp
EXEC (#SQL)

SQL Server stored procedures calls require variable declaration (but already declared)

I have a few stored procedures which I call in this order.
So, from the first stored procedure, importTaxonomy I call parseXBRL and from parseXBRL I call createTaxonomyStructure.
But in this flow, when the code of the last stored procedure is executed I get an error.
Msg 1087, Level 15, State 2, Line 1
Must declare the table variable "#temporaryTable".
Below you can find the first few lines code of this stored procedure:
CREATE PROCEDURE createTaxonomyStructure #taxonomy_table nvarchar(max), #debug bit = 0
AS
DECLARE #statement NVARCHAR(MAX)
DECLARE #temporaryTable TABLE (taxonomyLine NVARCHAR(MAX)) -- declared a temporary table to avoid creating a Dynamic Query with the entire cursor, but just with the temporary table
DECLARE #taxonomyLine NVARCHAR(MAX) -- variable that will store one line of the taxonomy
SET #statement = 'INSERT INTO #temporaryTable SELECT taxText FROM ' + #taxonomy_table -- statement that will import the taxonomy in the temporary table
EXEC sp_executesql #statement
DECLARE taxonomyCursor CURSOR READ_ONLY FAST_FORWARD FOR -- read each line in the taxonomy to parse afterwards
SELECT taxonomyLine
FROM #temporaryTable
OPEN taxonomyCursor
FETCH NEXT FROM taxonomyCursor INTO #taxonomyLine -- parsing each taxonomy line and extracting the values from important attributes
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #id_element NVARCHAR(MAX)
DECLARE #leaf_element NVARCHAR(MAX)
SELECT #id_element = (SELECT dbo.extract_IDElement(#taxonomyLine))
SELECT #leaf_element = (SELECT dbo.extract_IDLeafElement(#taxonomyLine))
SET #statement = 'UPDATE ' + #taxonomy_table + ' SET fullName = ''' + #id_element + ''', leafName = ''' + #leaf_element + '''';
EXEC sp_executesql #statement
END
I do declare this variable, but I still get the error and I don't understand why.
How can I get over this error?
Thanks!
the error is here:
SET #statement = 'INSERT INTO #temporaryTable SELECT taxText FROM ' + #taxonomy_table -- statement that will import the taxonomy in the temporary table
EXEC sp_executesql #statement
change it to
set #statement = 'select taxText from ' + #taxonomy_table
insert into #temporaryTable
exec sp_executesql #statement
The error is happening because in the scope of executing #statement there's no variable table #temporaryTable.

Send query as parameter to SQL function

I want to create a SQL tabled-value function that will receive a query as n parameter through my API. In my function I want execute that query. The query will be a SELECT statement.
This is what I have done so far and what to achieve but it is not the correct way to do so.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS (
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200))
RETURNS TABLE
AS
RETURN
(
-- Execute query into a table
SELECT *
INTO #tableName
FROM (
EXEC(#query)
)
)
GO
Please suggest the correct way!
Try this one -
CREATE PROCEDURE dbo.sp_CUSTOM_EXPORT_RESULTS
#query NVARCHAR(MAX) = 'SELECT * FROM dbo.test'
, #guid UNIQUEIDENTIFIER
, #tableName VARCHAR(200) = 'test2'
AS BEGIN
SELECT #query =
REPLACE(#query,
'FROM',
'INTO [' + #tableName + '] FROM')
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
IF OBJECT_ID (N''' + #tableName + ''') IS NOT NULL
DROP TABLE [' + #tableName + ']
' + #query
PRINT #SQL
EXEC sys.sp_executesql #SQL
RETURN 0
END
GO
Output -
IF OBJECT_ID (N'test2') IS NOT NULL
DROP TABLE [test2]
SELECT * INTO [test2] FROM dbo.test
What I see in your question is encapsulation of:
taking a dynamic SQL expression
executing it to fill a parametrized table
Why do you want to have such an encapsulation?
First, this can have a negative impact on your database performance. Please read this on EXEC() and sp_executesql() . I hope your SP won't be called from multiple parts of your application, because this WILL get you into trouble, at least performance-wise.
Another thing is - how and where are you constructing your SQL? Obviously you do it somewhere else and it seems its manually created. If we're talking about a contemporary application, there are lot of OR/M solutions for this and manual construction of TSQL in runtime should be always avoided if possible. Not to mention EXEC is not guarding you against any form of SQL injection attacks. However, if all of this is a part of some database administration TSQL bundle, forget his paragraph.
At the end, if you want to simply load a new table from some existing table (or part of it) as a part of some administration task in TSQL, consider issuing a SELECT ... INTO ... This will create a new target table structure for you (omitting indexes and constraints) and copy the data. SELECT INTO will outperform INSERT INTO SELECT because SELECT INTO gets minimally logged.
I hope this will get you (and others) at least a bit on the right track.
You can use stored procedure as well, here is the code that you can try.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS
(
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200)
)
RETURNS TABLE
AS
RETURN
(
declare #strQuery nvarchar(max)
-- Execute query into a table
SET #strQuery = REPLACE(#query,'FROM', 'INTO '+#tableName+' FROM')
exec sp_executesql #strQuery
)
GO

Alter user defined type in SQL Server

I created few user defined types in my database as below
CREATE TYPE [dbo].[StringID] FROM [nvarchar](20) NOT NULL
and assigned them to various tables. The tables in my database are in various schemas (not only dbo)
But I realized I need bigger field, and I need to alter, e.g increase from [nvarchar](20) to [nvarchar](50), but there is no ALTER TYPE statement.
I need a script that uses a temp table/cursor whatever and saves all the tables and fields where my type is used. Then change existing fields to base type - e.g. from CustID [StringID] to CustID [nvarchar(20)].
Drop the user type and recreate it with new type - e.g. nvarchar(50)
and finally set back fields to user type
I do not have rules defined on types, so don't have to drop rules and re-add them.
Any help is appreciated.
This is what I normally use, albeit a bit manual:
/* Add a 'temporary' UDDT with the new definition */
exec sp_addtype t_myudt_tmp, 'numeric(18,5)', NULL
/* Build a command to alter all the existing columns - cut and
** paste the output, then run it */
select 'alter table dbo.' + TABLE_NAME +
' alter column ' + COLUMN_NAME + ' t_myudt_tmp'
from INFORMATION_SCHEMA.COLUMNS
where DOMAIN_NAME = 't_myudt'
/* Remove the old UDDT */
exec sp_droptype t_mydut
/* Rename the 'temporary' UDDT to the correct name */
exec sp_rename 't_myudt_tmp', 't_myudt', 'USERDATATYPE'
We are using the following procedure, it allows us to re-create a type from scratch, which is "a start". It renames the existing type, creates the type, recompiles stored procs and then drops the old type. This takes care of scenarios where simply dropping the old type-definition fails due to references to that type.
Usage Example:
exec RECREATE_TYPE #schema='dbo', #typ_nme='typ_foo', #sql='AS TABLE([bar] varchar(10) NOT NULL)'
Code:
CREATE PROCEDURE [dbo].[RECREATE_TYPE]
#schema VARCHAR(100), -- the schema name for the existing type
#typ_nme VARCHAR(128), -- the type-name (without schema name)
#sql VARCHAR(MAX) -- the SQL to create a type WITHOUT the "CREATE TYPE schema.typename" part
AS DECLARE
#scid BIGINT,
#typ_id BIGINT,
#temp_nme VARCHAR(1000),
#msg VARCHAR(200)
BEGIN
-- find the existing type by schema and name
SELECT #scid = [SCHEMA_ID] FROM sys.schemas WHERE UPPER(name) = UPPER(#schema);
IF (#scid IS NULL) BEGIN
SET #msg = 'Schema ''' + #schema + ''' not found.';
RAISERROR (#msg, 1, 0);
END;
SELECT #typ_id = system_type_id FROM sys.types WHERE UPPER(name) = UPPER(#typ_nme);
SET #temp_nme = #typ_nme + '_rcrt'; -- temporary name for the existing type
-- if the type-to-be-recreated actually exists, then rename it (give it a temporary name)
-- if it doesn't exist, then that's OK, too.
IF (#typ_id IS NOT NULL) BEGIN
exec sp_rename #objname=#typ_nme, #newname= #temp_nme, #objtype='USERDATATYPE'
END;
-- now create the new type
SET #sql = 'CREATE TYPE ' + #schema + '.' + #typ_nme + ' ' + #sql;
exec sp_sqlexec #sql;
-- if we are RE-creating a type (as opposed to just creating a brand-spanking-new type)...
IF (#typ_id IS NOT NULL) BEGIN
exec recompile_prog; -- then recompile all stored procs (that may have used the type)
exec sp_droptype #typename=#temp_nme; -- and drop the temporary type which is now no longer referenced
END;
END
GO
CREATE PROCEDURE [dbo].[recompile_prog]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #v TABLE (RecID INT IDENTITY(1,1), spname sysname)
-- retrieve the list of stored procedures
INSERT INTO
#v(spname)
SELECT
'[' + s.[name] + '].[' + items.name + ']'
FROM
(SELECT sp.name, sp.schema_id, sp.is_ms_shipped FROM sys.procedures sp UNION SELECT so.name, so.SCHEMA_ID, so.is_ms_shipped FROM sys.objects so WHERE so.type_desc LIKE '%FUNCTION%') items
INNER JOIN sys.schemas s ON s.schema_id = items.schema_id
WHERE is_ms_shipped = 0;
-- counter variables
DECLARE #cnt INT, #Tot INT;
SELECT #cnt = 1;
SELECT #Tot = COUNT(*) FROM #v;
DECLARE #spname sysname
-- start the loop
WHILE #Cnt <= #Tot BEGIN
SELECT #spname = spname
FROM #v
WHERE RecID = #Cnt;
--PRINT 'refreshing...' + #spname
BEGIN TRY -- refresh the stored procedure
EXEC sp_refreshsqlmodule #spname
END TRY
BEGIN CATCH
PRINT 'Validation failed for : ' + #spname + ', Error:' + ERROR_MESSAGE();
END CATCH
SET #Cnt = #cnt + 1;
END;
END
there's a good example of a more comprehensive script here
It's worth noting that this script will include views if you have any. I ran it and instead of exec'ing inline generated a script as the output which I then tweaked and ran.
Also, if you have functions/sprocs using the user defeined types you'll need to drop those before running your script.
Lesson Learned: in future, don't bother with UDTs they're more hassle than they're worth.
SET NOCOUNT ON
DECLARE #udt VARCHAR(150)
DECLARE #udtschema VARCHAR(150)
DECLARE #newudtschema VARCHAR(150)
DECLARE #newudtDataType VARCHAR(150)
DECLARE #newudtDataSize smallint
DECLARE #OtherParameter VARCHAR(50)
SET #udt = 'Name' -- Existing UDDT
SET #udtschema = 'dbo' -- Schema of the UDDT
SET #newudtDataType = 'varchar' -- Data type for te new UDDT
SET #newudtDataSize = 500 -- Lenght of the new UDDT
SET #newudtschema = 'dbo' -- Schema of the new UDDT
SET #OtherParameter = ' NULL' -- Other parameters like NULL , NOT NULL
DECLARE #Datatype VARCHAR(50),
#Datasize SMALLINT
DECLARE #varcharDataType VARCHAR(50)
DECLARE #Schemaname VARCHAR(50),
#TableName VARCHAR(50),
#FiledName VARCHAR(50)
CREATE TABLE #udtflds
(
Schemaname VARCHAR(50),
TableName VARCHAR(50),
FiledName VARCHAR(50)
)
SELECT TOP 1
#Datatype = Data_type,
#Datasize = character_maximum_length
FROM INFORMATION_SCHEMA.COLUMNS
WHERE Domain_name = #udt
AND Domain_schema = #udtschema
SET #varcharDataType = #Datatype
IF #DataType Like '%char%'
AND #Datasize IS NOT NULL
AND ( #newudtDataType <> 'varchar(max)'
OR #newudtDataType <> 'nvarchar(max)'
)
BEGIN
SET #varcharDataType = #varcharDataType + '('
+ CAST(#Datasize AS VARCHAR(50)) + ')'
END
INSERT INTO #udtflds
SELECT TABLE_SCHEMA,
TABLE_NAME,
Column_Name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE Domain_name = #udt
AND Domain_schema = #udtschema
DECLARE #exec VARCHAR(500)
DECLARE alter_cursor CURSOR
FOR SELECT Schemaname,
TableName,
FiledName
FROM #udtflds
OPEN alter_cursor
FETCH NEXT FROM alter_cursor INTO #Schemaname, #TableName, #FiledName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #exec = 'Alter Table ' + #Schemaname + '.' + #TableName
+ ' ALTER COLUMN ' + #FiledName + ' ' + #varcharDataType
EXECUTE ( #exec
)
FETCH NEXT FROM alter_cursor INTO #Schemaname, #TableName, #FiledName
END
CLOSE alter_cursor
SET #exec = 'DROP TYPE [' + #udtschema + '].[' + #udt + ']'
EXEC ( #exec
)
SET #varcharDataType = #newudtDataType
IF #newudtDataType Like '%char%'
AND #newudtDataSize IS NOT NULL
AND ( #newudtDataType <> 'varchar(max)'
OR #newudtDataType <> 'nvarchar(max)'
)
BEGIN
SET #varcharDataType = #varcharDataType + '('
+ CAST(#newudtDataSize AS VARCHAR(50)) + ')'
END
SET #exec = 'CREATE TYPE [' + #newudtschema + '].[' + #udt + '] FROM '
+ #varcharDataType + ' ' + #OtherParameter
EXEC ( #exec
)
OPEN alter_cursor
FETCH NEXT FROM alter_cursor INTO #Schemaname, #TableName, #FiledName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #exec = 'Alter Table ' + #Schemaname + '.' + #TableName
+ ' ALTER COLUMN ' + #FiledName + ' ' + '[' + #newudtschema
+ '].[' + #udt + ']'
EXECUTE ( #exec
)
FETCH NEXT FROM alter_cursor INTO #Schemaname, #TableName, #FiledName
END
CLOSE alter_cursor
DEALLOCATE alter_cursor
SELECT *
FROM #udtflds
DROP TABLE #udtflds
1: http://www.sql-server-performance.com/2008/how-to-alter-a-uddt/ has replaced http://www.sql-server-performance.com/faq/How_to_alter_a%20_UDDT_p1.aspx
The simplest way to do this is through Visual Studio's object explorer, which is also supported in the Community edition.
Once you have made a connection to SQL server, browse to the type, right click and select View Code, make your changes to the schema of the user defined type and click update. Visual Studio should show you all of the dependencies for that object and generate scripts to update the type and recompile dependencies.
As devio says there is no way to simply edit a UDT if it's in use.
A work-round through SMS that worked for me was to generate a create script and make the appropriate changes; rename the existing UDT; run the create script; recompile the related sprocs and drop the renamed version.
The solutions provided here can only be applied if the user defined types are used in table definitions only, and if the UDT columns are not indexed.
Some developers also have SP's and functions using UDT parameters, which is not covered either. (see comments on Robin's link and in the Connect entry)
The Connect entry from 2007 has finally been closed after 3 years:
Thank you for submitting this
suggestion, but given its priority
relative to the many other items in
our queue, it is unlikely that we will
actually complete it. As such, we are
closing this suggestion as “won’t
fix”.
I tried to solve a similiar problem ALTERing XML SCHEMA COLLECTIONS, and the steps seem to mostly apply to ALTER TYPE, too:
To drop a UDT, the following steps are necessary:
If a table column references the UDT, it has to be converted to the underlying type
If the table column has a default constraint, drop the default constraint
If a procedure or function has UDT parameters, the procedure or function has to be dropped
If there is an index on a UDT column, the index has to be dropped
If the index is a primary key, all foreign keys have to be dropped
If there are computed columns based on a UDT column, the computed columns have to be dropped
If there are indexes on these computed columns, the indexes have to be dropped
If there are schema-bound views, functions, or procedures based on tables containing UDT columns, these objects have to be dropped
I ran into this issue with custom types in stored procedures, and solved it with the script below. I didn't fully understand the scripts above, and I follow the rule of "if you don't know what it does, don't do it".
In a nutshell, I rename the old type, and create a new one with the original type name. Then, I tell SQL Server to refresh its details about each stored procedure using the custom type. You have to do this, as everything is still "compiled" with reference to the old type, even with the rename. In this case, the type I needed to change was "PrizeType". I hope this helps. I'm looking for feedback, too, so I learn :)
Note that you may need to go to Programmability > Types > [Appropriate User Type] and delete the object. I found that DROP TYPE doesn't appear to always drop the type even after using the statement.
/* Rename the UDDT you want to replace to another name */
exec sp_rename 'PrizeType', 'PrizeTypeOld', 'USERDATATYPE';
/* Add the updated UDDT with the new definition */
CREATE TYPE [dbo].[PrizeType] AS TABLE(
[Type] [nvarchar](50) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
[ImageUrl] [varchar](max) NULL
);
/* We need to force stored procedures to refresh with the new type... let's take care of that. */
/* Get a cursor over a list of all the stored procedures that may use this and refresh them */
declare sprocs cursor
local static read_only forward_only
for
select specific_name from information_schema.routines where routine_type = 'PROCEDURE'
declare #sprocName varchar(max)
open sprocs
fetch next from sprocs into #sprocName
while ##fetch_status = 0
begin
print 'Updating ' + #sprocName;
exec sp_refreshsqlmodule #sprocName
fetch next from sprocs into #sprocName
end
close sprocs
deallocate sprocs
/* Drop the old type, now that everything's been re-assigned; must do this last */
drop type PrizeTypeOld;
New answer to an old question:
Visual Studio Database Projects handle the drop and recreate process when you deploy changes. It will drop stored procs that use UDDTs and then recreate them after dropping and recreating the data type.
1.Rename the old UDT,
2.Execute query ,
3.Drop the old UDT.
Simple DROP TYPE first then CREATE TYPE again with corrections/alterations?
There is a simple test to see if it is defined before you drop it ... much like a table, proc or function -- if I wasn't at work I would look what that is?
(I only skimmed above too ... if I read it wrong I apologise in advance! ;)