Tsql T-SQL中的动态表创建

Tsql T-SQL中的动态表创建,tsql,Tsql,我们的开发团队将用户定义的字段添加到许多表中。有时,我需要复制一个表,以便在数据库中创建新公司 我曾经使用where子句将每个表复制到临时表中,选择与新公司关系最密切的公司之一。然后,我会用新的公司编号更新临时表,然后将临时表抽回到原始表中。我需要每个字段名来执行此操作。 因为我们似乎经常这样做,并且字段名会更改,新添加的字段名也会更改,所以我正在尝试创建一个更具动态性的存储过程。 经过大量研究,我制定了以下程序。但我在最后一步中不断遇到这个错误: Msg 215,16级,状态1,第100行 为

我们的开发团队将用户定义的字段添加到许多表中。有时,我需要复制一个表,以便在数据库中创建新公司

我曾经使用where子句将每个表复制到临时表中,选择与新公司关系最密切的公司之一。然后,我会用新的公司编号更新临时表,然后将临时表抽回到原始表中。我需要每个字段名来执行此操作。 因为我们似乎经常这样做,并且字段名会更改,新添加的字段名也会更改,所以我正在尝试创建一个更具动态性的存储过程。 经过大量研究,我制定了以下程序。但我在最后一步中不断遇到这个错误:

Msg 215,16级,状态1,第100行 为对象“HQCO”提供的参数 这不是一个函数。如果 参数将用作表格 提示,需要WITH关键字

这里的任何帮助都将不胜感激。 我已经验证了变量@BuildStatement具有带有逗号分隔符的实际字段名。我只想用这个变量来代替列出我的字段名

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