在開發過程中,很多時候要把結果集存放到臨時表中,常用的方法有兩種。
一. SELECT INTO
1. 使用select into會自動生成臨時表,不需要事先創建
select * into #temp from sysobjects select * from #temp
2. 如果當前會話中,已存在同名的臨時表
select * into #temp from sysobjects
再次運行,則會報錯提示:數據庫中已存在名為 '%1!' 的對象。
Msg 2714, Level 16, State 6, Line 2
There is already an object named '#temp' in the database.
在使用select into前,可以先做一下判斷:
if OBJECT_ID('tempdb..#temp') is not null drop table #temp select * into #temp from sysobjects select * from #temp
3. 利用select into生成一個空表
如果要生成一個空的表結構,不包含任何數據,可以給定一個恆不等式如下:
select * into #temp from sysobjects where 1=2 select * from #temp
備注:(更新:2018-09-20)
(1) 通過select into復制表默認會保留identity列屬性, 從linked server復制表則不會;
--server1, database1 create table test_identity(id int identity, value int) insert into test_identity values(100) --server2, database2 select * into temp from sever1.database1.dbo.test_identity select object_name(object_id) as table_name, name, is_identity,* from sys.columns where object_id=object_id('temp') /* table_name name is_identity temp id 0 */
(2) 列的是否為null屬性默認直接復制,如果顯式給定列值,則目標表的列屬性不允許為null;
--principal_id列定義可為空 exec sp_help 'sys.objects' drop table if exists test_null01; drop table if exists test_null02; select principal_id into test_null01 from sys.objects select isnull(principal_id,0) as principal_id into test_null02 from sys.objects select name, is_nullable,* from sys.columns where object_id = object_id('test_null01') --name is_nullable --principal_id 1 select name, is_nullable,* from sys.columns where object_id = object_id('test_null02') --name is_nullable --principal_id 0 select isnull(null,'') c1 into test_null_01 select '' c1 into test_null_02 select 1 c1 into test_null_03 exec sp_columns test_null_01 exec sp_columns test_null_02 exec sp_columns test_null_03 --NULLABLE --0
(3) 如果顯式給定列值為null,或者join后列值全部為null, 目標表中該列的數據類型默認為int,除非用CAST/CONVERT顯式指定null列的數據類型;
--if get only null value after join, select into will use int for null-value columns as well select null as data_type into test_data_type; exec sp_columns test_data_type
(4) SELECT… INTO… 除了復制identity屬性外,僅復制數據,所以原表上的約束/索引/壓縮選項等都不會被復制,所以從columnstore的表拉數據出來,會發現表變大了很多了,因為columnstore默認壓縮數據,這種場景可考慮使用insert into… with(tablock) select… 結合610跟蹤標記來替代SELECT… INTO;
(5) 從SQL SERVER 2014起,SELECT …INTO…的插入操作,執行計划顯示為並行化操作符,也即插入操作不再是單線程;
二. INSERT INTO
1. 使用insert into,需要先手動創建臨時表
1.1 保存從select語句中返回的結果集
create table test_getdate(c1 datetime)
insert into test_getdate select GETDATE()
select * from test_getdate
1.2 保存從存儲過程返回的結果集
create table #helpuser ( UserName nvarchar(128), RoleName nvarchar(128), LoginName nvarchar(128), DefDBName nvarchar(128), DefSchemaName nvarchar(128), UserID smallint, SID smallint ) insert into #helpuser exec sp_helpuser select * from #helpuser
1.3 保存從動態語句返回的結果集
create table test_dbcc ( TraceFlag varchar(100), Status tinyint, Global tinyint, Session tinyint ) insert into test_dbcc exec('DBCC TRACESTATUS') select * from test_dbcc
對於動態SQL,或者類似DBCC這種非常規的SQL語句,都可以通過這種方式來保存結果集。
2. 不能嵌套使用insert exec語句
2.1 下面這個例子,嘗試保存sp_help_job的結果集到臨時表,發生錯誤
create table #JobInfo ( job_id uniqueidentifier, originating_server nvarchar(128), name nvarchar(128), enabled tinyint, description nvarchar(512), start_step_id int, category nvarchar(128), owner nvarchar(128), notify_level_eventlog int, notify_level_email int, notify_level_netsend int, notify_level_page int , notify_email_operator nvarchar(128), notify_netsend_operator nvarchar(128), notify_page_operator nvarchar(128), delete_level int, date_created datetime, date_modified datetime, version_number int, last_run_date int, last_run_time int, last_run_outcome int, next_run_date int, next_run_time int, next_run_schedule_id int, current_execution_status int, current_execution_step nvarchar(128), current_retry_attempt int, has_step int, has_schedule int, has_target int, type int ) insert into #JobInfo exec msdb..sp_help_job
返回錯誤信息:INSERT EXEC 語句不能嵌套。
Msg 8164, Level 16, State 1, Procedure sp_get_composite_job_info, Line 72
An INSERT EXEC statement cannot be nested.
展開錯誤信息中的存儲過程:
exec sp_helptext sp_get_composite_job_info
發現里面還有個INSERT INTO…EXEC的嵌套調用,SQL Server在語法上不支持。
INSERT INTO @xp_results EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner, @job_id
2.2 可以用分布式查詢來避免這個問題,這種寫法在INSIDE SQL Server 2005中作者提到過
(1) 首先到打開服務器選項Ad Hoc Distributed Queries
exec sp_configure 'show advanced options',1 RECONFIGURE GO exec sp_configure 'Ad Hoc Distributed Queries',1 RECONFIGURE GO
(2) 通過OPENROWSET連接到本機,運行存儲過程,取得結果集
使用windows認證
select * into #JobInfo_S1 from openrowset('sqloledb', 'server=(local);trusted_connection=yes','exec msdb.dbo.sp_help_job') select * from #JobInfo_S1
使用SQL Server認證
SELECT * INTO #JobInfo_S2 FROM OPENROWSET('SQLOLEDB','127.0.0.1';'sa';'sa_password','exec msdb.dbo.sp_help_job') SELECT * FROM #JobInfo_S2
這樣的寫法,既免去了手動建表的麻煩,也可以避免insert exec 無法嵌套的問題。幾乎所有SQL語句都可以使用。
--dbcc不能直接運行 SELECT a.* into #t FROM OPENROWSET('SQLOLEDB','127.0.0.1';'sa';'sa_password', 'dbcc log(''master'',3)') AS a --可以變通一下 SELECT a.* into #t FROM OPENROWSET('SQLOLEDB','127.0.0.1';'sa';'sa_password', 'exec(''DBCC LOG(''''master'''',3)'')') AS a
后續的SQL SERVER版本中,這種寫法有限制 (更新:2018-09-19)
1. 在SQL SERVER 2008 R2下測試,問題如下:
--sp_help_job沒問題 SELECT * FROM OPENROWSET ('SQLOLEDB','Server=.\SQLEXPRESS;Trusted_Connection=yes','EXEC msdb.dbo.sp_help_job') --隨手寫了幾個sp_who2, xp_fixeddrives, sp_helpdb,都失敗了 select * from OPENROWSET('SQLOLEDB','Server=.\SQLEXPRESS;TRUSTED_CONNECTION=YES;','exec sp_who2') /* Msg 7357, Level 16, State 2, Line 2 Cannot process the object "exec sp_who2". The OLE DB provider "SQLNCLI10" for linked server "(null)" indicates that either the object has no columns or the current user does not have permissions on that object. */ select * from OPENROWSET('SQLOLEDB','Server=.\SQLEXPRESS;TRUSTED_CONNECTION=YES;','exec xp_fixeddrives') /* Msg 7357, Level 16, State 2, Line 1 Cannot process the object "exec xp_fixeddrives". The OLE DB provider "SQLNCLI10" for linked server "(null)" indicates that either the object has no columns or the current user does not have permissions on that object. */ select * from OPENROWSET('SQLOLEDB','Server=.\SQLEXPRESS;TRUSTED_CONNECTION=YES;','exec sp_helpdb') /* Msg 208, Level 16, State 1, Procedure sp_helpdb, Line 51 Invalid object name '#spdbdesc'. */
2.在SQL SERVER 2012, 2014, 2016下測試,問題如下:
--sp_help_job也失敗了 SELECT * FROM OPENROWSET ('SQLOLEDB','Server=.\MSSQL2016;Trusted_Connection=yes','EXEC msdb.dbo.sp_help_job') /* Msg 11520, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 The metadata could not be determined because statement 'EXECUTE master.dbo.xp_sqlagent_is_starting @retval OUTPUT' in procedure 'sp_is_sqlagent_starting' invokes an extended stored procedure.*/ --sp_who2, xp_fixeddrives, sp_helpdb,錯誤也都相對統一了 select * from OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec sp_who2') /* Msg 11526, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 The metadata could not be determined because statement 'delete #tb1_sysprocesses where lower(status) = 'sleeping' and upper(cmd) in (' in procedure 'sp_who2' uses a temp table. */ select * from OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec xp_fixeddrives') /* Msg 11519, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 The metadata could not be determined because statement 'exec xp_fixeddrives' invokes an extended stored procedure. */ select * from OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec sp_helpdb') /* Msg 11526, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 The metadata could not be determined because statement 'insert into #spdbdesc (dbname, owner, created, dbid, cmptlevel) select name, isnull(suser_sname(s' in procedure 'sp_helpdb' uses a temp table. */
可以看出是因為不能確定所返回結果集的meta信息導致的:
EXEC sp_describe_first_result_set @tsql = N'exec msdb.dbo.sp_help_job' GO /* Msg 11520, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 The metadata could not be determined because statement 'EXECUTE master.dbo.xp_sqlagent_is_starting @retval OUTPUT' in procedure 'sp_is_sqlagent_starting' invokes an extended stored procedure. */
變通的解決辦法:自定義SP對需要調用的系統SP包裝一次,用WITH RESULT SETS返回固定的結果集,從而避免這個錯誤;
注意WITH RESULT SETS選項從SQL SERVER 2012起開始支持,實例如下:

