Apakah ada cara untuk menghasilkan skrip pembuatan tabel di TSQL?

22

Apakah ada cara untuk menghasilkan skrip buat dari tabel yang ada murni di T-SQL (yang tanpa menggunakan SMO, karena T-SQL tidak memiliki akses ke SMO). Katakanlah prosedur tersimpan yang menerima nama tabel dan mengembalikan string yang berisi skrip buat untuk tabel yang diberikan?

Sekarang izinkan saya menggambarkan situasi yang saya hadapi, karena mungkin ada cara berbeda untuk mendekati ini. Saya punya contoh dengan beberapa lusin basis data. Semua database ini memiliki skema yang sama, semua tabel yang sama, indeks dan sebagainya. Mereka dibuat sebagai bagian dari instalasi perangkat lunak pihak ketiga. Saya perlu memiliki cara untuk bekerja dengan mereka sehingga saya dapat mengumpulkan data dari mereka secara ad-hoc. Orang-orang baik di dba.se telah membantu saya di sini. Cara membuat pemicu dalam database yang berbeda?

Saat ini saya perlu menemukan cara untuk memilih dari tabel di semua database. Saya telah mencatat semua nama basis data ke dalam tabel yang disebut Databaseesdan saya menulis skrip berikut untuk mengeksekusi pernyataan pilih pada semuanya:

IF OBJECT_ID('tempdb..#tmp') IS NOT NULL
DROP TABLE #tmp

select * into #tmp from Database1.dbo.Table1 where 1=0
DECLARE @statement nvarchar(max) = 
  N'insert into #tmp select * from Table1 where Column1=0 and Cloumn2 =1'

DECLARE @LastDatabaseID INT
SET @LastDatabaseID = 0

DECLARE @DatabaseNameToHandle varchar(60)
DECLARE @DatabaseIDToHandle int

SELECT TOP 1 @DatabaseNameToHandle = Name,
@DatabaseIDToHandle = Database_Ref_No
FROM Databasees
WHERE Database_Ref_No > @LastDatabaseID
ORDER BY Database_Ref_No

WHILE @DatabaseIDToHandle IS NOT NULL
BEGIN

  DECLARE @sql NVARCHAR(MAX) = QUOTENAME(@DatabaseNameToHandle) + '.dbo.sp_executesql'
  EXEC @sql @statement

  SET @LastDatabaseID = @DatabaseIDToHandle
  SET @DatabaseIDToHandle = NULL

  SELECT TOP 1 @DatabaseNameToHandle = Name,
  @DatabaseIDToHandle = Database_Ref_No
  FROM Databasees
  WHERE Database_Ref_No > @LastDatabaseID
  ORDER BY Database_Ref_No
END

select * from #tmp
DROP TABLE #tmp

Namun skrip di atas gagal dengan pesan berikut:

Nilai eksplisit untuk kolom identitas dalam tabel '#tmp' hanya dapat ditentukan ketika daftar kolom digunakan dan IDENTITY_INSERT adalah ON.

Menambahkan ini:

SET IDENTITY_INSERT #tmp ON

tidak membantu, karena, saya tidak dapat menentukan daftar kolom dan menyimpannya secara umum.

Dalam SQL tidak ada cara untuk mematikan identitas pada tabel yang diberikan. Anda hanya bisa menjatuhkan kolom dan menambahkan kolom, yang, jelas mengubah urutan kolom. Dan jika urutan kolom berubah, Anda, sekali lagi, perlu menentukan daftar kolom, itu akan berbeda tergantung pada tabel yang Anda query.

Jadi saya berpikir jika saya bisa mendapatkan skrip tabel buat dalam kode T-SQL saya, saya bisa memanipulasinya dengan ekspresi manipulasi string untuk menghapus kolom identitas dan juga menambahkan kolom untuk nama Database ke set hasil.

Adakah yang bisa memikirkan cara yang relatif mudah untuk mencapai apa yang saya inginkan?

Andrew Savinykh
sumber

Jawaban:

28

Kembali pada tahun 2007, saya meminta cara mudah untuk menghasilkan CREATE TABLEskrip melalui T-SQL daripada menggunakan UI atau SMO. Singkatnya saya ditolak .

Namun, SQL Server 2012 membuat ini sangat mudah. Anggaplah kita memiliki tabel dengan skema yang sama di beberapa basis data, misalnya dbo.whatcha:

CREATE TABLE dbo.whatcha
(
  id INT IDENTITY(1,1), 
  x VARCHAR(MAX), 
  b DECIMAL(10,2), 
  y SYSNAME
);

