Wie kopiere ich eine Tabelle mit SELECT INTO, ignoriere aber die IDENTITY-Eigenschaft?

41

Ich habe eine Tabelle mit Identitätsspalte sagen:

create table with_id (
 id int identity(1,1),
 val varchar(30)
);

Es ist bekannt, dass dies

select * into copy_from_with_id_1 from with_id;

ergibt copy_from_with_id_1 mit identität auf id.

In der folgenden Frage zum Stapelüberlauf werden alle Spalten explizit aufgelistet.

Lass es uns versuchen

select id, val into copy_from_with_id_2 from with_id;

Hoppla, auch in diesem Fall ist id eine Identitätsspalte.

Was ich will, ist ein Tisch wie

create table without_id (
 id int,
 val varchar(30)
);
bernd_k
quelle

Antworten:

52

Aus der Onlinedokumentation

Das Format von new_table wird durch Auswerten der Ausdrücke in der Auswahlliste bestimmt. Die Spalten in new_table werden in der Reihenfolge erstellt, die in der Auswahlliste angegeben ist. Jede Spalte in new_table hat denselben Namen, Datentyp, Nullwert und Wert wie der entsprechende Ausdruck in der Auswahlliste. Die IDENTITY-Eigenschaft einer Spalte wird außer unter den Bedingungen übertragen, die unter "Arbeiten mit Identitätsspalten" im Abschnitt "Bemerkungen" definiert sind.

Unten auf der Seite:

Wenn eine vorhandene Identitätsspalte in einer neuen Tabelle ausgewählt wird, übernimmt die neue Spalte die IDENTITY-Eigenschaft, es sei denn, eine der folgenden Bedingungen ist erfüllt:

  • Die SELECT-Anweisung enthält einen Join, eine GROUP BY-Klausel oder eine Aggregatfunktion.
  • Mehrere SELECT-Anweisungen werden mit UNION verknüpft.
  • Die Identitätsspalte wird mehrmals in der Auswahlliste aufgeführt.
  • Die Identitätsspalte ist Teil eines Ausdrucks.
  • Die Identitätsspalte stammt aus einer fernen Datenquelle.

Wenn eine dieser Bedingungen zutrifft, wird die Spalte NICHT NULL erstellt, anstatt die IDENTITY-Eigenschaft zu erben. Wenn eine Identitätsspalte in der neuen Tabelle erforderlich ist, eine solche Spalte jedoch nicht verfügbar ist oder Sie einen Startwert oder einen Inkrementwert wünschen, der sich von der Quellidentitätsspalte unterscheidet, definieren Sie die Spalte in der Auswahlliste mit der Funktion IDENTITY. Siehe "Erstellen einer Identitätsspalte mit der IDENTITY-Funktion" im folgenden Abschnitt "Beispiele".

Also ... Sie könnten theoretisch durchkommen mit:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Es wäre wichtig, diesen Code zu kommentieren, um ihn zu erklären, damit er nicht entfernt wird, wenn jemand ihn das nächste Mal ansieht.

Eric Humphrey - Lotsahelp
quelle
29

Inspiriert von Erics Antwort, fand ich die folgende Lösung, die nur von den Tabellennamen abhängt und keinen bestimmten Spaltennamen verwendet:

select * into without_id from with_id where 1 = 0
union all
select * from with_id where 1 = 0
;
insert into without_id select * from with_id;

Bearbeiten

Es ist sogar möglich, dies zu verbessern

select * into without_id from with_id
union all
select * from with_id where 1 = 0
;
bernd_k
quelle
13

Sie können einen Join verwenden, um die neue Tabelle auf einmal zu erstellen und aufzufüllen:

SELECT
  t.*
INTO
  dbo.NewTable
FROM
  dbo.TableWithIdentity AS t
  LEFT JOIN dbo.TableWithIdentity ON 1 = 0
;

