一、前言
雖然我們大多數人都學習過SQL,但是經常忽略它。總是會自以為學到的已經足夠用了,從而導致我們在實際開發的過程中遇到復雜的問題后只能在檢索數據后通過傳統的代碼來完成,但是其中很多的功能利用SQL就可以輕松的辦到,所以我們開始重視SQL,它的地位不亞於C#,javascript。
二、目錄
三、正文
1.多行插入
大家都學習過INSERT,但是每次我們都只插入一條數據,如果我們需要插入多條數據呢,那么情況就會和圖1.1一樣。
圖1.1
上面是最簡單的示例,我們還可以進行一定的優化,避免循環建立和關閉連接的次數,比如下圖(圖1.2)
圖1.2
即使我們采用了圖1.2所示的方式,既然會長時間的占據帶框,並且Web服務器和數據庫服務器既然要進行多次連接,如果使用本節的知識我們就可以做到圖1.3所示的效果。
圖1.3
首先我們先來看看普通的插入語句:
1 INSERT INTO Cou_Course(CourseID,TeacherID,SemesterID,CourseName,CourseType,CourseState) 2 VALUES('235648651235423512','12312421421','12124214124','awefweagwaeg','123','124124')
每次插入一條新的數據都要重新一次,如果是批量的添加就可想而知了,而如果我們直接通過一條插入語句插入多條數據就可以這樣做:
1 INSERT INTO Cou_Course(CourseID,TeacherID,SemesterID,CourseName,CourseType,CourseState) 2 VALUES('235648651235423515','12312421421','12124214124','awefweagwaeg','123','124124'), 3 ('235648651235423514','12312421421','12124214124','awefweagwaeg','123','124124'), 4 ('235648651235423517','12312421421','12124214124','awefweagwaeg','123','124124')
當然語法也很簡單,只要用逗號來分割就可以了。雖然僅僅只是一個簡單的技巧,但是又有多少人會使用呢?
2.將其他表數據插入
解決了批量插入的問題,我們還會遇到將其他表的數據插入到另一個表中的需求,特別是在編寫存儲過程中我們可能會對數據進行處理,並把處理的結果存放在臨時表中,最后才將臨時表中的數據全部添加到實際的表中,如果利用傳統的做法會非常麻煩,而通過本節介紹的知識將可以很輕松的實現。
首先我們需要做一些簡單的准備工作,這里我們需要創建兩個表,A表(id為自增),B表跟A表一樣,然后我們在A表中隨意寫一些內容:
然后利用 INSERT INTO … SELECT … 來實現將A表的數據添加到B表中去:
INSERT INTO B(Name) SELECT Name FROM A
當然讀者看過之后會覺得非常簡單,因為我們現在僅僅只是學習這個知識點,重點在於我們能夠記住它,並能夠在后面使用它(實際開發中會比這更復雜,但是基本的東西還是不變的)
3.子查詢
在正常的使用中我們經常會遇到無法使用鏈接的情況,但是我們需要根據另一個表的情況來決定當前的SELECT,這個時候我們就需要子查詢。筆者在這里只能例舉簡單的示例,實際的需求會出現非常復雜的子查詢。這里我們還是借助上面的A、B表,首先實現的功能是檢索A表,但是條件是A表的Name要在B表中存在:
SELECT id,Name FROM A WHERE EXISTS( SELECT Name FROM B WHERE B.Name = A.Name )
上面的語句中我們在WHERE中就使用了子查詢負責去B表中查詢是否存在記錄,而EXISTS的作用就是EXISTS括號中的語句只要返回了一個或一個以上的結果則成立。所以最后我們可以看到所有的數據都呈現了。當然子查詢不僅僅可以用於條件中,我們還可以用於SELECT后,比如我們根據A表的Name去B表中查詢Name跟它一樣的id號:
SELECT (SELECT TOP 1 B.id FROM B WHERE B.Name = A.Name) FROM A
子查詢的用處非常多,也非常強大,遠不止筆者這里介紹的這么一點。
4.通用表表達式(CTE)
雖然這個名字很專業,但是實際用起來是非常簡單的,當然簡單的同時也幫助我們解決了很多的問題,最終的效果跟臨時表一樣,但是使用起來比臨時表更方便。比如下面的代碼我們將創建一個名為MyCTE的臨時表:
WITH MyCTE AS( SELECT * FROM A ) SELECT * FROM MyCTE
是不是非常簡單,當然還可以同時定義多個通用表,比如下面的代碼所示:
1 WITH MyCTE AS( 2 SELECT * FROM A 3 ), MyCTE2 AS( 4 SELECT * FROM B 5 ) 6 SELECT * FROM MyCTE,MyCTE2
多個通用表只需要用逗號分割即可,當然我們這里還涉及了一個知識點,相信有人會發覺出來。
5.MERGE指令
這個指令是SQL SERVER 2008中新增的,相比前面幾個來說比較難懂,但是作用卻非常強大,利用這個指令我們可以同時進行添加、修改和刪除,並且是由條件的。具體的實現方式就是根據源表與目標表進行對比,如果匹配則執行對應的更新操作,如果源表中存在,但是目標表不存在則執行添加操作,如果相反則執行刪除操作。下面我們將通過循序漸進的方式來介紹如何使用MERGE,首先我們需要確定目標表,因為后面的更新,添加和刪除操作都是針對目標表的,所以目標表只能是一個表不能是檢索后的數據,比如下面的代碼我們將前面我們示例中使用的A表作為目標表:
1 MERGE A AS itarget
有了目標表還不足夠,我們還要需要一個源表,用來形成對比,而源表則可以是檢索語句,因為筆者這里只是簡單的示例,所以直接檢索了B表中的數據:
1 USING( 2 SELECT * FROM B 3 ) AS isource
這樣我們就有了目標表和源表,最后合並:
1 MERGE A AS itarget 2 USING( 3 SELECT * FROM B 4 ) AS isource
接着我們需要指定對應的條件,從而根據是否符合這個條件而決定對目標表進行什么操作,比如下面的語句將判斷兩表中是否存在相同id的數據:
ON (itarget.id = isource.id)
有了條件后,我們就可以根據這個條件進行對應的操作了,筆者將在滿足這個條件后修改目標表的Name,在后面追加change字符串:
1 WHEN MATCHED THEN 2 UPDATE SET itarget.Name = itarget.Name + 'change'
最后的語句如下所示:
1 MERGE A AS itarget 2 USING( 3 SELECT * FROM B 4 ) AS isource 5 ON (itarget.id = isource.id) 6 WHEN MATCHED THEN 7 UPDATE SET itarget.Name = itarget.Name + 'change';
最后我們查看A表,發現數據都改變了:
這個時候我們在A表新添加一條數據,以滿足源表不匹配的情況,然后在原本的語句后面添加如下的語句:
WHEN NOT MATCHED BY SOURCE THEN DELETE;
我們可以猜測出,當目標表中存在源表中不存在的數據后將會刪除這條數據,所以執行后我們將看到A表新添加的數據已經被刪除了,完整的語句如下所示:
1 MERGE A AS itarget 2 USING( 3 SELECT * FROM B 4 ) AS isource 5 ON (itarget.id = isource.id) 6 WHEN MATCHED THEN 7 UPDATE SET itarget.Name = itarget.Name 8 WHEN NOT MATCHED BY SOURCE THEN 9 DELETE;
最后就是源表中存在,但是目標表中不存在的情況了,我們只需要將上面的BY SOURCE改成BY TARGET即可,通過下面這條語句我們將把源表與目標表不匹配的數據添加到目標表中去(當然我們需要提前在源表中新增一條數據):
WHEN NOT MATCHED BY TARGET THEN INSERT (Name) VALUES(isource.Name);
執行完成后我們將看到A表多了幾條數據。下面是完整的語句:
1 MERGE A AS itarget 2 USING( 3 SELECT * FROM B 4 ) AS isource 5 ON (itarget.id = isource.id) 6 WHEN MATCHED THEN 7 UPDATE SET itarget.Name = itarget.Name 8 WHEN NOT MATCHED BY SOURCE THEN 9 DELETE 10 WHEN NOT MATCHED BY TARGET THEN 11 INSERT (Name) VALUES(isource.Name);
6.窗口化函數
首先是ROW_NUMBER,顧名思義,就是給我們檢索出來的數據加上序號,舊的分頁都是采用這種方式,但是往往我們僅僅只是使用了它的一點,他還可以分塊進行標序號,比如我們將上面的A表改成如下形式:
然后采用如下所示的SQL語句,就可以按照Name進行標序號:
SELECT ROW_NUMBER() OVER(PARTITION BY A.Name ORDER BY A.id) AS 'RNUM',id FROM A
結果如下所示:
下面我們通過一段SQL以及對應的結果呈現其他的窗口化函數:
SELECT ROW_NUMBER() OVER(PARTITION BY A.Name ORDER BY A.id) AS 'RNUM', RANK() OVER(ORDER BY A.Name) AS 'RANK', DENSE_RANK() OVER(ORDER BY A.Name) AS 'DENSE RANK', NTILE(4) OVER(ORDER BY A.id) AS 'NTILE' FROM A
結果如下所示:
其中簡單介紹下NTILE,我們傳了一個4那么它會將前面1/4標記為1,然后接着標記1/4為2,以此類推。關於RANK和DENSE_RANK比較好理解,看看最后的結果就可以得出結論了。
7.分頁查詢
這是最后一節了,但是相關的語句卻很簡單,我們只要記住以下關鍵字就可以了:
OFFSET…FETCH NEXT…
比如下面的SQL語句,我們將跳過前面5條數據,獲取3條數據:
1 SELECT * FROM A 2 ORDER BY A.id 3 OFFSET 5 ROWS 4 FETCH NEXT 3 ROWS ONLY