Как удалить свойство IDENTITY столбца в SQL Server 2005 - PullRequest
9 голосов
/ 03 апреля 2009

Я хочу иметь возможность вставлять данные из таблицы со столбцом идентификации во временную таблицу в SQL Server 2005.

TSQL выглядит примерно так:

-- Create empty temp table
SELECT *
INTO #Tmp_MyTable
FROM MyTable
WHERE 1=0
...
WHILE ...
BEGIN
    ...
    INSERT INTO #Tmp_MyTable
    SELECT TOP (@n) *
    FROM MyTable
    ...

END

Приведенный выше код создал #Tmp_Table со столбцом идентификаторов, а вставка впоследствии завершается с ошибкой «Явное значение для столбца идентификаторов в таблице« #Tmp_MyTable »можно указывать только в том случае, если используется список столбцов, а IDENTITY_INSERT установлен в ON . "

Есть ли в TSQL способ удалить свойство идентификации столбца во временной таблице без явного перечисления всех столбцов ? Я специально хочу использовать «SELECT *», чтобы код продолжал работать, если в MyTable будут добавлены новые столбцы.

Я полагаю, что удаление и воссоздание столбца изменит его положение, делая невозможным использование SELECT *.

Обновление:

Я пытался использовать IDENTITY_INSERT, как предложено в одном ответе. Это не работает - см. Репродукцию ниже. Что я делаю не так?

-- Create test table
CREATE TABLE [dbo].[TestTable](
    [ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
    [Name] [varchar](50) NULL,
 CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)
) 
GO
-- Insert some data
INSERT INTO TestTable
(Name)
SELECT 'One'
UNION ALL
SELECT 'Two'
UNION ALL
SELECT 'Three'
GO
-- Create empty temp table
SELECT *
INTO #Tmp
FROM TestTable
WHERE 1=0

SET IDENTITY_INSERT #Tmp ON -- I also tried OFF / ON
INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

SET IDENTITY_INSERT #Tmp OFF 
GO
-- Drop test table
DROP TABLE [dbo].[TestTable]
GO

Обратите внимание, что сообщение об ошибке "Явное значение для столбца идентификаторов в таблице '#TmpMyTable' может быть указано только , когда используется список столбцов и IDENTITY_INSERT включен." - Я специально не хочу использовать список столбцов, как описано выше.

Обновление 2 Попробовал предложение от Майка , но это дало ту же ошибку:

-- Create empty temp table
SELECT *
INTO #Tmp
FROM (SELECT
      m1.*
      FROM TestTable                 m1
          LEFT OUTER JOIN TestTable  m2 ON m1.ID=m2.ID
      WHERE 1=0
 ) dt

INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

Что касается того, почему я хочу сделать это: MyTable - это промежуточная таблица, которая может содержать большое количество строк, которые нужно объединить в другую таблицу. Я хочу обработать строки из промежуточной таблицы, вставить / обновить основную таблицу и удалить их из промежуточной таблицы в цикле, который обрабатывает N строк на транзакцию. Я понимаю, что есть и другие способы достичь этого.

Обновление 3

Я не смог заставить решение Майка работать, однако он предложил следующее решение, которое действительно работает: префикс с не идентифицирующим столбцом и удаление столбца идентификаторов:

SELECT CAST(1 AS NUMERIC(18,0)) AS ID2, *
INTO #Tmp
FROM TestTable
WHERE 1=0
ALTER TABLE #Tmp DROP COLUMN ID

INSERT INTO #Tmp
SELECT TOP 1 * FROM TestTable

Предложение Майка хранить только ключи во временной таблице также является хорошим, хотя в этом конкретном случае есть причины, по которым я предпочитаю иметь все столбцы во временной таблице.

Ответы [ 5 ]

7 голосов
/ 03 апреля 2009

Вы можете попробовать

SET IDENTITY_INSERT #Tmp_MyTable ON 
-- ... do stuff
SET IDENTITY_INSERT #Tmp_MyTable OFF

