SQLSERVER用無中生有的思想來替代遊標

昨天在MSDN論壇看到一個帖子,帖子中LZ需要根據某列的值把其他列的值插入到額外列

帖子地址:http://social.technet.microsoft.com/Forums/zh-CN/3eac78ca-d071-4c00-afa0-ef48c8501745/sql-statementcolumn-namecolumnsql-


建表腳本

 View Code

圖1

LZ說原表就是類似上面那樣,實際表中pay_lv_會有很多列至少100列,我這裏爲了測試只建了25個pay_lv_列

而LZ希望select出來的結果是下圖那樣

 

圖2

client列和pay_level列不變,增加一個pay_cost列

pay_cost列根據pay_level列的值去取pay_lv_列的值,或者我用下面的圖片會更加明白

 

圖3

例如第6行,pay_level的值是6,那麼就去pay_lv_6這一列的值(值是20)把他放到pay_cost列裏

其他也是一樣,第二行pay_level的值是10,那就去pay_lv_10這一列的值(值是17)把他放到pay_cost列裏

如此類推


要select出圖2的結果,有下面幾種方法

1、case when

2、UNPIVOT函數

3、遊標

我這裏再建另外一個表,這個表跟原表是一樣的,只是數據沒有那麼多,pay_lv_列數只有3列

 View Code

(1)case when

1 SELECT  client,[pay_level],( CASE pay_level
2                   WHEN 1 THEN pay_lv_1
3                   WHEN 2 THEN pay_lv_2
4                   WHEN 3 THEN pay_lv_3
5                   ELSE 0
6                 END) AS 'pay_cost'
7 FROM    #t;

圖4

(2)UNPIVOT函數

 1 SELECT  * INTO #tt
 2 FROM    ( SELECT    *
 3           FROM      #t
 4         ) p UNPIVOT
 5    ( pay_cost FOR pay_lv IN ( pay_lv_1, pay_lv_2, pay_lv_3 ) )AS unpvt
 6 WHERE   CAST(RIGHT(pay_lv, 1) AS INT) = pay_level
 7 
 8 SELECT [client],[pay_level],[pay_cost] FROM [#tt]
 9 
10 DROP TABLE [#tt]

圖5

上面兩個方法:CASE WHEN和UNPIVOT函數可以用拼接SQL的方法來做,不過由於本人功力不夠,寫不出來

(3)遊標

 我不喜歡使用遊標,主要有兩個原因

1、每次用的時候,要打開筆記本看語法

2、佔用資源

 我使用了下面的sql語句來解決LZ的問題

 1 IF object_id('#ttt') IS NOT NULL
 2 DROP TABLE #ttt
 3 IF object_id('#temptb') IS NOT NULL
 4 DROP TABLE #temptb
 5 
 6 DECLARE @i INT
 7   --用於循環的
 8 SET @i = 1
 9 DECLARE @pay_level INT
10   --保存pay_level字段的值
11 DECLARE @COUNT INT
12    --保存#t1表的總行數值
13 DECLARE @pay_lv INT
14   --用於保存pay_lv的值
15 DECLARE @sql NVARCHAR(2000)
16 
17 CREATE TABLE #ttt (ID INT IDENTITY(1,1), pay_cost INT )
18 
19 SELECT  IDENTITY( INT,1,1 ) AS ID, * INTO    #temptb FROM  t1
20 
21 
22 --獲取#t1表的總行數
23 SELECT  @COUNT = COUNT(*) FROM    [#temptb]
24 WHILE @i <= @COUNT 
25     BEGIN
26         SELECT  @pay_level = [pay_level] FROM    [#temptb] WHERE   id = @i
27     --判斷列名是否存在,不存在就插入0
28         IF 'pay_lv_' + CAST(@pay_level AS VARCHAR(200)) IN ( SELECT   NAME FROM     SYS.[syscolumns] ) 
29             BEGIN
30                 --用拼接sql的方法來獲得pay_lv列對應的值,然後插入到#ttt表
31                 SET @sql = N'select ' + ' @pay_lv=pay_lv_' + CAST(@pay_level AS NVARCHAR(200)) + ' from #temptb where id=' + CAST(@i AS NVARCHAR(20))
32                 EXEC sp_executesql @sql, N'@pay_lv   int   output ', @pay_lv OUTPUT
33                 INSERT  INTO #ttt VALUES  (@pay_lv)
34             END
35         ELSE 
36             BEGIN
37                 INSERT  INTO #ttt VALUES(0)
38             END
39         SET @i = @i + 1
40     END
41 
42 
43 
44 SELECT  A.[client], A.[pay_level], B.[pay_cost]
45 FROM    [#temptb] AS A
46 INNER JOIN [#ttt] AS B ON A.[ID] = B.[ID]
47 ORDER BY A.[ID] ASC
48 
49 DROP TABLE [#temptb]
50 DROP TABLE [#ttt]


我這個sql語句也需要拼接sql來達到LZ想要的效果

不過這篇文章的重點不是拼接SQL


重點是怎麼模仿遊標

其實這個方法是最原始的方法,之前解決論壇問題的時候用過,想不到這次也能用上

 View Code


關鍵代碼有以下幾句

 1 CREATE TABLE #ttt (ID INT IDENTITY(1,1), pay_cost INT )
 2 
 3 SELECT  IDENTITY( INT,1,1 ) AS ID, * INTO    #temptb FROM  t1
 4 
 5 --獲取#t1表的總行數
 6 SELECT  @COUNT = COUNT(*) FROM    [#temptb]
 7 WHILE @i <= @COUNT 
 8 SELECT  @pay_level = [pay_level] FROM    [#temptb] WHERE   id = @i
 9 SET @i = @i + 1
10 ----------------------------------
11 SELECT  A.[client], A.[pay_level], B.[pay_cost]
12 FROM    [#temptb] AS A
13 INNER JOIN [#ttt] AS B ON A.[ID] = B.[ID]
14 ORDER BY A.[ID] ASC

 

原表是沒有自增id的,我建一個臨時表#temptb,臨時表有一個自增id,並把原表的數據全部放入臨時表

獲取臨時表的行數,用於循環

每次執行的時候根據 WHERE   id = @i 來逐行逐行獲取值,變量@i每次循環都遞增1

將獲取到的值都插入到#ttt這個臨時表裏面,然後根據ID的值做兩表連接就可以得到LZ的結果

我說的無中生有就是“在原表裏增加一個自增id方便循環,既簡單又容易理解o(∩_∩)o ”

 


判斷

我這裏還用了一句

1 IF 'pay_lv_' + CAST(@pay_level AS VARCHAR(200)) IN ( SELECT   NAME FROM     SYS.[syscolumns] ) 

用於判斷要獲取值的pay_lv_列是否存在,如果存在就插入pay_lv_列的值,如果不存在就插入0


總結

其實如果覺得某樣東西很難去實現,能不能用一個變通的方法呢?多動腦筋,辦法會有的

 

如有不對的地方,歡迎大家拍磚o(∩_∩)o

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章