Aufgrund der 1 = 0Bedingung hat die rechte Seite keine Übereinstimmungen und verhindert somit das Duplizieren der linken Seitenreihen. Da es sich um eine äußere Verknüpfung handelt, werden auch die linken Seitenreihen nicht entfernt. Da es sich um eine Verknüpfung handelt, wird die IDENTITY-Eigenschaft entfernt.

Wenn Sie daher nur die linken Seitenspalten auswählen , wird eine exakte Kopie von dbo.TableWithIdentity nur in Bezug auf die Daten erstellt, dh wenn die IDENTITY-Eigenschaft entfernt ist.

Alles , was gesagt wird, Max Vernon hat einen wichtigen Punkt in einem Kommentar angehoben, die es wert unter Berücksichtigung ist. Wenn Sie sich den Ausführungsplan der obigen Abfrage ansehen:

Ausführungsplan

Sie werden feststellen, dass die Quelltabelle nur einmal im Ausführungsplan erwähnt wird. Die andere Instanz wurde vom Optimierer entfernt.

Wenn das Optimierungsprogramm also korrekt feststellen kann, dass die rechte Seite des Joins im Plan nicht benötigt wird, sollte zu erwarten sein, dass es in einer zukünftigen Version von SQL Server möglicherweise möglich ist, herauszufinden, dass die IDENTITY-Eigenschaft nicht benötigt wird entfernt, da in der Quellzeile keine weitere IDENTITY-Spalte mehr vorhanden ist, die dem Abfrageplan entspricht. Das bedeutet, dass die obige Abfrage möglicherweise irgendwann nicht mehr wie erwartet funktioniert.

Aber, wie von ypercubeᵀᴹ richtig bemerkt , hat das Handbuch bisher explizit angegeben, dass die IDENTITY-Eigenschaft bei einem Join nicht beibehalten wird:

Wenn eine vorhandene Identitätsspalte in einer neuen Tabelle ausgewählt wird, übernimmt die neue Spalte die IDENTITY-Eigenschaft, es sei denn, die SELECT-Anweisung enthält einen Join.

So lange das Handbuch dies erwähnt, können wir uns wahrscheinlich darauf verlassen, dass das Verhalten gleich bleibt.

Ein großes Lob an Shaneis und ypercubeᵀᴹ , die im Chat ein verwandtes Thema angesprochen haben .

Andriy M
quelle
Würde das auch JOIN (SELECT 1) AS dummy ON 1 = 1funktionieren?
Ypercubeᵀᴹ
5

Versuchen Sie diesen Code ..

SELECT isnull(Tablename_old.IDENTITYCOL + 0, -1) AS 'New Identity Column'
INTO   dbo.TableName_new
FROM   dbo.TableName_old 

Der ISNULLAufruf stellt sicher, dass die neue Spalte mit NOT NULLNullfähigkeit erstellt wird.

Saurav Ghosh
quelle
1
Ist es das ISNULL()oder das +0, was es tut? Oder werden beide benötigt?
ypercubeᵀᴹ
3

Nur um einen anderen Weg zu zeigen:

Sie können einen Verbindungsserver verwenden .

SELECT * 
INTO without_id 
FROM [linked_server].[source_db].dbo.[with_id];

Sie können vorübergehend einen Verbindungsserver zum lokalen Server erstellen, indem Sie Folgendes ausführen:

DECLARE @LocalServer SYSNAME 
SET @LocalServer = @@SERVERNAME;
EXEC master.dbo.sp_addlinkedserver @server = N'localserver'
    , @srvproduct = ''
    , @provider = 'SQLNCLI'
    , @datasrc = @LocalServer;
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'localserver'
    , @useself = N'True'
    , @locallogin = NULL
    , @rmtuser = NULL
    , @rmtpassword = NULL;

Zu welchem ​​Zeitpunkt würden Sie den select * intoCode ausführen und auf den localservervierteiligen Namen des Verbindungsservers verweisen :

SELECT * 
INTO without_id 
FROM [localserver].[source_db].dbo.[with_id];

Bereinigen Sie anschließend den localserverVerbindungsserver wie folgt:

EXEC sp_dropserver @server = 'localserver'
    , @droplogins = 'droplogins';

Oder Sie könnten die OPENQUERYSyntax verwenden

SELECT * 
INTO without_id 
FROM OPENQUERY([linked_server], 'SELECT * FROM [source_db].dbo.[with_id]');
bernd_k
quelle
1

Die Identitätseigenschaft wird nicht übertragen, wenn die select-Anweisung einen Join enthält

select a.* into without_id from with_id a inner join with_id b on 1 = 0;

gibt auch das gewünschte Verhalten (der kopierten idSpalte, um die IDENTITYEigenschaft nicht beizubehalten . Es hat jedoch den Nebeneffekt, dass überhaupt keine Zeile kopiert wird! (wie bei einigen anderen Methoden), so dass Sie dann Folgendes tun müssen:

insert into without_id select * from with_id;

(Danke AakashM!)

anon-99
quelle
1

Die einfache Möglichkeit besteht darin, die Spalte als Teil eines Ausdrucks zu definieren.

Beispiel:
Wenn die Tabelle dbo.Employee eine Identität in der ID-Spalte hat, hat die temporäre Tabelle #t im folgenden Beispiel auch eine IDENTITY in der ID-Spalte.

--temp table has IDENTITY
select ID, Name 
into #t
from dbo.Employee

Ändern Sie dies, um einen Ausdruck auf ID anzuwenden, und Sie haben keine IDENTITY in der ID-Spalte mehr. In diesem Fall fügen wir der ID-Spalte einen einfachen Zusatz hinzu.

--no IDENTITY
select ID = ID + 0, Name 
into #t
from dbo.Employee

Andere Beispiele für Ausdrücke für andere Datentypen können sein: convert (), string concatenation oder Isnull ()

Faust der Wut
quelle
1
Aus docs.microsoft.com/en-us/sql/t-sql/queries/… : „Wenn eine vorhandene Identitätsspalte in einer neuen Tabelle ausgewählt wird, übernimmt die neue Spalte die IDENTITY-Eigenschaft, sofern keine der folgenden Bedingungen erfüllt ist … Die Identitätsspalte ist Teil eines Ausdrucks… die Spalte wird NICHT NULL erstellt, anstatt die IDENTITY-Eigenschaft zu erben. ”
Manngo,
1

Manchmal möchten Sie aus einer Tabelle einfügen, in der Sie nicht wissen (oder sich nicht darum kümmern), ob die Spalte mit IDENTITY erstellt wurde oder nicht. Möglicherweise handelt es sich nicht einmal um eine Ganzzahlspalte, mit der Sie arbeiten. In diesem Fall funktioniert Folgendes:

SELECT TOP(0) ISNULL([col],NULL) AS [col], ... INTO [table2] FROM [table1]
ALTER TABLE [table2] REBUILD WITH (DATA_COMPRESSION=page)
INSERT INTO [table2] ...

ISNULL löscht das IDENTITY-Attribut aus der Spalte, fügt es jedoch mit demselben Namen und Typ wie die ursprüngliche Spalte ein und macht es auch nicht nullfähig. TOP (0) erstellt eine leere Tabelle, in die Sie ausgewählte Zeilen einfügen können. Bei Bedarf können Sie die Tabelle auch komprimieren, bevor Sie Daten einfügen.

Tony
quelle
0
select convert(int, id) as id, val 
into copy_from_with_id_without_id 
from with_id;

wird Identität entfernen.

Der Nachteil ist, dass es idnullbar wird, aber Sie können diese Einschränkung hinzufügen.

John Hunter
quelle
1
Sie können ISNULL verwenden , um das zu umgehen .
Erik Darling
-2

Das tust du nicht. select * intobewahrt die Identität.

Mladen Prajdic
quelle
2
Es gab keine Anforderung in der Frage zu verwenden *.
Martin Smith
2
Und das identityEigentum bleibt nicht immer erhalten, wie andere Antworten zeigten.
ypercubeᵀᴹ