Skrip berikut menggunakan sys.dm_exec_describe_first_results_setfungsi manajemen dinamis baru untuk mengambil tipe data yang tepat untuk setiap kolom (dan mengabaikan IDENTITYproperti). Itu membangun tabel #tmp yang Anda butuhkan, menyisipkan dari masing-masing database dalam daftar Anda, dan kemudian memilih dari #tmp, semua dalam satu batch SQL dinamis dan tanpa menggunakan WHILEloop (yang tidak membuatnya lebih baik, hanya lebih mudah untuk lihat dan memungkinkan Anda untuk mengabaikan Database_Ref_Nosepenuhnya :-)).

SET NOCOUNT ON;

DECLARE @sql NVARCHAR(MAX), @cols NVARCHAR(MAX) = N'';

SELECT @cols += N',' + name + ' ' + system_type_name
  FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.whatcha', NULL, 1);

SET @cols = STUFF(@cols, 1, 1, N'');

SET @sql = N'CREATE TABLE #tmp(' + @cols + ');'

DECLARE @dbs TABLE(db SYSNAME);

INSERT @dbs VALUES(N'db1'),(N'db2');
  -- SELECT whatever FROM dbo.databases

SELECT @sql += N'
  INSERT #tmp SELECT ' + @cols + ' FROM ' + QUOTENAME(db) + '.dbo.tablename;'
  FROM @dbs;

SET @sql += N'
  SELECT ' + @cols + ' FROM #tmp;';

PRINT @sql;
-- EXEC sp_executesql @sql;

PRINTOutput yang dihasilkan :

CREATE TABLE #tmp(id int,x varchar(max),b decimal(10,2),y nvarchar(128));
  INSERT #tmp SELECT id,x,b,y FROM [db1].dbo.tablename;
  INSERT #tmp SELECT id,x,b,y FROM [db2].dbo.tablename;
  SELECT id,x,b,y FROM #tmp;

Ketika Anda yakin itu melakukan apa yang Anda harapkan, cukup batalkan komentar pada EXEC.

(Ini mempercayai Anda bahwa skema itu sama; skema tidak memvalidasi bahwa satu atau beberapa tabel sejak itu telah diubah, dan mungkin gagal sebagai hasilnya.)

Aaron Bertrand
sumber
Mengapa itu tidak menghasilkan spesifikasi identitas?
FindOutIslamNow
1
@Kilanny Apakah Anda membaca seluruh jawaban, seperti bagian di mana saya berbicara tentang mengapa kita mengabaikan properti identitas?
Aaron Bertrand
Saya memerlukan definisi tabel LENGKAP (termasuk identitas, indeks, batasan, ..). Untungnya saya menemukan skrip hebat ini stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt Anyway, terima kasih
FindOutIslamNow
@Kilanny Hebat. Agar jelas, persyaratan Anda tidak cocok dengan persyaratan dalam pertanyaan ini. Mereka membutuhkan salinan tabel tanpa identitas karena mereka menggunakannya untuk menyalin data yang ada, bukan menghasilkan baris baru.
Aaron Bertrand
Aaron, ini adalah solusi elegan dalam keadaan darurat di mana Anda tidak perlu kunci, dll ...
GWR
5

Tidak mungkin untuk T-SQL menghasilkan skrip pembuatan tabel yang lengkap. Setidaknya tidak ada build in way. Anda selalu dapat menulis "generator" Anda sendiri melalui informasi sys.columns.

Namun dalam kasus Anda, Anda tidak perlu mendapatkan skrip pembuatan lengkap. Yang Anda butuhkan adalah mencegah SELECT INTOdari menyalin properti identitas. Cara termudah untuk melakukannya adalah dengan menambahkan perhitungan ke kolom itu. Jadi, bukannya

select * into #tmp from Database1.dbo.Table1 where 1=0

kamu perlu menulis

select id*0 as id, other, column, names into #tmp from Database1.dbo.Table1 where 1=0

Untuk menghasilkan pernyataan ini, Anda dapat kembali menggunakan sys.columns seperti pada SQL Fiddle ini

Setup Skema MS SQL Server 2008 :

CREATE TABLE dbo.testtbl(
    id INT IDENTITY(1,1),
    other NVARCHAR(MAX),
    [column] INT,
    [name] INT
);

Dua kolom yang kita butuhkan adalah namedan is_identity: Permintaan 1 :

SELECT name,is_identity
  FROM sys.columns
 WHERE object_id = OBJECT_ID('dbo.testtbl');

Hasil :

