01. 把存儲過程結果集SELECT INTO到臨時表


在開發過程中,很多時候要把結果集存放到臨時表中,常用的方法有兩種。

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

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM