Tsql T-SQL中的动态表创建
我们的开发团队将用户定义的字段添加到许多表中。有时,我需要复制一个表,以便在数据库中创建新公司 我曾经使用where子句将每个表复制到临时表中,选择与新公司关系最密切的公司之一。然后,我会用新的公司编号更新临时表,然后将临时表抽回到原始表中。我需要每个字段名来执行此操作。 因为我们似乎经常这样做,并且字段名会更改,新添加的字段名也会更改,所以我正在尝试创建一个更具动态性的存储过程。 经过大量研究,我制定了以下程序。但我在最后一步中不断遇到这个错误: Msg 215,16级,状态1,第100行 为对象“HQCO”提供的参数 这不是一个函数。如果 参数将用作表格 提示,需要WITH关键字 这里的任何帮助都将不胜感激。 我已经验证了变量@BuildStatement具有带有逗号分隔符的实际字段名。我只想用这个变量来代替列出我的字段名Tsql T-SQL中的动态表创建,tsql,Tsql,我们的开发团队将用户定义的字段添加到许多表中。有时,我需要复制一个表,以便在数据库中创建新公司 我曾经使用where子句将每个表复制到临时表中,选择与新公司关系最密切的公司之一。然后,我会用新的公司编号更新临时表,然后将临时表抽回到原始表中。我需要每个字段名来执行此操作。 因为我们似乎经常这样做,并且字段名会更改,新添加的字段名也会更改,所以我正在尝试创建一个更具动态性的存储过程。 经过大量研究,我制定了以下程序。但我在最后一步中不断遇到这个错误: Msg 215,16级,状态1,第100行 为
USE [Viewpoint]
GO
/****** Object: StoredProcedure [dbo].[udCreateNewCompany] Script Date: 03/04/2011 09:17:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*****NOTE*******************************/
/*You must grant permission to a stored procedure
Grant all on dbo.udCreateNewCompany to public */
CREATE proc [dbo].[udCreateNewCompany]
@OldCo bCompany,
@NewCo bCompany
as
set nocount on
-- create #tmp file to hold sp_columns values
Create table #tmp (
TABLE_QUALIFIER varchar(40),
TABLE_OWNER varchar(20),
TABLE_NAME varchar(40),
COLUMN_NAME varchar(40),
DATA_TYPE int,
TYPE_NAME varchar(20),
PREC int, LENGTH int,
SCALE int, RADIX int,
NULLABLE char(4),
REMARKS varchar(128),
COLUMN_DEF varchar(40),
SQL_DATA_TYPE int,
SQL_DATETIME_SUB int,
CHAR_OCTET_LENGTH int,
ORDINAL_POSITION int,
IS_NULLABLE char(4),
SS_DATA_TYPE int)
--**************************************************************
-- create #Fields to hold field names
create table #Fields(
FieldName char(40) null
)
-- HQCO Headquarters Company
-- create a temporary table of HQCO values
select * into #HQCO
from HQCO
where HQCo=@OldCo
--update temporary file with new company number
update #HQCO
set HQCo=@NewCo
Set nocount on
-- create a file of all the field name for the HQCO table
Insert #tmp Exec sp_columns HQCO
-- delete the system key field
delete #tmp where COLUMN_NAME='KeyID'
declare @ColumnName as char(40)
declare @BuildStatement as varchar(8000)
set @BuildStatement = ' '
-- LOOP through the #tmp file to read the column names to build a "@BuildStatement"
DECLARE Mycursor cursor forward_only read_only FOR
SELECT COLUMN_NAME
FROM #tmp
open Mycursor
fetch next from Mycursor into
/* load field value(s) retrieved from table into variable(s) */
@ColumnName
while @@fetch_status=0
begin
set @BuildStatement = rtrim(@BuildStatement) + rtrim(@ColumnName) + ','
insert into #Fields(FieldName)
select @ColumnName
fetch next from Mycursor into
/* load field value(s) retrieved from table into variable(s) */
@ColumnName
end
-- @BuildStatement looks something like this HQCo,Name,Address,
-- we need to remove that last comma
create table #Holdit(
FieldList varchar(8000) null
)
insert into #Holdit(FieldList)
select @BuildStatement
declare @LastComma as numeric(4,0)
set @LastComma=(select LEN(@BuildStatement) -1)
set @BuildStatement=SUBSTRING(@BuildStatement,1,@LastComma)
insert into HQCO( @BuildStatement )
select @BuildStatement from #HQCO
GO
必须对insert语句使用动态sql
EXEC ('INSERT INTO HQCO( ' + @BuildStatement + ' )
SELECT ' + @BuildStatement + ' FROM #HQCO')
此外,还必须按如下方式定义临时表字段:
create table #Fields(
FieldName sysname null
)
在插入HQCO@BuildStatement的INSERT语句中,不能提供参数名@BuildStatement作为列名的占位符。您需要使用@BuildStatement参数动态构造查询字符串,并使用sp_executesql执行它 例如:
/*...Do Stuff...*/
DECLARE @sql varchar(MAX);
SET @sql = 'INSERT INTO HQCO( ' + @BuildStatement + ') SELECT ' + @BuildStatement + ' FROM #HQCO';
EXEC sp_executesql(@sql);
这是完全未经测试的,是从其他一些脚本拼凑而成的,但我相信这两者都会起作用。仅使用表名调用它,以查看它将生成的表创建脚本的示例。将exec位设置为1以实际创建表
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[pr_CreateAndExecCopyTableScript] (
@TableName VARCHAR(255) = ''
, @TableNameExt VARCHAR(10) = '_Copy'
, @DisplayScript BIT = 1
, @Exec BIT = 0
, @NoPK BIT = 0
, @PKOnly BIT = 0
, @NoIndexes BIT = 1
, @NoTable BIT = 0)
AS
SET NOCOUNT ON
--Test for empty entry
IF @TableName = ''
BEGIN
PRINT '@TableName is a required parameter.'
RETURN 1
END
--Test for source table
IF NOT EXISTS ( SELECT *
FROM sysobjects
WHERE id = OBJECT_ID(N'[dbo].[' + @TableName + ']')
AND OBJECTPROPERTY(id, N'IsUserTable') = 1 )
BEGIN
PRINT 'Table ' + @TableName + ' not found.'
RETURN 2
END
--End invalid entries for parameters section
DECLARE @Query VARCHAR(MAX)
, @DFQuery VARCHAR(MAX)
SET @Query = ''
SET @DFQuery = ''
--Begin creating temp tables
--temp table #TableScript is used to gather data needed to generate script that will create the table
IF @NoTable = 0
AND @PKOnly = 0
CREATE TABLE #TableScript (
ColumnName VARCHAR(128)
, DataType VARCHAR(40)
, Length VARCHAR(4)
, [Precision] VARCHAR(4)
, Scale VARCHAR(4)
, IsNullable VARCHAR(1)
, TableName VARCHAR(128)
, ConstraintName VARCHAR(255)
, DefaultValue VARCHAR(255)
, GroupName VARCHAR(35)
, collation SYSNAME NULL
, IdentityColumn BIT NULL
, ColOrder INT)
--temp table #IndexScript is used to gather data needed to generate script that will create indexes for table
CREATE TABLE #IndexScript (
IndexName VARCHAR(255)
, IndId INT
, ColumnName VARCHAR(255)
, IndKey INT
, UniqueIndex INT)
--End creating temp tables
--Begin filling temp table #TableScript
IF @NoTable = 0
AND @PKOnly = 0
BEGIN
INSERT INTO #TableScript (
ColumnName
, DataType
, Length
, [Precision]
, Scale
, IsNullable
, TableName
, ConstraintName
, DefaultValue
, GroupName
, collation
, IdentityColumn
, ColOrder)
SELECT c.name AS ColumnName
, t.name AS DataType
, CASE t.length
WHEN 8000 THEN c.prec --This criteria used because Enterprise Manager delivers the length in parenthesis for these datatypes when using its scripting capabilities.
ELSE NULL
END AS Length
, CASE t.name
WHEN 'numeric' THEN c.prec
WHEN 'decimal' THEN c.prec
ELSE NULL
END AS [Precision]
, CASE t.name
WHEN 'numeric' THEN c.scale
WHEN 'decimal' THEN c.scale
ELSE NULL
END AS Scale
, c.isnullable
, o.name AS TableName
, d.name AS ConstraintName
, cm.text AS DefaultValue
, g1a.groupname
, c.collation
, CASE WHEN c.autoval IS NULL THEN 0
ELSE 1
END AS IdentityColumn
, ColOrder
FROM syscolumns c
INNER JOIN sysobjects o
ON c.id = o.id
LEFT JOIN systypes t
ON t.xusertype = c.xusertype --the first three joins get column names, data types, and column nullability.
LEFT JOIN sysobjects d
ON c.cdefault = d.id --this left join gets column default constraint names.
LEFT JOIN syscomments cm
ON cm.id = d.id --this left join gets default values for default constraints.
LEFT JOIN sysindexes g1
ON g1.id = o.id --the left join for sysfilegroups and sysindexes with aliases g1 and g1a
LEFT JOIN sysfilegroups g1a
ON g1.groupid = g1a.groupid --are for determining which file group the table is in.
WHERE o.name = @TableName
AND g1.id = o.id
AND g1.indid IN (0, 1) --these two conditions are to isolate the file group of the table.
--Assign file group name
DECLARE @GroupName VARCHAR(35)
SELECT DISTINCT
@GroupName = GroupName
FROM #TableScript
--Remove collation to save space. Hack to get around script failing on tables with a very large number of columns. SD
UPDATE #TableScript
SET collation = NULL
--Set TimeStamp columns to bigint, you can't set the value of a timestamp column manually.
UPDATE #TableScript
SET DataType = 'BigInt'
WHERE DataType = 'TimeStamp'
END
--End filling temp table #TableScript
--Begin building create table and default value constraints scripts.
IF @NoTable = 0
AND @PKOnly = 0
BEGIN
SET @Query = 'if exists (select * from sysobjects where id = object_id(N' + '''[dbo].[' + @TableName
+ @TableNameExt + ']''' + ') and OBJECTPROPERTY(id, N' + '''IsUserTable''' + ') = 1)' + CHAR(10)
+ 'drop table [dbo].[' + @TableName + @TableNameExt + ']' + CHAR(10) + 'GO' + CHAR(10) + CHAR(10)
+ 'CREATE TABLE [dbo].[' + @TableName + @TableNameExt + '] ('
DECLARE @DataType VARCHAR(40)
, @Length VARCHAR(4)
, @Precision VARCHAR(4)
, @Scale VARCHAR(4)
, @Isnullable VARCHAR(1)
, @DefaultValue VARCHAR(255)
, @ColumnName VARCHAR(255)
, @ConstraintName VARCHAR(255)
, @collation SYSNAME
, @TEXTIMAGE_ON BIT
, @IdentityColumn BIT
SET @TEXTIMAGE_ON = 0
DECLARE ColumnName CURSOR
FOR SELECT ColumnName
FROM #TableScript
ORDER BY ColOrder
OPEN ColumnName
FETCH NEXT FROM ColumnName INTO @ColumnName
WHILE (@@fetch_status = 0)
BEGIN
SELECT @DataType = DataType
, @Length = Length
, @Precision = [Precision]
, @Scale = Scale
, @Isnullable = isnullable
, @DefaultValue = DefaultValue
, @ConstraintName = ConstraintName
, @collation = collation
, @IdentityColumn = IdentityColumn
FROM #TableScript
WHERE ColumnName = @ColumnName
IF @DefaultValue IS NOT NULL
BEGIN
IF @DFQuery = ''
SET @DFQuery = @DFQuery + CHAR(10) + CHAR(10) + 'ALTER TABLE [dbo].[' + @TableName + @TableNameExt
+ '] WITH NOCHECK ADD'
SET @DFQuery = @DFQuery + CHAR(10) + CHAR(9) + 'CONSTRAINT [DF_' + @TableName + @TableNameExt + '_'
+ @ColumnName + '] DEFAULT ' + @DefaultValue + ' FOR [' + @ColumnName + '],'
END
IF @DataType = 'text'
OR @DataType = 'ntext'
SET @TEXTIMAGE_ON = 1
SET @Query = @Query + CHAR(10) + CHAR(9) + '[' + @ColumnName + '] [' + @DataType + ']'
--Disabled creating identity column. It's not needed in the Copy table.
/* IF @IdentityColumn = 1
SET @Query = @Query
+ ' IDENTITY (' + LTRIM(STR(IDENT_SEED(@TableName))) + ', ' + LTRIM(STR(IDENT_INCR(@TableName))) + ')'
*/
IF @DataType = 'varchar'
OR @DataType = 'nvarchar'
OR @DataType = 'char'
OR @DataType = 'nchar'
OR @DataType = 'varbinary'
OR @DataType = 'binary'
SET @Query = @Query + ' (' + @Length + ')'
IF @DataType = 'numeric'
OR @DataType = 'decimal'
SET @Query = @Query + ' (' + @Precision + ', ' + @Scale + ')'
IF @collation IS NOT NULL
AND @DataType <> 'sysname'
AND @DataType <> 'ProperName'
SET @Query = @Query + ' COLLATE ' + @collation
IF @Isnullable = '1'
SET @Query = @Query + ' NULL'
ELSE
SET @Query = @Query + ' NOT NULL'
FETCH NEXT FROM ColumnName INTO @ColumnName
IF @@fetch_status = 0
SET @Query = @Query + ', '
END
CLOSE ColumnName
DEALLOCATE ColumnName
SET @Query = @Query + CHAR(10) + ')'
IF @GroupName IS NOT NULL
SET @Query = @Query + ' ON [' + @GroupName + ']'
IF @TEXTIMAGE_ON = 1
SET @Query = @Query + ' TEXTIMAGE_ON [' + @GroupName + ']'
IF RIGHT(@DFQuery, 1) = ','
SET @DFQuery = LEFT(@DFQuery, LEN(@DFQuery) - 1)
SET @Query = @Query + CHAR(10) + 'GO'
END
--End building create table and default value constraints scripts.
--Begin filling temp table #IndexScript.
INSERT INTO #IndexScript (
IndexName
, IndId
, ColumnName
, IndKey
, UniqueIndex)
SELECT i.name
, i.indid
, c.name
, k.keyno
, (i.status & 2) --Learned this will identify a unique index from sp_helpindex
FROM sysindexes i
INNER JOIN sysobjects o
ON i.id = o.id
INNER JOIN sysindexkeys k
ON i.id = k.id
AND i.indid = k.indid
INNER JOIN syscolumns c
ON c.id = k.id
AND k.colid = c.colid
WHERE o.name = @TableName
AND i.indid > 0
AND i.indid < 255 --eliminates non indexes
AND LEFT(i.name, 7) <> '_WA_Sys' --eliminates statistic indexes
--End filling temp table #IndexScript.
DECLARE @PK VARCHAR(2)
, @IndID INT
, @IndexName VARCHAR(255)
, @IndKey INT
SET @PK = ''
SET @IndKey = 1
SELECT DISTINCT
@IndexName = IndexName
, @IndID = indid
FROM #IndexScript
WHERE LEFT(IndexName, 2) = 'PK'
--Begin creating primary key script.
IF @PKOnly = 1
OR (@NoTable = 1
AND @NoPK = 0)
BEGIN
SET @Query = '--Add Primary Key' + CHAR(10)
SET @PK = 'PK'
END
IF @NoPK = 0
BEGIN
IF @IndexName IS NOT NULL
BEGIN
SET @Query = @Query + CHAR(10) + CHAR(10) + 'ALTER TABLE [dbo].[' + @TableName + @TableNameExt
+ '] WITH NOCHECK ADD' + CHAR(10) + 'CONSTRAINT [PK_' + @TableName + @TableNameExt + @PK
+ '] PRIMARY KEY '
IF @IndID = 1
SET @Query = @Query + 'CLUSTERED'
ELSE
SET @Query = @Query + 'NONCLUSTERED'
SET @Query = @Query + CHAR(10) + '('
DECLARE @OldColumnName VARCHAR(255)
SET @OldColumnName = 'none_yet'
WHILE @IndKey <= 16
BEGIN
SELECT @ColumnName = ColumnName
FROM #IndexScript
WHERE IndexName = @IndexName
AND IndID = @IndID
AND IndKey = @IndKey
IF @ColumnName IS NOT NULL
AND @ColumnName <> @OldColumnName
BEGIN
SET @Query = @Query + CHAR(10) + '[' + @ColumnName + '],'
END
SET @OldColumnName = @ColumnName
SET @IndKey = @IndKey + 1
END
IF RIGHT(@Query, 1) = ','
SET @Query = LEFT(@Query, LEN(@Query) - 1)
SET @Query = @Query + CHAR(10) + ')'
--Add file group name
IF @GroupName IS NOT NULL
SET @Query = @Query + ' ON [' + @GroupName + ']'
SET @Query = @Query + CHAR(10) + 'GO'
END
END
--End creating primary key script.
--Add default value constraint script to main script.
IF @NoTable = 0
AND @PKOnly = 0
SET @Query = @Query + @DFQuery + CHAR(10) + 'GO'
--Begin building index script.
IF @NoIndexes = 0
AND @PKOnly = 0
BEGIN
IF @NoPK = 0
SET @Query = @Query + CHAR(10)
IF @NoTable = 1
SET @Query = @Query + '--Add Indexes' + CHAR(10)
ELSE
SET @Query = @Query + CHAR(10)
DECLARE @IndexNameOrig VARCHAR(255)
, @UniqueIndex INT
DECLARE IndexName CURSOR
FOR SELECT DISTINCT
IndexName
, indid
, UniqueIndex
FROM #IndexScript
WHERE LEFT(IndexName, 2) <> 'PK'
AND LEFT(IndexName, 4) <> 'hind'
OPEN IndexName
FETCH NEXT FROM IndexName INTO @IndexName, @IndID, @UniqueIndex
WHILE @@fetch_status = 0
BEGIN
SET @IndexNameOrig = @IndexName
IF RIGHT(@IndexName, 2) = 'PM'
OR RIGHT(@IndexName, 2) = 'AM'
SET @IndexName = LEFT(@IndexName, LEN(@IndexName) - 5)
IF LEFT(RIGHT(@IndexName, 10), 1) = '_'
SET @IndexName = LEFT(@IndexName, LEN(@IndexName) - 10)
ELSE
IF LEFT(RIGHT(@IndexName, 11), 1) = '_'
SET @IndexName = LEFT(@IndexName, LEN(@IndexName) - 11)
ELSE
IF LEFT(RIGHT(@IndexName, 12), 1) = '_'
SET @IndexName = LEFT(@IndexName, LEN(@IndexName) - 12)
SET @Query = @Query + CHAR(10) + 'CREATE '
IF @UniqueIndex <> 0
SET @Query = @Query + 'UNIQUE '
IF @IndID = 1
SET @Query = @Query + 'CLUSTERED '
SET @Query = @Query + 'INDEX [' + @IndexName + '] ON [dbo].[' + @TableName + @TableNameExt + ']('
SET @IndKey = 1
SET @OldColumnName = 'none_yet'
WHILE @IndKey <= 16
BEGIN
SELECT @ColumnName = ColumnName
FROM #IndexScript
WHERE IndexName = @IndexNameOrig
AND IndID = @IndID
AND IndKey = @IndKey
IF @ColumnName IS NOT NULL
AND @ColumnName <> @OldColumnName
BEGIN
SET @Query = @Query + '[' + @ColumnName + '],'
END
SET @OldColumnName = @ColumnName
SET @IndKey = @IndKey + 1
END
IF RIGHT(@Query, 1) = ','
SET @Query = LEFT(@Query, LEN(@Query) - 1)
SET @Query = @Query + ')'
--Add file group name
IF @GroupName IS NOT NULL
SET @Query = @Query + ' ON [' + @GroupName + ']'
SET @Query = @Query + CHAR(10) + 'GO' + CHAR(10)
FETCH NEXT FROM IndexName INTO @IndexName, @IndID, @UniqueIndex
END
CLOSE IndexName
DEALLOCATE IndexName
END
--End building index script.
DROP TABLE #IndexScript
IF @NoTable = 0
AND @PKOnly = 0
DROP TABLE #TableScript
IF @DisplayScript = 1
PRINT @Query
IF @Exec = 1
BEGIN
--This code needed to remark out all GO commands before executing the code in the variable @Query
SET @Query = REPLACE(@Query, CHAR(10) + 'GO', CHAR(10) + '--GO')
EXEC (@Query)
END
RETURN 0