Создание динамических таблиц в T-SQL - PullRequest
0 голосов
/ 07 марта 2011

наша команда разработчиков добавляет определенные пользователем поля во многие из наших таблиц.Иногда мне нужно дублировать таблицу для создания новой компании в нашей базе данных.

Я использовал для копирования каждой таблицы во временную таблицу с помощью предложения where, выбирая одну из наших компаний, которая является ближайшейсвязанные с новой компанией.Затем я обновил бы временную таблицу новым номером компании, а затем закачал временную таблицу обратно в исходную таблицу.Мне нужно было каждое имя поля, чтобы сделать это.Поскольку мы, кажется, делаем это довольно часто, и имена полей меняются (добавляются новые), я пытаюсь создать процедуру хранения, которая будет более динамичной.После долгих исследований я разработал следующую процедуру.Но я получаю эту ошибку на последнем шаге:

Сообщение 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

Ответы [ 3 ]

1 голос
/ 09 марта 2011

Это полностью непроверенный, собранный из некоторых других сценариев, которые я использовал, но я верю, что это либо сработает. Назовите его просто именем таблицы, чтобы увидеть пример сценария создания таблицы, который он сгенерирует. Установите бит 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
1 голос
/ 07 марта 2011

вы должны использовать динамический sql для оператора вставки

EXEC ('INSERT INTO HQCO( ' + @BuildStatement + ' )
SELECT ' + @BuildStatement + ' FROM #HQCO')

Кроме того, вы должны определить временную таблицу #Fields следующим образом:

create table #Fields(
    FieldName sysname null
) 
0 голосов
/ 07 марта 2011

Нельзя указывать имя параметра (@BuildStatement) в качестве заполнителя для имени столбца в инструкции INSERT (вставить в HQCO (@BuildStatement)).Вам потребуется использовать параметр @BuildStatement для динамического построения строки запроса и ее выполнения с помощью sp_executesql.

Например:

/*...Do Stuff...*/

DECLARE @sql varchar(MAX);

SET @sql = 'INSERT INTO HQCO( ' + @BuildStatement + ') SELECT ' + @BuildStatement + ' FROM #HQCO';

EXEC sp_executesql(@sql);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...