需求是这样的:
同一台机器上的3个不同数据库之间进行联合查询, 将查询的结果导入一张临时表, 并查询临时表返回统计信息. 用户更具统计信息, 在倒临时表中的实际数据导出文本到txt.
USE [UMSDB] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO --- AUTO DROP --- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[USP_UMS_InsertMigratedUserInfoByLogicalPool]') AND type in (N'P', N'PC')) DROP PROCEDURE [dbo].[USP_UMS_InsertMigratedUserInfoByLogicalPool] GO -- ============================================= -- Author: <zhangpeng> -- Create date: <2012-05-25> -- Description: <1. Insert Multiple UserInfos of Migration Finished By LogicalPool Id -- 2. Get Statistics Result of UserInfos > -- ============================================= CREATE PROCEDURE [dbo].[USP_UMS_InsertMigratedUserInfoByLogicalPool] ( @LogicalPoolId int ) AS DECLARE @v_TmpTableName nvarchar(64) DECLARE @v_Sql nvarchar(2000) BEGIN -- 1. Create Global TmpTable SET @v_TmpTableName = '[##TMP_MigrationFinishedInfo]' SET @v_Sql = 'IF EXISTS (SELECT * FROM tempdb..sysobjects WHERE id = object_id(N''tempdb..'+@v_TmpTableName+''') AND [type] IN (N''U'')) BEGIN Truncate TABLE '+@v_TmpTableName+' END ELSE BEGIN CREATE TABLE '+@v_TmpTableName+' ( UserId int PRIMARY KEY NOT NULL, MobileNo bigint NULL, Region varchar(64) NOT NULL, LogicalPool int NOT NULL ) END' EXEC(@v_Sql) -- 2. Insert Data to TmpTable SET @v_Sql = 'INSERT INTO [dbo].'+@v_TmpTableName+' SELECT temp.UserId,temp.MobileNo,mns.Region,temp.LogicalPool FROM [IICHADB].[dbo].[CODE_MobileNoSegment] mns CROSS JOIN ( SELECT uca.UserId,uca.MobileNo,uca.LogicalPool FROM ( SELECT UserId,Max(Mid) AS Mid FROM [UMSDB].[dbo].[Task] WHERE TaskStatus = 3 AND DestLogicalPool = @LogicalPoolId GROUP BY UserId ) t Inner JOIN [CATDB].[dbo].[CAT_UserCatalogAll] uca ON t.UserId = uca.UserId AND uca.LogicalPool = @LogicalPoolId ) temp WHERE temp.MobileNo BETWEEN mns.BeginNo AND mns.EndNo' EXEC sp_executesql @v_Sql, N'@LogicalPoolId int',@LogicalPoolId -- 3. Return the Statistics Info SET @v_Sql = 'SELECT substring(Region,1,6) AS Region, count(UserId) AS [Count] FROM [dbo].'+@v_TmpTableName+' GROUP BY substring(Region,1,6)' EXEC(@v_Sql) END GO
自己花了比较多时间的地方, 留个标记:
1. 动态创建临时表, 表名不确定.
虽然, 这里貌似是写死的表名, 但是将来如果需要把表明作参数的传入的话, 修改成本会小很多.
这里浪费时间比较多的地方也是自己犯2的地方:
a. 在AutoDrop地方、表创建结束出加了 GO, 编译同不错. 试了挺长时间, 才发现是Go的问题, 动态Sql中语句不要添加Go.
b. 本地临时表(#TMP_TmpTableName)和全局临时表(##TMP_TmpTableName)
刚开始使用的临时表名称为 #TMP_TmpTableName, 拼接的Sql语句没错, 但是就是无法找着临时表, 即用print后的sql创建可以找到零时表, 但是用exec执行后确无法找到临时表. 试了各种办法, 后来脑子闪一下, 会不会是临时表作用域的问题, #TMP_TmpTableName临时表为"本地临时表", 只能用在当前会话中, 而##TMP_TmpTableName临时表为全局临时表, 可以用在所有会话中. 具体是不是这个原因不知, 但是用全局临时表确实可以创建出来了.
c. exec(@v_Sql)、exec @v_Sql、exec sp_executesql N'@v_Variable1 int,@v_Variable2 int',@v_Variable1,@v_Variable2用法
再排查过程中, 发现的的上述几种写法的不同. 有篇文章, 写的不错, 几行代码解释的比较清楚, 我贴一下. 这段是当时贴到Sql查询器的窗口中的, 标记捎带就贴上了, 浏览器关掉了, 没有再去找原帖地址, 见谅.
--动态sql语句基本语法 1 : 普通SQL语句可以用Exec执行 eg: Select * from tableName Exec('select * from tableName') Exec sp_executesql N'select * from tableName' -- 请注意字符串前一定要加N 2:字段名,表名,数据库名之类作为变量时,必须用动态SQL eg: declare @fname varchar(20) set @fname = 'FiledName' Select @fname from tableName -- 错误,不会提示错误,但结果为固定值FiledName,并非所要。 Exec('select ' + @fname + ' from tableName') -- 请注意 加号前后的 单引号的边上加空格 当然将字符串改成变量的形式也可 declare @fname varchar(20) set @fname = 'FiledName' --设置字段名 declare @s varchar(1000) set @s = 'select ' + @fname + ' from tableName' Exec(@s) -- 成功 exec sp_executesql @s -- 此句会报错 declare @s Nvarchar(1000) -- 注意此处改为nvarchar(1000) set @s = 'select ' + @fname + ' from tableName' Exec(@s) -- 成功 exec sp_executesql @s -- 此句正确 3. 输出参数 declare @num int, @sqls nvarchar(4000) set @sqls='select count(*) from tableName' exec(@sqls) --如何将exec执行结果放入变量中? declare @num int, @sqls nvarchar(4000) set @sqls='select @a=count(*) from tableName ' exec sp_executesql @sqls,N'@a int output',@num output select @num
使用exec sp_executesql 生成的查询计划会高效, 这是公司Sql规范里的要求, 凡是非字符串的变量, 都要求用这种方式处理
d. 最后再说说那个最长的Select, 不算很复杂. 但也有需要思考过程. 稍微标记下
1. SET @v_Sql = 'INSERT INTO [dbo].'+@v_TmpTableName+'
2. SELECT temp.UserId,temp.MobileNo,mns.Region,temp.LogicalPool
3. FROM [IICHADB].[dbo].[CODE_MobileNoSegment] mns
4. CROSS JOIN (
5. SELECT uca.UserId,uca.MobileNo,uca.LogicalPool
6. FROM ( SELECT UserId,Max(Mid) AS Mid FROM [UMSDB].[dbo].[Task] WHERE TaskStatus = 3 AND DestLogicalPool = @LogicalPoolId 7. GROUP BY UserId ) t
8. Inner JOIN [CATDB].[dbo].[CAT_UserCatalogAll] uca ON t.UserId = uca.UserId AND uca.LogicalPool = @LogicalPoolId
9. ) temp
10. WHERE temp.MobileNo BETWEEN mns.BeginNo AND mns.EndNo'
11. EXEC sp_executesql @v_Sql, N'@LogicalPoolId int',@LogicalPoolId
第1行不用多说, 拼Sql
第2,3,4,10行 是要和5,6,7,8,9 行做交叉连接, 因为是交叉连接执行比较慢, 所以想方设法减小两个表的数据量. 同时, 左表通常是数据量较少的表(据说Sql在实际执行时, 默认的会将记录少的表做主表.)
第5,6,7,8,9行实际上是个内连接, CAT_UserCatalogAll是个数据量巨大的表.
第6行的Max(Mid)、Group By UserId 是为了排除重复记录并取最新的记录, 而 TaskStatus 、DestLogicalpool 、uca.LogicalPool都是用来缩小表的数据量的多少.
比较简单, 至于能不能行. 下周DBA说了算, 先这么来吧, 周末了
==== 经过后续测试, 关于本地临时表和全局临时表的问题:
实际在程序中使用的时候, 全局临时表也是不好使的. 当程序执行完存储过程后, 全局临时表也会消失. 最后, 还是用个实体表来当临时表用吧.
参考文献:
可以创建本地和全局临时表。本地临时表仅在当前会话中可见;全局临时表在所有会话中都可见。
本地临时表的名称前面有一个编号符 (#table_name),而全局临时表的名称前面有两个编号符 (##table_name)。
SQL 语句使用 CREATE TABLE 语句中为 table_name 指定的名称引用临时表:
CREATE TABLE #MyTempTable (cola INT PRIMARY KEY)
INSERT INTO #MyTempTable VALUES (1)
如果本地临时表由存储过程创建或由多个用户同时执行的应用程序创建,则 SQL Server 必须能够区分由不同用户创建的表。为此,SQL Server 在内部为每个本地临时表的表名追加一个数字后缀。存储在 tempdb 数据库的 sysobjects 表中的临时表,其全名由 CREATE TABLE 语句中指定的表名和系统生成的数字后缀组成。为了允许追加后缀,为本地临时表指定的表名 table_name 不能超过 116 个字符。
除非使用 DROP TABLE 语句显式除去临时表,否则临时表将在退出其作用域时由系统自动除去:
当存储过程完成时,将自动除去在存储过程中创建的本地临时表。由创建表的存储过程执行的所有嵌套存储过程都可以引用此表。但调用创建此表的存储过程的进程无法引用此表。
所有其它本地临时表在当前会话结束时自动除去。
全局临时表在创建此表的会话结束且其它任务停止对其引用时自动除去。任务与表之间的关联只在单个 Transact-SQL 语句的生存周期内保持。换言之,当创建全局临时表的会话结束时,最后一条引用此表的 Transact-SQL 语句完成后,将自动除去此表。
在存储过程或触发器中创建的本地临时表与在调用存储过程或触发器之前创建的同名临时表不同。如果查询引用临时表,而同时有两个同名的临时表,则不定义针对哪个表解析该查询。嵌套存储过程同样可以创建与调用它的存储过程所创建的临时表同名的临时表。嵌套存储过程中对表名的所有引用都被解释为是针对该嵌套过程所创建的表
http://www.cnblogs.com/Hdsome/archive/2008/12/10/1351504.html
http://www.cnblogs.com/Mainz/archive/2008/12/20/1358897.html
http://www.cnblogs.com/chillsrc/archive/2008/09/24/1297806.html
#类型临时表的生命周期为当前数据库连接结束
也就是当前链接结束后就自动销毁了