SQLSERVER用無中生有的思想來替代游標
昨天在MSDN論壇看到一個帖子,帖子中LZ需要根據某列的值把其他列的值插入到額外列
建表腳本:

1 USE tempdb 2 GO 3 4 --建表 5 CREATE TABLE t1 6 ( 7 client VARCHAR(10) , 8 pay_level INT , 9 pay_lv_1 INT , 10 pay_lv_2 INT , 11 pay_lv_3 INT , 12 pay_lv_4 INT , 13 pay_lv_5 INT , 14 pay_lv_6 INT , 15 pay_lv_7 INT , 16 pay_lv_8 INT , 17 pay_lv_9 INT , 18 pay_lv_10 INT , 19 pay_lv_11 INT , 20 pay_lv_12 INT , 21 pay_lv_13 INT , 22 pay_lv_14 INT , 23 pay_lv_15 INT , 24 pay_lv_16 INT , 25 pay_lv_17 INT , 26 pay_lv_18 INT , 27 pay_lv_19 INT , 28 pay_lv_20 INT , 29 pay_lv_21 INT , 30 pay_lv_22 INT , 31 pay_lv_23 INT , 32 pay_lv_24 INT , 33 pay_lv_25 INT, 34 ); 35 36 37 --插入測試數據 38 DECLARE @i INT 39 SET @i = 1 40 WHILE @i < 8 41 BEGIN 42 INSERT INTO t1 ( client, pay_level, pay_lv_1, pay_lv_2, pay_lv_3, 43 pay_lv_4, pay_lv_5, pay_lv_6, pay_lv_7, pay_lv_8, 44 pay_lv_9, pay_lv_10, pay_lv_11, pay_lv_12, 45 pay_lv_13, pay_lv_14, pay_lv_15, pay_lv_16, 46 pay_lv_17, pay_lv_18, pay_lv_19, pay_lv_20, 47 pay_lv_21, pay_lv_22, pay_lv_23, pay_lv_24, 48 pay_lv_25 ) 49 SELECT 'client' + CAST(@i AS VARCHAR(10)), 50 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 51 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 52 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 53 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 54 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 55 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 56 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 57 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 58 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 59 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 60 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 61 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 62 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND() 63 SET @i=@i+1 64 65 END 66 67 SELECT * FROM t1 68 GO
圖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列