USE MSDB GO IF (EXISTS (SELECT * FROM msdb.dbo.sysobjects WHERE (name = N'sp_help_job_with_results') AND (type = 'P'))) DROP PROCEDURE sp_help_job_with_results go CREATE PROCEDURE sp_help_job_with_results @job_id UNIQUEIDENTIFIER = NULL, @job_name SYSNAME = NULL, @job_aspect VARCHAR(9) = NULL, @job_type VARCHAR(12) = NULL, @owner_login_name SYSNAME = NULL, @subsystem NVARCHAR(40) = NULL, @category_name SYSNAME = NULL, @enabled TINYINT = NULL, @execution_status INT = NULL, @date_comparator CHAR(1) = NULL, @date_created DATETIME = NULL, @date_last_modified DATETIME = NULL, @description NVARCHAR(512) = NULL AS BEGIN -- If job_id or job_name were not specified there will be only one resultset IF (@job_id IS NULL AND @job_name IS NULL) BEGIN EXEC sp_help_job @job_id, @job_name, @job_aspect, @job_type, @owner_login_name, @subsystem, @category_name, @enabled, @execution_status, @date_comparator, @date_created, @date_last_modified, @description WITH RESULT SETS ( ( job_id UNIQUEIDENTIFIER, originating_server NVARCHAR(30), name SYSNAME, [enabled] TINYINT, [description] NVARCHAR(512), start_step_id INT, category SYSNAME, [owner] SYSNAME, notify_level_eventlog INT, notify_level_email INT, notify_level_netsend INT, notify_level_page INT, notify_email_operator SYSNAME, notify_netsend_operator SYSNAME, notify_page_operator SYSNAME, delete_level INT, date_created DATETIME, date_modified DATETIME, version_number INT, last_run_date INT, last_run_time INT, last_run_outcome INT, next_run_date INT, next_run_time INT, next_run_schedule_id INT, current_execution_status INT, current_execution_step SYSNAME, current_retry_attempt INT, has_step INT, has_schedule INT, has_target INT, [type] INT ) ) END ELSE BEGIN -- If job_id or job_name is not null, there will be multiple resultsets EXEC sp_help_job @job_id, @job_name, @job_aspect, @job_type, @owner_login_name, @subsystem, @category_name, @enabled, @execution_status, @date_comparator, @date_created, @date_last_modified, @description WITH RESULT SETS ( ( job_id UNIQUEIDENTIFIER, originating_server NVARCHAR(30), name SYSNAME, [enabled] TINYINT, [description] NVARCHAR(512), start_step_id INT, category SYSNAME, [owner] SYSNAME, notify_level_eventlog INT, notify_level_email INT, notify_level_netsend INT, notify_level_page INT, notify_email_operator SYSNAME, notify_netsend_operator SYSNAME, notify_page_operator SYSNAME, delete_level INT, date_created DATETIME, date_modified DATETIME, version_number INT, last_run_date INT, last_run_time INT, last_run_outcome INT, next_run_date INT, next_run_time INT, next_run_schedule_id INT, current_execution_status INT, current_execution_step SYSNAME, current_retry_attempt INT, has_step INT, has_schedule INT, has_target INT, [type] INT ), ( step_id INT, step_name SYSNAME, subsystem NVARCHAR(40) , command NVARCHAR(max) , flags NVARCHAR(4000), cmdexec_success_code INT, on_success_action NVARCHAR(4000), on_success_step_id INT, on_fail_action NVARCHAR(4000), on_fail_step_id INT, [server] SYSNAME, database_name SYSNAME, database_user_name SYSNAME, retry_attempts INT, retry_interval INT, os_run_priority NVARCHAR(4000), output_file_name NVARCHAR(200), last_run_outcome INT, last_run_duration INT, last_run_retries INT, last_run_date INT, last_run_time INT, proxy_id INT ), ( schedule_id INT, schedule_name SYSNAME, [enabled] INT, freq_type INT, freq_interval INT, freq_subday_type INT, freq_subday_interval INT, freq_relative_interval INT, freq_recurrence_factor INT, active_start_date INT, active_end_date INT, active_start_time INT, active_end_time INT, date_created DATETIME, schedule_description NVARCHAR(4000) , next_run_date INT, next_run_time INT, schedule_uid UNIQUEIDENTIFIER, job_count INT ), ( server_id INT, server_name NVARCHAR(30), enlist_date DATETIME, last_poll_date DATETIME, last_run_date INT, last_run_time INT, last_run_duration INT, last_run_outcome TINYINT, last_outcome_message NVARCHAR(1024) ) ) END END GO