|   NAME | IS_IDENTITY |
|--------|-------------|
|     id |           1 |
|  other |           0 |
| column |           0 |
|   name |           0 |

Dengan itu kita bisa menggunakan CASEpernyataan untuk membuat setiap kolom untuk daftar kolom:

Pertanyaan 2 :

SELECT ','+ 
    CASE is_identity
    WHEN 1 THEN QUOTENAME(name)+'*0 AS '+QUOTENAME(name)
    ELSE QUOTENAME(name)
    END
  FROM sys.columns
 WHERE object_id = OBJECT_ID('dbo.testtbl');

Hasil :

|        COLUMN_0 |
|-----------------|
| ,[id]*0 AS [id] |
|        ,[other] |
|       ,[column] |
|         ,[name] |

Dengan sedikit tipuan XML, kita dapat menggabungkan semua ini bersama-sama untuk mendapatkan daftar kolom lengkap:

Pertanyaan 3 :

SELECT STUFF((
  SELECT ','+ 
      CASE is_identity
      WHEN 1 THEN QUOTENAME(name)+'*0 AS '+QUOTENAME(name)
      ELSE QUOTENAME(name)
      END
    FROM sys.columns
   WHERE object_id = OBJECT_ID('dbo.testtbl')
   ORDER BY column_id
     FOR XML PATH(''),TYPE
  ).value('.','NVARCHAR(MAX)'),1,1,'')

Hasil :

|                               COLUMN_0 |
|----------------------------------------|
| [id]*0 AS [id],[other],[column],[name] |

Perlu diingat, bahwa Anda tidak dapat membuat tabel #temp menggunakan SQL dinamis dan menggunakannya di luar pernyataan itu karena tabel #temp keluar dari ruang lingkup setelah pernyataan sql dinamis Anda selesai. Jadi Anda harus menekan semua kode Anda ke dalam string SQL dinamis yang sama atau menggunakan tabel nyata. Jika Anda harus dapat menjalankan beberapa skrip / prosedur ini secara bersamaan, Anda perlu memberi kami nama tabel acak, jika tidak mereka akan saling menginjak. Sesuatu seperti QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))seharusnya membuat nama yang cukup bagus.


Alih-alih menyalin semua data di sekitar, Anda juga bisa menggunakan teknik serupa untuk secara otomatis menghasilkan tampilan untuk setiap tabel yang menyatukan semua inkarnasi dari tabel itu di semua basis data. Namun tergantung pada ukuran tabel yang mungkin lebih cepat atau lebih lambat, jadi Anda harus mengujinya. Jika Anda pergi rute ini, saya akan menempatkan pandangan-pandangan itu ke dalam basis data terpisah.

Sebastian Meine
sumber
1
Anda dapat membuat tabel #temp dan kemudian referensi dari SQL dinamis. Hanya jika Anda membuatnya dalam lingkup itu tidak terlihat setelah SQL dinamis dieksekusi.
Aaron Bertrand
3

Ada skrip yang bagus untuk mencapai hal ini dalam artikel SQLServerCentral:

Versi terbaru skrip saat ini juga tersedia sebagai teks di sini (stormrage.com).

Saya berharap ada cara untuk memasukkan semua skrip di sini, karena itu berfungsi untuk saya. Script terlalu panjang untuk ditempelkan di sini.

Pemberitahuan hak cipta:

--#################################################################################################
-- copyright 2004-2013 by Lowell Izaguirre scripts*at*stormrage.com all rights reserved.
-- http://www.stormrage.com/SQLStuff/sp_GetDDL_Latest.txt
--Purpose: Script Any Table, Temp Table or Object
--
-- see the thread here for lots of details: http://www.sqlservercentral.com/Forums/Topic751783-566-7.aspx

-- You can use this however you like...this script is not rocket science, but it took a bit of work to create.
-- the only thing that I ask
-- is that if you adapt my procedure or make it better, to simply send me a copy of it,
-- so I can learn from the things you've enhanced.The feedback you give will be what makes
-- it worthwhile to me, and will be fed back to the SQL community.
-- add this to your toolbox of helpful scripts.
--#################################################################################################
Marcello Miorelli
sumber
-1

Anda dapat menghasilkan kasar CREATE TABLEmenggunakan SQL dinamis dari data di INFORMATION_SCHEMA.COLUMNS.

Jika Anda perlu menambahkan batasan dll, Anda perlu menambahkan informasi dari beberapa INFORMATION_SCHEMAtampilan lain .

Dokumentasi Microsoft

david25272
sumber