1 USE tempdb 2 GO 3 4 5 CREATE TABLE #t 6 ( 7 client VARCHAR(10) , 8 pay_level INT , 9 pay_lv_1 INT , 10 pay_lv_2 INT , 11 pay_lv_3 INT 12 ); 13 14 INSERT INTO #t ( client , 15 pay_level , 16 pay_lv_1 , 17 pay_lv_2 , 18 pay_lv_3 19 ) 20 VALUES ( 'client1' , -- client - varchar(10) 21 1, -- pay_level - int 22 10 , -- pay_lv_1 - int 23 12 , -- pay_lv_2 - int 24 14 -- pay_lv_3 - int 25 ) 26 27 28 INSERT INTO #t ( client , 29 pay_level , 30 pay_lv_1 , 31 pay_lv_2 , 32 pay_lv_3 33 ) 34 VALUES ( 'client2' , -- client - varchar(10) 35 3, -- pay_level - int 36 21 , -- pay_lv_1 - int 37 22 , -- pay_lv_2 - int 38 23 -- pay_lv_3 - int 39 ) 40 41 INSERT INTO #t ( client , 42 pay_level , 43 pay_lv_1 , 44 pay_lv_2 , 45 pay_lv_3 46 ) 47 VALUES ( 'client3' , -- client - varchar(10) 48 2, -- pay_level - int 49 30 , -- pay_lv_1 - int 50 32 , -- pay_lv_2 - int 51 33 -- pay_lv_3 - int 52 ) 53 54 SELECT * FROM #t
(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
重點是怎麽模仿游標
其實這個方法是最原始的方法,之前解決論壇問題的時候用過,想不到這次也能用上

1 USE tempdb 2 GO 3 4 --建表 5 CREATE TABLE t1 6 ( 7 client VARCHAR(10) , 8 pay_level INT , 9 pay_lv_1 INT , 10 pay_lv_2 INT , 11 pay_lv_3 INT , 12 pay_lv_4 INT , 13 pay_lv_5 INT , 14 pay_lv_6 INT , 15 pay_lv_7 INT , 16 pay_lv_8 INT , 17 pay_lv_9 INT , 18 pay_lv_10 INT , 19 pay_lv_11 INT , 20 pay_lv_12 INT , 21 pay_lv_13 INT , 22 pay_lv_14 INT , 23 pay_lv_15 INT , 24 pay_lv_16 INT , 25 pay_lv_17 INT , 26 pay_lv_18 INT , 27 pay_lv_19 INT , 28 pay_lv_20 INT , 29 pay_lv_21 INT , 30 pay_lv_22 INT , 31 pay_lv_23 INT , 32 pay_lv_24 INT , 33 pay_lv_25 INT, 34 ); 35 36 37 --插入測試數據 38 DECLARE @i INT 39 SET @i = 1 40 WHILE @i < 8 41 BEGIN 42 INSERT INTO t1 ( client, pay_level, pay_lv_1, pay_lv_2, pay_lv_3, 43 pay_lv_4, pay_lv_5, pay_lv_6, pay_lv_7, pay_lv_8, 44 pay_lv_9, pay_lv_10, pay_lv_11, pay_lv_12, 45 pay_lv_13, pay_lv_14, pay_lv_15, pay_lv_16, 46 pay_lv_17, pay_lv_18, pay_lv_19, pay_lv_20, 47 pay_lv_21, pay_lv_22, pay_lv_23, pay_lv_24, 48 pay_lv_25 ) 49 SELECT 'client' + CAST(@i AS VARCHAR(10)), 50 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 51 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 52 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 53 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 54 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 55 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 56 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 57 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 58 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 59 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 60 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 61 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND(), 62 ( 20 + 1 ) * RAND(), ( 20 + 1 ) * RAND() 63 SET @i=@i+1 64 65 END 66 67 SELECT * FROM t1 68 GO 69 70 --ALTER TABLE [t1] DROP COLUMN [pay_lv_2] 71 72 73 ----------------------------------------------------- 74 IF object_id('#ttt') IS NOT NULL 75 DROP TABLE #ttt 76 IF object_id('#temptb') IS NOT NULL 77 DROP TABLE #temptb 78 79 DECLARE @i INT 80 --用於循環的 81 SET @i = 1 82 DECLARE @pay_level INT 83 --保存pay_level字段的值 84 DECLARE @COUNT INT 85 --保存t1表的總行數值 86 DECLARE @pay_lv INT 87 --用於保存pay_lv的值 88 DECLARE @sql NVARCHAR(2000) 89 90 CREATE TABLE #ttt (ID INT IDENTITY(1,1), pay_cost INT ) 91 92 SELECT IDENTITY( INT,1,1 ) AS ID, * INTO #temptb FROM t1 93 94 95 --獲取t1表的總行數 96 SELECT @COUNT = COUNT(*) FROM [#temptb] 97 WHILE @i <= @COUNT 98 BEGIN 99 SELECT @pay_level = [pay_level] FROM [#temptb] WHERE id = @i 100 --判斷列名是否存在,不存在就插入0 101 IF 'pay_lv_' + CAST(@pay_level AS VARCHAR(200)) IN ( SELECT NAME FROM SYS.[syscolumns] ) 102 BEGIN 103 --用拼接sql的方法來獲得pay_lv列對應的值,然后插入到#ttt表 104 SET @sql = N'select ' + ' @pay_lv=pay_lv_' + CAST(@pay_level AS NVARCHAR(200)) + ' from #temptb where id=' + CAST(@i AS NVARCHAR(20)) 105 EXEC sp_executesql @sql, N'@pay_lv int output ', @pay_lv OUTPUT 106 INSERT INTO #ttt VALUES (@pay_lv) 107 END 108 ELSE 109 BEGIN 110 INSERT INTO #ttt VALUES(0) 111 END 112 SET @i = @i + 1 113 END 114 115 116 117 SELECT A.[client], A.[pay_level], B.[pay_cost] 118 FROM [#temptb] AS A 119 INNER JOIN [#ttt] AS B ON A.[ID] = B.[ID] 120 ORDER BY A.[ID] ASC 121 122 DROP TABLE [#temptb] 123 DROP TABLE [#ttt]
關鍵代碼有以下幾句
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