Это позволит вам выбрать #Tmp_MyTable, даже если у него есть столбец идентификаторов.

Но это не будет работать:

-- Create empty temp table
SELECT *
INTO #Tmp_MyTable
FROM MyTable
WHERE 1=0
...
WHILE ...
BEGIN
    ...
    SET IDENTITY_INSERT #Tmp_MyTable ON 

    INSERT INTO #Tmp_MyTable
    SELECT TOP (@n) *
    FROM MyTable

    SET IDENTITY_INSERT #Tmp_MyTable OFF 
    ...    
END

(приводит к ошибке «Явное значение для столбца идентификаторов в таблице« #Tmp »можно указывать только в том случае, если используется список столбцов, а IDENTITY_INSERT включен.))

Кажется, что нет способа без реального удаления столбца, но это изменило бы порядок столбцов, как упоминалось в OP. Гадкий взлом: создать новую таблицу на основе #Tmp_MyTable ...

Я предлагаю вам написать хранимую процедуру, которая создает временную таблицу на основе имени таблицы (MyTable) с теми же столбцами (по порядку), но с отсутствующим свойством идентификатора.

Вы можете использовать следующий код:

select t.name as tablename, typ.name as typename, c.*
from sys.columns c inner join
     sys.tables t on c.object_id = t.[object_id] inner join
     sys.types typ on c.system_type_id = typ.system_type_id
order by t.name, c.column_id

, чтобы получить представление о том, как работает отражение в TSQL. Я полагаю, что вам придется циклически перебирать столбцы для рассматриваемой таблицы и выполнять динамические (созданные вручную, хранящиеся в строках и затем оцененные) операторы изменения в сгенерированной таблице.

Не могли бы вы опубликовать такую ​​хранимую процедуру для остального мира? Этот вопрос, похоже, часто возникает и на других форумах ...

3 голосов
/ 03 апреля 2009

Если вы просто обрабатываете строки, как вы описываете, было бы лучше просто выбрать верхние значения N первичного ключа во временную таблицу, например:

CREATE TABLE #KeysToProcess
(
     TempID    int  not null primary key identity(1,1)
    ,YourKey1  int  not null
    ,YourKey2  int  not null
)

INSERT INTO #KeysToProcess (YourKey1,YourKey2)
SELECT TOP n YourKey1,YourKey2  FROM MyTable

Ключи не должны меняться очень часто (я надеюсь), но другие столбцы могут без вреда делать это таким образом.

получите @@ ROWCOUNT вставки, и вы можете сделать простой цикл для TempID, где он будет от 1 до @@ ROWCOUNT

и / или

Просто присоедините #KeysToProcess к своей таблице MyKeys и отправляйтесь в путь, без необходимости дублировать все данные.

Это нормально работает на моем SQL Server 2005, где MyTable.MyKey - это столбец идентификаторов.

-- Create empty temp table
SELECT *
INTO #TmpMikeMike
FROM (SELECT
      m1.*
      FROM MyTable                 m1
          LEFT OUTER JOIN MyTable  m2 ON m1.MyKey=m2.MyKey
      WHERE 1=0
 ) dt

INSERT INTO #TmpMike
SELECT TOP 1 * FROM MyTable

SELECT * from #TmpMike



EDIT
ЭТО РАБОТАЕТ, без ошибок ...

-- Create empty temp table
SELECT *
INTO #Tmp_MyTable
FROM (SELECT
          m1.*
          FROM MyTable                 m1
              LEFT OUTER JOIN MyTable  m2 ON m1.KeyValue=m2.KeyValue
          WHERE 1=0
     ) dt
...
WHILE ...
BEGIN
    ...
    INSERT INTO #Tmp_MyTable
    SELECT TOP (@n) *
    FROM MyTable
    ...

END

однако, в чем ваша настоящая проблема? Почему вам нужно выполнить цикл при вставке «*» в эту временную таблицу? Возможно, вам удастся изменить стратегию и придумать гораздо лучший алгоритм в целом.

1 голос
/ 03 апреля 2009

РЕДАКТИРОВАТЬ Переключение IDENTITY_INSERT, как предложено Дареном, безусловно, является более элегантным подходом, в моем случае мне нужно было удалить столбец идентификаторов, чтобы я мог повторно вставить выбранные данные в исходную таблицу

Способ, которым я обратился к этому, состоял в том, чтобы создать временную таблицу точно так же, как вы, явно отбросить столбец идентификаторов, а затем динамически построить sql, чтобы у меня был список столбцов, исключающий столбец идентификаторов (как в вашем случае так процесс все равно будет работать, если в схему внесены изменения), а затем выполнить sql, вот пример

declare @ret int
Select * into #sometemp from sometable
Where
id = @SomeVariable

Alter Table #sometemp Drop column SomeIdentity 

Select @SelectList = ''
Select @SelectList = @SelectList 
+ Coalesce( '[' + Column_name + ']' + ', ' ,'')
from information_schema.columns
where table_name = 'sometable'
and Column_Name <> 'SomeIdentity'

Set @SelectList = 'Insert into sometable (' 
+ Left(@SelectList, Len(@SelectList) -1) + ')'
Set @SelectList = @SelectList 
+ ' Select * from #sometemp '
exec @ret  =  sp_executesql  @selectlist
0 голосов
/ 21 апреля 2014

Наиболее эффективный способ удаления столбцов идентификаторов (особенно для больших баз данных) на SQL Server - это непосредственное изменение метаданных DDL, на SQL Server старше 2005 года это можно сделать с помощью:

sp_configure 'allow update', 1
go
reconfigure with override
go

update syscolumns set colstat = 0 --turn off bit 1 which indicates identity column
where id = object_id('table_name') and name = 'column_name'
go

exec sp_configure 'allow update', 0
go
reconfigure with override
go

SQL Server 2005+ не поддерживает переконфигурирование с переопределением , но вы можете выполнять специальные запросы, когда экземпляр SQL Server запускается в однопользовательском режиме (запускать экземпляр db с -m * Флаг 1007 *, т. Е. «C: \ Program Files \ Microsoft SQL Server \ MSSQL10.MSSQLSERVER \ MSSQL \ Binn \ sqlservr.exe -m», убедитесь, что он запускается от имени администратора) с выделенной консолью администратора (из SQL Management Studio подключитесь с помощью *). 1008 * ADMIN: префикс, то есть ADMIN: MyDatabase). Метаданные столбца хранятся во внутренней таблице sys.sysschobjs (не отображается без DAC):

