在开发过程中,很多时候要把结果集存放到临时表中,常用的方法有两种。

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

(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

sp_help_job_with_results

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_fixeddrives

调用封装过的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')

01. 把存储过程结果集SELECT INTO到临时表的更多相关文章

  1. 把存储过程结果集SELECT INTO到临时表

    把存储过程结果集SELECT INTO到临时表 在开发过程中,很多时候要把结果集存放到临时表中,常用的方法有两种. 一. SELECT INTO . 使用select into会自动生成临时表,不需要 ...

  2. 转:把存储过程结果集SELECT INTO到临时表

    把存储过程结果集SELECT INTO到临时表   在开发过程中,很多时候要把结果集存放到临时表中,常用的方法有两种.   一. SELECT INTO  1. 使用select into会自动生成临 ...

  3. 存储过程中使用select……into

    在MySQL存储过程中使用SELECT -INTO语句为变量赋值: 用来将查询返回的一行的各个列值保存到局部变量中. 要求: 查询的结果集中只能有1行. SELECT col_name[,...] I ...

  4. oracle数据库存储过程中的select语句的位置

    导读:在oracle数据库存储过程中如果用了select语句,要么使用"select into 变量"语句要么使用游标,oracle不支持单独的select语句. 先看下这个存储过 ...

  5. 【转载】Sqlserver存储过程中使用Select和Set给变量赋值

    Sqlserver存储过程是时常使用到的一个数据库对象,在存储过程中会使用到Declare来定义存储过程变量,定义的存储过程变量可以通过Set或者Select等关键字方法来进行赋值操作,使用Set对存 ...

  6. SELECT INTO创建临时表

    SELECT INTO创建临时表 SQL Server临时表有两种类型:本地和全局.它们在名称.可见性以及可用性上有区别.本地临时表的名称以单个数字符号 (#) 打头:它们仅对当前的用户连接是可见的: ...

  7. MySQL存储过程中使用SELECT …INTO语句为变量赋值

    使用SELECT …INTO语句为变量赋值 在MySQL存储过程中,可以使用SELECT …INTO语句对变量进行赋值,该语句在数据库中进行查询,并将得到的结果赋值给变量.SELECT …INTO语句 ...

  8. 把存储过程SELECT INTO到临时表

    在开发过程中,很多时候要把结果集存放到临时表中,常用的方法有两种. 一. SELECT INTO1. 使用select into会自动生成临时表,不需要事先创建12 select * into #te ...

  9. Codeforces 741B:Arpa's weak amphitheater and Mehrdad's valuable Hoses(01背包+并查集)

    http://codeforces.com/contest/741/problem/B 题意:有 n 个人,每个人有一个花费 w[i] 和价值 b[i],给出 m 条边,代表第 i 和 j 个人是一个 ...

随机推荐

  1. CreateEvent的用法

    事件对象就像一个开关:它只有两种状态---开和关.当一个事件处于”开”状态,我们称其为”有信号”否则称为”无信号”.可以在一个线程的执行函数中创建一个事件对象,然后观察它的状态,如果是”无信号”就让该 ...

  2. JQery icheck 插件

    <script type="text/javascript"> $(document).ready(function(){ var callbacks_list = $ ...

  3. 一种基于PTP 协议的局域网高精度时钟同步方法(转)

    原文地址 http://www.dzsc.com/data/html/2011-1-17/88338.html 1 引言 在分布式系统中, 常常需要一个全局时间, 用来确定系统中各种事件发生的先后.协 ...

  4. 解决Unable to connect to a repository at URL 禁止访问 (forbidden)

    连接SVN报如下错误. Unable to connect to a repository at URL 禁止访问 (forbidden) 1.         右键点击本地副本,TortoiseSV ...

  5. C166 Interfacing C to Assembler

    Interfacing C to Assembler You can easily interface your C programs to routines written in XC16x/C16 ...

  6. IOS学习经验总结--来自知乎网友

    转自知乎:http://www.zhihu.com/question/20016551 我当时刚学iOS开发的时候一样的感觉 总想知道原理 内部怎么回事 感觉在像在雾里但是iOS开发就是这样 他是封闭 ...

  7. C++ delete operator做了什么事

    1.C++中的delete operator做了两件事:调用析构方法和调用operator delete释放内存. 2.考虑析构方法,如果析构方法是虚方法,调用指针真实类型的析构方法,否则调用表面类型 ...

  8. [MySQL复制异常]'Cannot execute statement: impossible to write to binary log since statement is in row format and BINLOG_FORMAT = STATEMENT.'

    MySQL复制错误]Last_Errno: 1666 Last_Error: Error executing row event: 'Cannot execute statement: imposs ...

  9. Codeforces Gym 100187D D. Holidays 排列组合

    D. Holidays Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100187/problem/D ...

  10. Codeforces Round #307 (Div. 2) A. GukiZ and Contest 水题

    A. GukiZ and Contest Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/551/ ...