I have a process which dynamically alters my SQL2K5 table structure according to changes in a published meta-data layer.
For example, if a new column needs to be added a
INFORMATION_SCHEMA views are just that - views. You can't update them so they are unlikely to cause any deadlocks. If you want to determine the real source (which I assume has something to do with your alters, or other code within the cursor that you didn't show, or other code you're calling in combination with calling these procedures - since selects against views and then selecting variables can't be the cause), I suggest reading Gail Shaw's blog post on interpreting deadlocks.
In spite of (1) I still suggest using more modern catalog views than INFORMATION_SCHEMA. The same information can be derived from, for example, sys.key_constraints.
You're using the default cursor options; and you're nesting cursors. If you end up still using cursors, you should get in the habit of using a less resource intensive cursor (e.g. LOCAL STATIC FORWARD_ONLY READ_ONLY).
You don't actually need a cursor to do this. Here is how I would re-write the PK table script:
CREATE PROCEDURE dbo.ScriptPKForTable
@TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@pkName SYSNAME,
@clustered BIT,
@object_id INT,
@sql NVARCHAR(MAX);
SELECT
@object_id = OBJECT_ID(UPPER(@TableName));
SELECT
@pkName = kc.name,
@clustered = CASE i.[type]
WHEN 1 THEN 1 ELSE 0 END
FROM
sys.key_constraints AS kc
INNER JOIN
sys.indexes AS i
ON kc.parent_object_id = i.[object_id]
AND kc.unique_index_id = i.index_id
WHERE
kc.parent_object_id = @object_id
AND kc.[type] = 'pk';
SET @sql = N'ALTER TABLE ' + QUOTENAME(@TableName)
+ ' ADD CONSTRAINT ' + @pkName
+ ' PRIMARY KEY ' + CASE @clustered
WHEN 1 THEN 'CLUSTERED' ELSE '' END + ' (';
SELECT
@sql = @sql + c.name + ','
FROM
sys.index_columns AS ic
INNER JOIN
sys.indexes AS i
ON ic.index_id = i.index_id
AND ic.[object_id] = i.[object_id]
INNER JOIN
sys.key_constraints AS kc
ON i.[object_id] = kc.[parent_object_id]
AND kc.unique_index_id = i.index_id
INNER JOIN
sys.columns AS c
ON i.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE
kc.[type] = 'PK'
AND kc.parent_object_id = @object_id
ORDER BY key_ordinal;
SET @sql = LEFT(@sql, LEN(@sql) - 1) + ');';
SELECT COALESCE(@sql, ' ');
END
GO
As for the index creation script, I think there is a better way to do this (again without explicit cursors, not that avoiding the cursor is the goal, but the code is going to be a LOT cleaner). First you need a function to build either key or include columns from the index:
CREATE FUNCTION dbo.BuildIndexColumns
(
@object_id INT,
@index_id INT,
@included_columns BIT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @s NVARCHAR(MAX);
SELECT @s = N'';
SELECT @s = @s + c.name + CASE ic.is_descending_key
WHEN 1 THEN ' DESC' ELSE '' END + ','
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c
ON ic.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE c.[object_id] = @object_id
AND ic.[object_id] = @object_id
AND ic.index_id = @index_id
AND ic.is_included_column = @included_columns
ORDER BY ic.key_ordinal;
IF @s > N''
SET @s = LEFT(@s, LEN(@s)-1);
RETURN (NULLIF(@s, N''));
END
GO
With that function in place, a ScriptIndexes procedure is pretty easy:
CREATE PROCEDURE dbo.ScriptIndexesForTable
@TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@sql NVARCHAR(MAX),
@object_id INT;
SELECT @sql = N'', @object_id = OBJECT_ID(UPPER(@TableName));
SELECT @sql = @sql + 'CREATE '
+ CASE i.is_unique WHEN 1 THEN 'UNIQUE ' ELSE '' END
+ CASE i.[type] WHEN 1 THEN 'CLUSTERED ' ELSE '' END
+ ' INDEX ' + i.name + ' ON ' + QUOTENAME(@TableName) + ' ('
+ dbo.BuildIndexColumns(@object_id, i.index_id, 0)
+ ')' + COALESCE(' INCLUDE('
+ dbo.BuildIndexColumns(@object_id, i.index_id, 1)
+ ')', '') + ';' + CHAR(13) + CHAR(10)
FROM
sys.indexes AS i
WHERE
i.[object_id] = @object_id
-- since this will be covered by ScriptPKForTable:
AND i.is_primary_key = 0
ORDER BY i.index_id;
SELECT COALESCE(@sql, ' ');
END
GO
Note that my solution does not assume the PK is clustered (your PK script hard-codes CLUSTERED but then your index script assumes that any of the indexes could be clustered). I also ignore additional properties such as filegroup, partitioning, or filtered indexes (not supported in 2005 anyway).