Bagaimana cara menyalin tabel dengan SELECT INTO tetapi mengabaikan properti IDENTITY?

43

Saya memiliki tabel dengan kolom identitas yang mengatakan:

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

Sudah terkenal, bahwa ini

select * into copy_from_with_id_1 from with_id;

menghasilkan copy_from_with_id_1 dengan identitas pada id juga.

Pertanyaan stack overflow berikut menyebutkan daftar semua kolom secara eksplisit.

Mari mencoba

select id, val into copy_from_with_id_2 from with_id;

Ups, bahkan dalam hal ini id adalah kolom identitas.

Yang saya inginkan adalah tabel seperti

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

Jawaban:

55

Dari Buku Daring

Format new_table ditentukan dengan mengevaluasi ekspresi dalam daftar pilih. Kolom di new_table dibuat dalam urutan yang ditentukan oleh daftar pilih. Setiap kolom di new_table memiliki nama, tipe data, nullability, dan nilai yang sama dengan ekspresi yang sesuai dalam daftar pilih. Properti IDENTITY dari suatu kolom ditransfer kecuali dalam kondisi yang ditentukan dalam "Bekerja dengan Kolom Identitas" di bagian Keterangan.

Bawah halaman:

Ketika kolom identitas yang ada dipilih ke tabel baru, kolom baru mewarisi properti IDENTITY, kecuali salah satu dari kondisi berikut ini benar:

  • Pernyataan SELECT berisi fungsi gabungan, klausa GROUP BY, atau agregat.
  • Beberapa pernyataan SELECT digabungkan dengan menggunakan UNION.
  • Kolom identitas terdaftar lebih dari satu kali dalam daftar pilih.
  • Kolom identitas adalah bagian dari ekspresi.
  • Kolom identitas berasal dari sumber data jarak jauh.

Jika salah satu dari kondisi ini benar, kolom dibuat BUKAN NULL alih-alih mewarisi properti IDENTITY. Jika kolom identitas diperlukan dalam tabel baru tetapi kolom seperti itu tidak tersedia, atau Anda ingin seed atau nilai kenaikan yang berbeda dari kolom identitas sumber, tentukan kolom dalam daftar pilih menggunakan fungsi IDENTITAS. Lihat "Membuat kolom identitas menggunakan fungsi IDENTITY" di bagian Contoh di bawah ini.

Jadi ... secara teori Anda bisa lolos dengan:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Penting untuk mengomentari kode ini untuk menjelaskannya, jangan sampai dihapus saat orang lain melihatnya.

Eric Humphrey - lotsahelp
sumber
30

Terinspirasi oleh jawaban Erics, saya menemukan solusi berikut yang hanya bergantung pada nama tabel dan tidak menggunakan nama kolom tertentu:

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;

Edit

Bahkan mungkin untuk meningkatkan ini

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

Anda dapat menggunakan gabungan untuk membuat dan mengisi tabel baru dalam sekali jalan:

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

Karena 1 = 0kondisinya, sisi kanan tidak akan memiliki kecocokan dan dengan demikian mencegah duplikasi baris sisi kiri, dan karena ini adalah sambungan luar, baris sisi kiri tidak akan dihilangkan juga. Akhirnya, karena ini adalah gabungan, properti IDENTITY dihilangkan.

Memilih hanya kolom sisi kiri, oleh karena itu, akan menghasilkan salinan tepat dbo.TableWithIdentity hanya data-bijaksana, yaitu dengan properti IDENTITY dilucuti.

Semua yang dikatakan, Max Vernon telah mengangkat poin yang valid dalam komentar yang patut diingat. Jika Anda melihat rencana eksekusi permintaan di atas:

Rencana eksekusi

Anda akan melihat bahwa tabel sumber disebutkan dalam rencana eksekusi sekali saja. Contoh lain telah dihilangkan oleh pengoptimal.

Jadi, jika pengoptimal dapat menetapkan dengan benar bahwa sisi kanan gabungan tidak diperlukan dalam paket, harus masuk akal untuk mengharapkan bahwa dalam versi SQL Server yang akan datang mungkin dapat mengetahui bahwa properti IDENTITY tidak perlu dihapus juga, karena tidak ada lagi kolom IDENTITAS di baris sumber yang diatur sesuai dengan rencana kueri. Itu berarti bahwa permintaan di atas mungkin berhenti berfungsi seperti yang diharapkan di beberapa titik.

Tetapi, sebagaimana dicatat dengan benar oleh ypercubeᵀᴹ , sejauh ini manual secara eksplisit menyatakan bahwa jika ada gabungan, properti IDENTITY tidak dilindungi:

