2009-04-03 3 views
9

Voglio essere in grado di inserire i dati da una tabella con una colonna di identità in una tabella temporanea in SQL Server 2005.Come cadere proprietà IDENTITY di colonna in SQL Server 2005

Il TSQL simile a:

-- 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 

il codice precedente creato #Tmp_Table con una colonna di identità, e l'inserto in seguito ha esito negativo con un errore "un valore esplicito per la colonna identity nella tabella '#Tmp_MyTable' può essere specificato solo quando un elenco di colonne viene utilizzato e IDENTITY_INSERT è acceso. "

C'è un modo in TSQL per eliminare la proprietà identity della colonna nella tabella temporanea senza elencare tutte le colonne esplicitamente? Voglio specificamente utilizzare "SELECT *" in modo che il codice continui a funzionare se vengono aggiunte nuove colonne a MyTable.

Credo che l'interruzione e la ricreazione della colonna cambieranno la sua posizione, rendendo impossibile l'utilizzo di SELECT *.

Aggiornamento:

Ho provato con IDENTITY_INSERT come suggerito in una risposta. Non funziona - vedi la riproduzione qui sotto. Che cosa sto facendo di sbagliato?

-- 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 

Si noti che il messaggio di errore "un valore esplicito per la colonna Identity nella tabella '#TmpMyTable' può essere specificato solo quando un elenco di colonne viene utilizzato e IDENTITY_INSERT è ON." - In particolare non voglio usare un elenco di colonne come spiegato sopra.

Update 2 Provato the suggestion from Mike ma questo ha dato lo stesso errore:

-- 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 

Per quanto riguarda il motivo per cui voglio fare questo: MyTable è una tabella di gestione temporanea che può contenere un gran numero di righe da unire in un altro tavolo. Voglio elaborare le righe dalla tabella di staging, inserire/aggiornare la mia tabella principale ed eliminarle dalla tabella di staging in un ciclo che elabora N righe per transazione. Mi rendo conto che ci sono altri modi per raggiungere questo obiettivo.

Update 3

non ho potuto ottenere Mike's solution al lavoro, tuttavia suggerito la soluzione seguente, che funziona: il prefisso con una colonna non-identità e cadere la colonna di identità:

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 

Anche il suggerimento di Mike di memorizzare solo le chiavi nella tabella temporanea è buono, sebbene in questo caso specifico ci siano motivi per cui preferisco avere tutte le colonne nella tabella temporanea.

+0

funziona per me, mi vedono risposta ... –

+0

soluzione È nell'aggiornamento 3 funziona benissimo per me. Stavo cercando di ottenere l'OUTPUT di un UPDATE in una tabella temporanea senza specificare le colonne (circa 100!) – apc

risposta

3

Se sono solo processando le righe come lei, non sarebbe meglio selezionare solo la parte superiore N valori di chiave primaria in una tabella temporanea come:

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 

Le chiavi non dovrebbe cambiare molto spesso (Spero) ma altre colonne non possono nuocere a farlo in questo modo.

ottenere il @@ ROWCOUNT dell'inserto e si può fare un ciclo facile su RIF dove sarà compreso tra 1 e @@ ROWCOUNT

e/o

basta aderire #KeysToProcess al vostro tavolo MyKey e sii sulla buona strada, senza bisogno di duplicare tutti i dati.

Questo funziona benissimo sul mio SQL Server 2005, in cui MyTable.MyKey è una colonna di identità.

-- 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
questo funziona, senza errori ...

-- 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 

tuttavia, qual è il tuo vero problema? Perché è necessario eseguire il ciclo durante l'inserimento di "*" in questa tabella temporanea? Potresti essere in grado di cambiare strategia e ottenere un algoritmo molto migliore in generale.

+0

Non funziona per me. Ho provato a modificare la mia riproduzione come suggerito, vedi sopra. – Joe

+0

Un'altra opzione per la query nidificate sarebbe 'Seleziona m1. * FROM MyTable m1 LEFT OUTER JOIN m2 MyTable ON 1 = 1 dove 1 = 0 ' allora non c'è bisogno di sapere nulla circa la colonna della tabella struttura. –

7

Si potrebbe provare

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

Questo vi permetterà di selezionare in #Tmp_MyTable anche se ha una colonna di identità.

Ma questo non sarà lavoro:

-- 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 