use myDatabase

update sys.syscolpars set status = 1, idtval = null -- status=1 - primary key, idtval=null - remove identity data
where id = object_id('table_name') AND name = 'column_name' 

Подробнее об этом подходе в этом блоге

0 голосов
/ 12 сентября 2013

Я написал эту процедуру как компиляцию множества ответов для автоматического и быстрого удаления идентификатора столбца:

CREATE PROCEDURE dbo.sp_drop_table_identity @tableName VARCHAR(256) AS
BEGIN
    DECLARE @sql VARCHAR (4096);
    DECLARE @sqlTableConstraints VARCHAR (4096);
    DECLARE @tmpTableName VARCHAR(256) = @tableName + '_noident_temp';

    BEGIN TRANSACTION

    -- 1) Create temporary table with edentical structure except identity
    -- Idea borrowed from /6750/v-sql-server-kak-mne-sgenerirovat-operator-create-table-dlya-dannoi-tablitsy
    -- modified to ommit Identity and honor all constraints, not primary key only!
    SELECT
        @sql = 'CREATE TABLE [' + so.name + '_noident_temp] (' + o.list + ')'
        + ' ' + j.list
    FROM sysobjects so
    CROSS APPLY (
        SELECT
            ' [' + column_name + '] '
            + data_type
            + CASE data_type
                WHEN 'sql_variant' THEN ''
                WHEN 'text' THEN ''
                WHEN 'ntext' THEN ''
                WHEN 'xml' THEN ''
                WHEN 'decimal' THEN '(' + CAST(numeric_precision as VARCHAR) + ', ' + CAST(numeric_scale as VARCHAR) + ')'
                ELSE COALESCE('(' + CASE WHEN character_maximum_length = -1 THEN 'MAX' ELSE CAST(character_maximum_length as VARCHAR) END + ')', '')
            END
            + ' '
            /* + case when exists ( -- Identity skip
            select id from syscolumns
            where object_name(id)=so.name
            and name=column_name
            and columnproperty(id,name,'IsIdentity') = 1
            ) then
            'IDENTITY(' +
            cast(ident_seed(so.name) as varchar) + ',' +
            cast(ident_incr(so.name) as varchar) + ')'
            else ''
            end + ' ' */
            + CASE WHEN IS_NULLABLE = 'No' THEN 'NOT ' ELSE '' END
            + 'NULL'
            + CASE WHEN information_schema.columns.column_default IS NOT NULL THEN ' DEFAULT ' + information_schema.columns.column_default ELSE '' END
            + ','
        FROM
            INFORMATION_SCHEMA.COLUMNS
        WHERE table_name = so.name
        ORDER BY ordinal_position
        FOR XML PATH('')
    ) o (list)
    CROSS APPLY(
        SELECT
            CHAR(10) + 'ALTER TABLE ' + @tableName + '_noident_temp ADD ' + LEFT(alt, LEN(alt)-1)
        FROM(
            SELECT
                CHAR(10)
                + ' CONSTRAINT ' + tc.constraint_name  + '_ni ' + tc.constraint_type + ' (' + LEFT(c.list, LEN(c.list)-1) + ')'
                + COALESCE(CHAR(10) + r.list, ', ')
            FROM
                information_schema.table_constraints tc
                CROSS APPLY(
                    SELECT
                        '[' + kcu.column_name + '], '
                    FROM
                        information_schema.key_column_usage kcu
                    WHERE
                        kcu.constraint_name = tc.constraint_name
                    ORDER BY
                        kcu.ordinal_position
                    FOR XML PATH('')
                ) c (list)
                OUTER APPLY(
                    -- https://stackoverflow.com/questions/3907879/sql-server-howto-get-foreign-key-reference-from-information-schema
                    SELECT
                        '  REFERENCES [' + kcu1.constraint_schema + '].' + '[' + kcu2.table_name + ']' + '([' + kcu2.column_name + ']) '
                        + CHAR(10)
                        + '    ON DELETE ' + rc.delete_rule
                        + CHAR(10)
                        + '    ON UPDATE ' + rc.update_rule + ', '
                    FROM information_schema.referential_constraints as rc
                        JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                        JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
                    WHERE
                        kcu1.constraint_catalog = tc.constraint_catalog AND kcu1.constraint_schema = tc.constraint_schema AND kcu1.constraint_name = tc.constraint_name
                ) r (list)
            WHERE tc.table_name = @tableName
            FOR XML PATH('')
        ) a (alt)
    ) j (list)
    WHERE
        xtype = 'U'
    AND name NOT IN ('dtproperties')
    AND so.name = @tableName

    SELECT @sql as '1) @sql';
    EXECUTE(@sql);

    -- 2) Obtain current back references on our table from others to reenable it later
    -- https://stackoverflow.com/questions/3907879/sql-server-howto-get-foreign-key-reference-from-information-schema
    SELECT
        @sqlTableConstraints = (
            SELECT
                'ALTER TABLE [' + kcu1.constraint_schema + '].' + '[' + kcu1.table_name + ']'
                + ' ADD CONSTRAINT ' + kcu1.constraint_name + '_ni FOREIGN KEY ([' + kcu1.column_name + '])'
                + CHAR(10)
                + '  REFERENCES ['  + kcu2.table_schema + '].[' + kcu2.table_name + ']([' + kcu2.column_name + '])'
                + CHAR(10)
                + '    ON DELETE ' + rc.delete_rule
                + CHAR(10)
                + '    ON UPDATE ' + rc.update_rule + ' '
            FROM information_schema.referential_constraints as rc
                JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
            WHERE
                kcu2.table_name = 'department'
            FOR XML PATH('')
        );
    SELECT @sqlTableConstraints as '8) @sqlTableConstraints';
    -- Execute at end

    -- 3) Drop outer references for switch (structure must be identical: http://msdn.microsoft.com/en-gb/library/ms191160.aspx) and rename table
    SELECT
        @sql = (
            SELECT
                ' ALTER TABLE [' + kcu1.constraint_schema + '].' + '[' + kcu1.table_name + '] DROP CONSTRAINT ' + kcu1.constraint_name
            FROM information_schema.referential_constraints as rc
                JOIN information_schema.key_column_usage as kcu1 ON (kcu1.constraint_catalog = rc.constraint_catalog AND kcu1.constraint_schema = rc.constraint_schema AND kcu1.constraint_name = rc.constraint_name)
                JOIN information_schema.key_column_usage as kcu2 ON (kcu2.constraint_catalog = rc.unique_constraint_catalog AND kcu2.constraint_schema = rc.unique_constraint_schema AND kcu2.constraint_name = rc.unique_constraint_name AND kcu2.ordinal_position = KCU1.ordinal_position)
            WHERE
                kcu2.table_name = @tableName
            FOR XML PATH('')
        );
    SELECT @sql as '3) @sql'
    EXECUTE (@sql);

    -- 4) Switch partition
    -- http://www.calsql.com/2012/05/removing-identity-property-taking-more.html
    SET @sql = 'ALTER TABLE ' + @tableName + ' switch partition 1 to ' + @tmpTableName;
    SELECT @sql as '4) @sql';
    EXECUTE(@sql);

    -- 5) Rename real old table to bak
    SET @sql = 'EXEC sp_rename ' + @tableName + ', ' + @tableName + '_bak';
    SELECT @sql as '5) @sql';
    EXECUTE(@sql);

    -- 6) Rename temp table to real
    SET @sql = 'EXEC sp_rename ' + @tmpTableName + ', ' + @tableName;
    SELECT @sql as '6) @sql';
    EXECUTE(@sql);

    -- 7) Drop bak table
    SET @sql = 'DROP TABLE ' + @tableName + '_bak';
    SELECT @sql as '7) @sql';
    EXECUTE(@sql);

    -- 8) Create again doped early constraints
    SELECT @sqlTableConstraints as '8) @sqlTableConstraints';
    EXECUTE(@sqlTableConstraints);


    -- It still may fail if there references from objects with WITH CHECKOPTION
    -- it may be recreated - https://stackoverflow.com/questions/1540988/sql-2005-force-table-rename-that-has-dependencies
    COMMIT
END

Использовать довольно просто:

EXEC sp_drop_table_identity @tableName = 'some_very_big_table'

Преимущества и ограничения:

  1. Используется switch partition (применимо также и к недеформированным таблицам) для быстрого перемещения без полной копии данных . Также применяются некоторые условия применимости.
  2. Это делает на лету копию таблицы без удостоверения личности. Такое решение я также публикую отдельно , и оно также может нуждаться в настройке не очень тривиальных структур, таких как составные поля (оно покрывает мои потребности).
  3. Если таблица включена в объекты со схемой, связанной с CHECKOUPTION (sp, views), это предотвращает переключение (см. Последний комментарий в коде). Это может быть дополнительно написано для временного удаления такой привязки. Я еще этого не делал.

Все отзывы приветствуются.

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