Ketika kolom identitas yang ada dipilih ke tabel baru, kolom baru mewarisi properti IDENTITY, kecuali [...] [t] ia SELECT pernyataan berisi gabungan.

Jadi, selama manual terus menyebutkannya, kita mungkin dapat yakin bahwa perilakunya akan tetap sama.

Kudos to Shaneis dan ypercubeᵀᴹ untuk memunculkan topik terkait dalam obrolan.

Andriy M
sumber
Apakah akan JOIN (SELECT 1) AS dummy ON 1 = 1berhasil juga?
ypercubeᵀᴹ
6

Coba kode ini ..

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

The ISNULLpanggilan memastikan bahwa kolom baru dibuat dengan NOT NULLnullability.

Saurav Ghosh
sumber
1
Apakah itu ISNULL()atau +0yang melakukannya? Atau keduanya dibutuhkan?
ypercubeᵀᴹ
Hanya menambahkan 0 karya. Ini adalah solusi paling sederhana selama Anda membuat daftar kolom secara eksplisit dan tidak menggunakan pilih *.
Ian Horwill
3

Hanya untuk menunjukkan cara yang berbeda:

Anda dapat menggunakan server tertaut .

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

Anda dapat membuat sementara server yang tertaut ke server lokal menggunakan ini:

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;

Di titik mana, Anda akan menjalankan select * intokode, merujuk localserverserver empat-nama-nama yang ditautkan:

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

Setelah itu selesai, bersihkan localserverserver yang ditautkan dengan ini:

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

Atau, Anda bisa menggunakan OPENQUERYsintaks

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

Properti identitas tidak ditransfer jika pernyataan pilih berisi gabungan, dan sebagainya

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

juga akan memberikan perilaku yang diinginkan (dari idkolom yang disalin untuk tidak menyimpan IDENTITYproperti. Namun, itu akan memiliki efek samping dari tidak menyalin baris sama sekali! (seperti dengan beberapa metode lain) sehingga Anda perlu melakukan:

insert into without_id select * from with_id;

(terima kasih AakashM!)

anon-99
sumber
1

Cara mudah adalah membuat bagian kolom dari ekspresi.

Contoh:
Jika tabel dbo.Employee memiliki identitas pada kolom ID maka dalam contoh di bawah ini tabel temp #t akan memiliki IDENTITAS pada kolom ID juga.

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

Ubah ini untuk menerapkan ekspresi pada ID dan Anda tidak akan lagi memiliki IDENTITAS pada kolom ID. Dalam hal ini kami menerapkan tambahan sederhana ke kolom ID.

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

Contoh lain dari ekspresi untuk tipe data lain dapat meliputi: convert (), string concatenation, atau Isnull ()

Tinjuan kemarahan
sumber
1
Dari docs.microsoft.com/en-us/sql/t-sql/queries/… : “Ketika kolom identitas yang ada dipilih ke dalam tabel baru, kolom baru mewarisi properti IDENTITY, kecuali salah satu dari kondisi berikut ini benar ... Kolom identitas adalah bagian dari ekspresi ... kolom dibuat BUKAN NULL alih-alih mewarisi properti IDENTITY. "
Manngo
1

Terkadang, Anda ingin menyisipkan dari tabel di mana Anda tidak tahu (atau peduli) apakah kolom itu dibuat menggunakan IDENTITY atau tidak. Bahkan mungkin bukan kolom bilangan bulat yang sedang Anda kerjakan. Dalam hal ini, berikut ini akan berfungsi:

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

ISNULL akan menjatuhkan atribut IDENTITY dari kolom tetapi menyisipkannya dengan nama dan jenis yang sama dengan kolom asli dan juga membuatnya tidak nullable. TOP (0) akan membuat tabel kosong yang bisa Anda gunakan untuk menyisipkan baris yang dipilih. Anda juga dapat membuat tabel dikompresi sebelum Anda memasukkan data jika diperlukan.

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

akan menghapus identitas.

Downsidenya idmenjadi nullable tetapi Anda bisa menambahkan batasan itu.

pemburu john
sumber
1
Anda dapat menggunakan ISNULL untuk menyiasatinya .
Erik Darling
-2

Kamu tidak. select * intomempertahankan identitas.

Mladen Prajdic
sumber
2
Tidak ada persyaratan dalam pertanyaan untuk digunakan *.
Martin Smith
2
Dan identityproperti tidak selalu dilestarikan, seperti yang ditunjukkan oleh jawaban lain.
ypercubeᵀᴹ