(genera l'errore "un valore esplicito per la colonna Identity nella tabella '#tmp' possono essere specificate solo se si utilizza un elenco di colonne e IDENTITY_INSERT è ON. ")

Sembra che non ci sia modo senza effettivamente rilasciare la colonna - ma ciò cambierebbe l'ordine delle colonne come OP menzionato. Ugly hack: Crea una nuova tabella basata su #Tmp_MyTable ...

Ti suggerisco di scrivere una stored procedure che crea una tabella temporanea basata sul nome di una tabella (MyTable) con le stesse colonne (in ordine), ma con il nome proprietà di identità mancante.

È possibile utilizzare seguente codice:

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 

per avere un assaggio di come riflessione lavora in TSQL. Credo che dovrai passare in rassegna le colonne per la tabella in questione ed eseguire dinamiche (costruite a mano, archiviate in stringhe e quindi valutate) alter istruzioni nella tabella generata.

Ti dispiacerebbe postare una procedura così memorizzata per il resto del mondo? Questa domanda sembra venire un bel po 'in altre sedi come pure ...

+0

non intendi il contrario? accendi prima l'inserto ... –

+0

@tvanfosson - michaelpryor ha ragione. Devi attivare identity_insert per consentire l'inserimento. Quindi, spegnilo quando hai finito. – NYSystemsAnalyst

+0

@NY - giusto, non so cosa stavo pensando. Commento precedente rimosso. – tvanfosson

1

EDIT Commutazione IDENTITY_INSERT come suggerito da Daren è sicuramente l'approccio più elegante, nel mio caso ho bisogno di eliminare la colonna di identità in modo che io è possibile reinserire i dati selezionati nella tabella di origine

Il modo in cui l'ho indirizzato era creare la tabella temporanea proprio come si fa, rilasciare esplicitamente la colonna Identity e quindi creare dinamicamente lo sql in modo da avere un elenco di colonne che esclude la colonna Identity (come nel tuo caso quindi il proc avrebbe ancora funzionato se ci fossero modifiche allo schema) e quindi eseguire lo sql ecco un esempio

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

ho scritto questa procedura come raccolta di molte risposte a automaticamente e veloce l'identità colonna goccia:

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 https://stackoverflow.com/questions/21547/in-sql-server-how-do-i-generate-a-create-table-statement-for-a-given-table 
    -- 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 

L'utilizzo è piuttosto semplice:

EXEC sp_drop_table_identity @tableName = 'some_very_big_table' 

Vantaggi e limitazioni:

  1. Utilizza l'istruzione switch partition (applicabile anche alle tabelle non partizionate) per spostamento veloce senza copia completa dei dati. Inoltre applica alcune condizioni per l'applicabilità.
  2. Realizza sul tavolo una copia senza identità. Tale soluzione I also post separately e potrebbe anche aver bisogno di accordarsi su strutture non così banali come i campi composti (copre le mie esigenze).
  3. Se la tabella è inclusa in oggetti con lo schema associato da CHECKOUPTION (sp, viste), impedisce il passaggio (vedere l'ultimo commento nel codice). Potrebbe essere inoltre programmato in modo temporaneo di eliminare tale associazione. Non l'avevo ancora fatto.

Tutte le risposte sono benvenute.

0

modo più efficace per eliminare le colonne di identità (in particolare per le grandi basi di dati) su SQL Server è quello di modificare i metadati DDL direttamente, in SQL Server 2005 più vecchio di questo può essere fatto con:

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 + non supporta riconfigurare con override, ma è possibile eseguire query ad hoc quando l'istanza di SQL Server viene avviata in modalità utente singolo (avviare istanza di db con -m flag, ad esempio "C: \ Programmi \ Microsoft SQL Server \ MSSQL10.MSSQLSERVER \ MSSQL \ Binn \ sqlservr.exe -m ", assicurarsi di eseguire come amministratore) con Admin Console dedicata (da SQL Management Studio connettersi con AMMINISTRATORE: prefisso, ad esempio ADMIN: MyDatabase). metdata Colonna è memorizzato in sys.sysschobjs tabella interna (non mostrata senza 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' 

Maggiori informazioni su questo approccio on this blog

+0

Non è necessario avviare l'istanza in modalità utente singolo e modificare le tabelle di sistema per questo. Può essere eseguito come operazione solo per i metadati con 'alter table switch' http://stackoverflow.com/questions/6084572/sql-server-how-to-set-auto-increment-after-creating-a-table-without -data-perdita/6086661 # 6086661 –