IF (EXISTS (SELECT * FROM sysobjects WHERE (name = 'sp_fixeddrives') AND (type = 'P'))) DROP PROCEDURE sp_fixeddrives GO CREATE PROCEDURE sp_fixeddrives AS BEGIN EXEC xp_fixeddrives WITH RESULT SETS ( ( drive varchar(10), [MB Free] varchar(100) ) ) END
調用封裝過的SP:
SET FMTONLY OFF EXEC sp_describe_first_result_set @tsql = N'exec msdb.dbo.sp_help_job_with_results' GO EXEC sp_describe_first_result_set @tsql = N'exec sp_fixeddrives' GO --直接調用sp_help_job失敗 SELECT * FROM OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec msdb.dbo.sp_help_job') --封裝為sp_help_job_with_results后調用成功 SELECT * FROM OPENROWSET ('SQLOLEDB','Server=.\MSSQL2016;Trusted_Connection=yes','EXEC msdb.dbo.sp_help_job_with_results') --直接調用xp_fixeddrives失敗 SELECT * FROM OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec xp_fixeddrives') --封裝為sp_fixeddrives后調用成功 SELECT * FROM OPENROWSET('SQLOLEDB','Server=.\MSSQL2016;TRUSTED_CONNECTION=YES;','exec sp_fixeddrives')