程序員的SQL 金典


第一本專門爲程序員編寫的數據庫圖書
知名IT 圖書做者楊中科的又一扛鼎力做
Chinapub在線購買地址:h p: //www. chi na- pub. com/ 301651
噹噹網在線購買地址:h p: //pr oduct.dangdang. com/ pr oduct. aspx?pr oduct_i d=20368319
第一本專門爲程序員編寫的數據庫圖書
《程序員的SQL金典》
l 將子查詢、錶鏈接、數據庫語法差別等用通俗易懂、詼諧
幽默的語言講解出來
l 配合大量真實案例,學了就能用,在短期內成爲數據庫
開發高手
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
l 高度提取不一樣數據庫的共同點,仔細分析不一樣點,並給出
解決方案,同時學會MSSQLServer、MYSQL、Oracle、DB2
數據庫再也不是夢
l 國內第一本講解開窗函數實際應用的圖書
輕舉技術之「綱」,張合用之「目」,鍛造SQL 高可用性數據庫應用指南從理論到實踐,凝聚SQL
主流數據庫最前沿的技術要領。
本書特點:主要介紹SQL 的語法規則及在實際開發中的應用,而且對SQL 在MySQL、
MS SQL Server、Oracle 和DB2 中的差別進行了分析;詳細講解數據庫對增、刪、改、查等
SQL的支持並給出了相應的SQL 應用案例;透徹分析函數、子查詢、錶鏈接、不一樣DBMS中
的SQL 語法差別、SQL 調優、NULL 值處理、事務、開窗函數等高級技術;經過對實際案例
開發過程的詳細分析,使讀者掌握SQL的綜合應用技巧。
內容提要
本書主要介紹SQL以及在實際開發中的應用,而且對SQL在MYSQL、MSSQLServer、Oracle
和DB2 中的差別性進行了分析。本書分爲三部分:第一部分爲基礎篇,主要講解數據庫對
增刪改查等SQL 的支持,給出了這些SQL 的應用案例;第二部分爲進階篇,講解了函數、
子查詢、表聯接、不一樣DBMS 中SQL 語法差別、SQL 調優、NULL 值處理、事務、開窗函數
等高級技術;第三部分爲案例篇,講解了前兩部分的知識的綜合運用。此書適用於學習數據
庫編程的讀者,對於有必定數據庫開發經驗的讀者也有很是大的參考價值。
前言
市面上講解數如今據庫的書都花了不少篇幅講解數據庫的備份、受權、調優、修復、監
控等內容,這些內容是數據庫管理員(DBA)應該掌握的,而對於程序員來講更須要去掌握
的則是SQL 語句的使用。可是市面上專門講解SQL 語句的書很是少,初學者學習數據庫開
發過程當中經常被那些寫給DBA 的書弄的暈頭轉向,沒法真正快速的掌握SQL 技術;並且這
些書中講解的SQL 也經常是針對特定數據庫系統的專有實現,沒法很容易的在其餘數據庫
系統中運行,讀者須要閱讀大量的書和查閱大量的文檔才能掌握不一樣數據庫系統的使用。
本書是專門寫給程序員的,所以沒有講解備份、受權、調優、修復、監控等開發人員不
關心的內容,直接從SQL 語句入手讓讀者快速的掌握數據庫開發的技能。「面向開發人員,
講解對開發人員最有用的知識」是本書的編寫宗旨。
MYSQL、MSSQLServer、Oracle 和DB2 等都是很是流行的數據庫管理系統(DBMS),雖
然在大部分SQL語法上這些DBMS 實現都是一致的,不過在實現細節以及高級語法方面這些
DBMS的實現差別仍是很是大的。若是編寫可以兼容這些DBMS的SQL語句是開發人員常常
須要面對的問題。本書將幫助讀者從根本上解決這個問題。
不少開發人員對於SQL語句的掌握只限於簡單的SELECT、UPDATE語句,對於稍微複雜
的邏輯常常須要編寫程序代碼來完成,這不只沒法發揮數據庫的優點,並且開發出的系統性
能很是低,而若是可以使用數據庫函數、子查詢、表聯接、開窗函數等高級的SQL 特性則
能夠大大簡化系統開發的難度,而且提升系統的性能。本書將對這些高級特性進行詳細的講
解。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
本書第一、2 章介紹數據庫系統的基本知識以及基本操做;第3 章介紹Insert、Delete
和Update 語句的基本應用;第4 章對Select 語句進行全面的介紹,對模糊匹配、分組、
限制數據條數、計算字段、組合查詢等高級內容進行了重點的講解;第5 章介紹經常使用的數據
庫函數以及它們在主流DBMS中的實現差別;第6章介紹索引與約束等知識點;第七、8章
分別介紹錶鏈接、子查詢等高級查詢技術;第9 章對主流DBMS的語法差別進行分析,而且
給出瞭解決方案;第10 章介紹注入漏洞攻擊、SQL 調優、事務、自動增加字段、NULL 值處
理、開窗函數等高級話題;第11章以一個案例講解書中知識點在實際開發中的應用。
在此,我要感謝爲這本書的誕生給於我幫助的全部人。首先我要感謝CowNew 開源團
隊的朋友們一直以來的無私奉獻;感謝KingChou 在開發CowNewSQL過程當中的卓越工做,沒
有CowNewSQL 也就不會有這本書的問世;還要感謝EasyJF的蔡世友,他一直以來對開源事
業的奉獻是值得我學習的;最後我要感謝電子工業出版社的田小康經理,他的高效工做使得
本書可以順利的完成和出版。
若是您對個人書有任何意見和建議,您能夠給我發送郵件:about521@163.com,本書
相關的後續資料將會發布到CowNew 開源團隊網站(h p: //www. cown ew. com )中。
第1 章 數據庫入門 1
1.1 數據庫概述 1
1.1.1 數據庫與數據庫管理系統 1
1.1.2 數據庫能作什麼 2
1.1.3 主流數據庫管理系統介紹 2
1.2 數據庫基礎概念 5
1.2.1 Catalog 5
1.2.2 表(Table) 6
1.2.3 列(Column) 7
1.2.4 數據類型(DataType) 8
1.2.5 記錄(Record) 9
1.2.6 主鍵(PrimaryKey) 9
1.2.7 索引(Index) 10
1.2.8 表關聯 12
1.2.9 數據庫的語言——SQL 13
1.2.10 DBA 與程序員 14
第2章 數據表的建立和管理 17
2.1 數據類型 17
2.1.1 整數類型 17
2.1.2 數值類型 19
2.1.3 字符相關類型 21
2.1.4 日期時間類型 23
2.1.5 二進制類型 24
2.2 經過SQL語句管理數據表 25
2.2.1 建立數據表 25
2.2.2 定義非空約束 26
2.2.3 定義默認值 27
2.2.4 定義主鍵 27
2.2.5 定義外鍵 29
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
2.2.6 修改已有數據表 30
2.2.7 刪除數據表 31
2.2.8 受限操做的變通解決方案 31
第3 章 數據的增、刪、改 33
3.1 數據的插入 34
3.1.1 簡單的INSERT 語句 34
3.1.2 簡化的INSERT 語句 36
3.1.3 非空約束對數據插入的影響 36
3.1.4 主鍵對數據插入的影響 37
3.1.5 外鍵對數據插入的影響 38
3.2 數據的更新 38
3.2.1 簡單的數據更新 39
3.2.2 帶WHERE子句的UPDATE 語句 40
3.2.3 非空約束對數據更新的影響 41
3.2.4 主鍵對數據更新的影響 42
3.2.5 外鍵對數據更新的影響 42
3.3 數據的刪除 43
3.3.1 簡單的數據刪除 43
3.3.2 帶WHERE子句的DELETE語句 44
第4章 數據的檢索 47
4.1 SELECT基本用法 48
4.1.1 簡單的數據檢索 48
4.1.2 檢索出須要的列 49
4.1.3 列別名 51
4.1.4 按條件過濾 52
4.1.5 數據彙總 53
4.1.6 排序 56
4.2 高級數據過濾 59
4.2.1 通配符過濾 59
4.2.2 空值檢測 63
4.2.3 反義運算符 64
4.2.4 多值檢測 65
4.2.5 範圍值檢測 66
4.2.6 低效的「WHERE 1=1」 68
4.3 數據分組 72
4.3.1 數據分組入門 74
4.3.2 數據分組與聚合函數 76
4.3.3 HAVING 語句 79
4.4 限制結果集行數 81
4.4.1 MySQL 81
4.4.2 MS SQL Server 2000 82
4.4.3 MS SQL Server 2005 83
4.4.4 Oracle 84
4.4.5 DB2 86
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
4.4.6 數據庫分頁 88
4.5 抑制數據重複 90
4.6 計算字段 91
4.6.1 常量字段 92
4.6.2 字段間的計算 93
4.6.3 數據處理函數 95
4.6.4 字符串的拼接 97
4.6.5 計算字段的其餘用途 103
4.7 不從實體表中取的數據 105
4.8 聯合結果集 107
4.8.1 簡單的結果集聯合 108
4.8.2 聯合結果集的原則 110
4.8.3 UNION ALL 112
4.8.4 聯合結果集應用舉例 114
第5章 函數 119
5.1 數學函數 122
5.1.1 求絕對值 122
5.1.2 求指數 122
5.1.3 求平方根 123
5.1.4 求隨機數 123
5.1.5 舍入到最大整數 125
5.1.6 舍入到最小整數 126
5.1.7 四捨五入 127
5.1.8 求正弦值 128
5.1.9 求餘弦值 129
5.1.10 求反正弦值 129
5.1.11 求反餘弦值 130
5.1.12 求正切值 130
5.1.13 求反正切值 131
5.1.14 求兩個變量的反正切 131
5.1.15 求餘切 132
5.1.16 求圓周率π值 132
5.1.17 弧度制轉換爲角度制 133
5.1.18 角度制轉換爲弧度制 134
5.1.19 求符號 134
5.1.20 求整除餘數 135
5.1.21 求天然對數 136
5.1.22 求以10爲底的對數 136
5.1.23 求冪 137
5.2 字符串函數 137
5.2.1 計算字符串長度 138
5.2.2 字符串轉換爲小寫 138
5.2.3 字符串轉換爲大寫 139
5.2.4 截去字符串左側空格 139
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
5.2.5 截去字符串右側空格 140
5.2.6 截去字符串兩側的空格 141
5.2.7 取子字符串 143
5.2.8 計算子字符串的位置 144
5.2.9 從左側開始取子字符串 145
5.2.10 從右側開始取子字符串 146
5.2.11 字符串替換 147
5.2.12 獲得字符的ASCII 碼 148
5.2.13 獲得一個ASCII碼數字對應的字符 149
5.2.14 發音匹配度 151
5.3 日期時間函數 153
5.3.1 日期、時間、日期時間與時間戳 153
5.3.2 主流數據庫系統中日期時間類型的表示方式 154
5.3.3 取得當前日期時間 154
5.3.4 日期增減 157
5.3.5 計算日期差額 166
5.3.6 計算一個日期是星期幾 172
5.3.7 取得日期的指定部分 177
5.4 其餘函數 183
5.4.1 類型轉換 183
5.4.2 空值處理 188
5.4.3 CASE函數 191
5.5 各數據庫系統獨有函數 194
5.5.1 MySQL中的獨有函數 195
5.5.2 MS SQL Server中的獨有函數 202
5.5.3 Oracle中的獨有函數 206
第6章 索引與約束 209
6.1 索引 209
6.2 約束 211
6.2.1 非空約束 211
6.2.2 惟一約束 212
6.2.3 CHECK約束 217
6.2.4 主鍵約束 221
6.2.5 外鍵約束 224
第7章 錶鏈接 233
7.1 錶鏈接簡介 236
7.2 內鏈接(INNER JOIN) 236
7.3 不等值鏈接 240
7.4 交叉鏈接 241
7.5 自鏈接 245
7.6 外部鏈接 248
7.6.1 左外部鏈接 250
7.6.2 右外部鏈接 251
7.6.3 全外部鏈接 252
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
第 8章 子查詢 255
8.1 子查詢入門 261
8.1.1 單值子查詢 261
8.1.2 列值子查詢 263
8.2 SELECT列表中的標量子查詢 265
8.3 WHERE 子句中的標量子查詢 267
8.4 集合運算符與子查詢 270
8.4.1 IN 運算符 270
8.4.2 ANY和SOME 運算符 272
8.4.3 ALL 運算符 274
8.4.4 EXISTS 運算符 275
8.5 在其餘類型SQL 語句中的子查詢應用 277
8.5.1 子查詢在INSERT語句中的應用 277
8.5.2 子查詢在UPDATE語句中的應用 283
8.5.3 子查詢在DELETE語句中的應用 285
第9章 主流數據庫的SQL語法差別解決方案 287
9.1 SQL語法差別分析 287
9.1.1 數據類型的差別 287
9.1.2 運算符的差別 288
9.1.3 函數的差別 289
9.1.4 經常使用SQL的差別 289
9.1.5 取元數據信息的差別 290
9.2 消除差別性的方案 293
9.2.1 爲每種數據庫編寫不一樣的SQL語句 293
9.2.2 使用語法交集 294
9.2.3 使用SQL實體對象 294
9.2.4 使用ORM工具 295
9.2.5 使用SQL翻譯器 296
9.3 CowNewSQL翻譯器 299
9.3.1 CowNewSQL支持的數據類型 299
9.3.2 CowNewSQL支持的SQL 語法 300
9.3.3 CowNewSQL支持的函數 305
9.3.4 CowNewSQL的使用方法 309
第10 章 高級話題 313
10.1 SQL注入漏洞攻防 313
10.1.1 SQL注入漏洞原理 313
10.1.2 過濾敏感字符 314
10.1.3 使用參數化SQL 315
10.2 SQL調優 316
10.2.1 SQL調優的基本原則 317
10.2.2 索引 317
10.2.3 全表掃描和索引查找 318
10.2.4 優化手法 318
10.3 事務 324
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
10.3.1 事務簡介 324
10.3.2 事務的隔離 325
10.3.3 事務的隔離級別 326
10.3.4 事務的使用 327
10.4 自動增加字段 327
10.4.1 MySQL 中的自動增加字段 327
10.4.2 MS SQL Server 中的自動增加字段 328
10.4.3 Oracle中的自動增加字段 329
10.4.4 DB2中的自動增加字段 332
10.5 業務主鍵與邏輯主鍵 333
10.6 NULL 的學問 334
10.6.1 NULL 與比較運算符 336
10.6.2 NULL 和計算字段 337
10.6.3 NULL 和字符串 338
10.6.4 NULL 和函數 339
10.6.5 NULL 和聚合函數 339
10.7 開窗函數 340
10.7.1 開窗函數簡介 342
10.7.2 PARTITION BY 子句 344
10.7.3 ORDER BY 子句 346
10.7.4 高級開窗函數 353
10.8 WITH 子句與子查詢 360
第11 章 案例講解 363
11.1 報表製做 371
11.1.1 顯示制單人詳細信息 371
11.1.2 顯示銷售單的詳細信息 373
11.1.3 計算收益 374
11.1.4 產品銷售額統計 378
11.1.5 統計銷售記錄的份額 379
11.1.6 爲採購單分級 380
11.1.7 檢索全部重疊日期銷售單 383
11.1.8 爲查詢編號 385
11.1.9 標記全部單內最大銷售量 386
11.2 排序 389
11.2.1 非字段排序規則 389
11.2.2 隨機排序 390
11.3 表間比較 391
11.3.1 檢索製做過採購單的人制做的銷售單 391
11.3.2 檢索沒有製做過採購單的人制做的銷售單 392
11.4 表複製 394
11.4.1 複製源表的結構並複製表中的數據 394
11.4.2 只複製源表的結構 395
11.5 計算字符在字符串中出現的次數 396
11.6 去除最高分、最低分 396
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
11.6.1 去除全部最低、最高值 397
11.6.2 只去除一個最低、最高值 397
11.7 與日期相關的應用 398
11.7.1 計算銷售確認日和制單日之間相差的天數 398
11.7.2 計算兩張銷售單之間的時間間隔 399
11.7.3 計算銷售單制單日期所在年份的天數 401
11.7.4 計算銷售單制單日期所在月份的第一天和最後一天 402
11.8 結果集轉置 403
11.8.1 將結果集轉置爲一行 404
11.8.2 把結果集轉置爲多行 406
11.9 遞歸查詢 410
11.9.1 Oracle中的CONNECT BY 子句 410
11.9.2 Oracle中的SYS_CONNECT_BY_PATH()函數 414
11.9.3 My SQL Server 和DB2 中遞歸查詢 415
附錄A 經常使用數據庫系統的安裝和使用 417
A.1 DB2 的安裝和使用 417
A.2 MySQL 的安裝和使用 429
A.3 Oracle的安裝和使用 441
A.4 Microso SQL Ser ver 的安裝和使用 452
第一章 數據庫入門
本章介紹數據庫的入門知識,首先介紹什麼是數據庫,而後介紹數據庫中的一些基本概
念,接着介紹操縱數據庫的不一樣方式,最後介紹操縱數據庫時使用的語言SQL,在章節中我
們還將穿插一些很是有趣的話題。
1.1數據庫概述
廣義上來說,數據庫就是「數據的倉庫」,計算機系統常常用來處理各類各樣大量的數
據,好比使用計算機系統收集一個地區的人口信息、檢索符合某些條件的當地人口信息、當
一我的去世後還要從系統中刪除此人的相關信息。咱們能夠自定義一個文件格式,而後把人
口數據按照這個格式保存到文件中,當須要對已經存入的數據進行檢索或者修改的時候就重
新讀取這個文件而後進行相關操做。這種數據處理方式存在不少問題,好比須要開發人員熟
悉操做磁盤文件的函數、開發人員必須編寫複雜的搜尋算法才能快速的把數據從文件中檢索
出來、當數據格式發生變化的時候要編寫複雜的文件格式升級程序、很難控制併發修改。
在計算機系統在各個行業開始廣泛應用之後,計算機專家也遇到了一樣的問題,所以他
們提出了數據庫理論,從而大大簡化了開發信息系統的難度。數據庫理論的鼻祖是Charles
W.Bachman,他也所以得到了1973年的圖靈獎。IBM的Ted Codd 則首先提出了關係數據庫
理論,並在IBM 研究機構開發原型,這個項目就是R 系統,而且使用SQL 作爲存取數據表
的語言,R系統對後來的Oracle、Ingres和DB2 等關係型數據庫系統都產生了很是重要的影
響。
1.1.1 「數據庫」與「數據庫管理系統」
前面咱們講到數據庫就是「數據的倉庫」,咱們還須要一套系統來幫助咱們管理這些數
據,好比幫助咱們查詢到咱們須要的數據、幫咱們將過期的數據刪除,這樣的系統咱們稱之
爲數據庫管理系統(Database Management System,DBMS)。有時候不少人也將DBMS 簡稱
爲「數據庫」,可是必定要區分「數據庫」的這兩個不一樣的意思。
數據庫管理系統是一種操縱和管理數據庫的系統軟件,是用於創建、使用和維護數據庫。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
它對數據庫進行統一的管理和控制,以保證數據庫的安全性和完整性。用戶經過DBMS 訪問
數據庫中的數據,數據庫管理員也經過DBMS進行數據庫的維護工做。它提供多種功能,可
使多個應用程序和用戶用不一樣的方法在同時或不一樣時刻去創建,修改和詢問數據庫。它使用
戶能方便地定義和操縱數據,維護數據的安全性和完整性,以及進行多用戶下的併發控制和
恢復數據庫。通俗的說,DBMS就是數據庫的大管家,須要維護什麼數據、查找什麼數據的
話找它告訴他了,它會幫你辦的乾淨利落。
1.1.2 數據庫能作什麼
數據庫可以幫助你儲存、組織和檢索數據。數據庫以必定的邏輯方式組織數據,當咱們
要對數據進行增刪改查的時候數據庫能很是快速的完成所要求的操做;同時數據庫隱藏了數
據的組織形式,咱們只要對數據的屬性進行描述就能夠了,當咱們要對數據庫中的數據進行
操做的時候只要告訴「作什麼」(What to do)就能夠了,DBMS會決定一個比較好的完成操
做的方式,也就是咱們無需關心「怎麼作」(How to do),這樣咱們就能從數據存儲的底層
中脫身出來,把更多精力投入到業務系統的開發中。
數據庫容許咱們建立規則,以確保在增長、更新以及刪除數據的時候保證數據的一致性;
數據庫容許咱們指定很是複雜的數據過濾機制,這樣不管業務規則多麼複雜,咱們都能輕鬆
應對;數據庫能夠處理多用戶併發修改問題;數據庫提供了操做的事務性機制,這樣能夠保
證業務數據的萬無一失。
1.1.3 主流數據庫管理系統介紹
目前有許多DBMS產品,如DB二、Oracle、Microso SQL Ser ver 、Sybase SQLServer、Informix、
MySQL 等,它們在數據庫市場上各自佔有一席之地。下面簡要介紹幾種經常使用的數據庫管理
系統。
(1)DB2
DB2 第一種使用使用SQL 的數據庫產品。DB2 於 1982 年首次發佈,如今已經能夠用
在許多操做系統平臺上,它除了能夠運行在OS/390 和VM 等大型機操做系統以及中等規模
的AS/400 系統以外,IBM 還提供了跨平臺(包括基於UNIX 的LINUX,HP-UX,Sun Solaris,
以及SCO UnixWare;還有用於我的電腦的Windows 2000系統)的DB2產品。應用程序能夠
經過使用微軟的ODBC 接口、Java 的JDBC接口或者CORBA 接口代理來訪問DB2數據庫。
DB2 有不一樣的版本,好比DB2 Everyplace是爲移動用戶提供的一個內存佔用小且性能出
色的版本;DB2 for z/OS 則是爲主機系統提供的版本;Enterprise Server Edi on( ESE) 是一種適
用於中型和大型企業的版本; Workgroup Server Edi on( WS E) 主要適用於小型和中型企業,
它提供除大型機鏈接以外的全部ESE 特性;而DB2 Express則是爲開發人員提供的能夠免費
使用的版本。
IBM是最先進行關係數據庫理論研究和產品開發的公司,在關係數據庫理論方面一直走
在業界的前列,因此DB2 的功能和性能都是很是優秀的,不過對開發人員的要求也比其餘
數據庫系統更高,使用不當很容易形成宕機、死鎖等問題;DB2 在SQL的擴展方面比較保守,
不少其餘數據庫系統支持的SQL 擴展特性在DB2 上都沒法使用;同時DB2 對數據的類型要
求也很是嚴格,在數據類型不匹配的時候會報錯而不是進行類型轉換,並且若是發生精度溢
出、數據超長等問題的時候也會直接報錯,這雖然保證了數據的正確性,可是也使得基於
DB2的開發更加麻煩。所以,不少開發人員稱DB2 爲「最難用的數據庫系統」。
(2)Oracle
Oracle 是和DB2 同時期發展起來的數據庫產品,也是第二個採用SQL 的數據庫產品。
Oracle 從DB2 等產品中吸收到了不少優勢,同時又避免了IBM 的官僚體制與過分學術化,
大膽的引進了許多新的理論與特性,因此Oracle 不管是功能、性能仍是可用性都是很是好
的。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
(3)Microso SQL Ser ver
Microso SQL Ser ver 是微軟推出的一款數據庫產品。細心的讀者也許已經發現咱們前面
提到了另一個名字很是類似的Sybase SQLServer,這裏的名字類似並非一種巧合,這還
要從Microso SQL Ser ver 的發展史談起。
微軟當初要進軍圖形化操做系統,因此就開始和IBM「合做」開發OS/2,最終固然無
疾而終,可是微軟就很快的推出了本身的新一代視窗操做系統;而當微軟發現數據庫系統這
塊新的市場的時候,微軟沒有本身重頭開發一個數據庫系統,而是找到了Sybase來「合做」
開發基於OS/2 的數據產品,固然微軟達到目的之後就當即中止和Sybase的合做了,於1995
年推出了本身的Microso SQL Ser ver 6. 0 ,通過幾年的發展終於在1998年推出了轟動一時的
Microso SQL Ser ver 7. 0 ,也正是這一個版本使得微軟在數據庫產品領域有了一席之地。正因
爲這段「合做」歷史,因此使得Microso SQL Ser ver 和Sybase SQLServer在不少地方很是類
似,好比底層採用的TDS協議、支持的語法擴展、函數等等。
微軟在2000 年推出了Microso SQL Ser ver 2000 ,這個版本繼續穩固了Microso SQL
Server 的市場地位,因爲Windows操做系統在我的計算機領域的普及,Microso SQL Ser ver
理所固然的成爲了不少數據庫開發人員的接觸的第一個並且有可能也是惟一一個數據庫產
品,不少人甚至在「SQL Server」和「數據庫」之間劃上了等號,並且用「SQL」一次來專指
Microso SQL Ser ver ,可見微軟的市場普及作的仍是很是好的。作足足夠的市場之後,微軟
在2005年「審時度勢」的推出了Microso SQL Server 2005 ,並將於2008年發佈新一代的
Microso SQL Ser ver 2008 。
Microso SQL Ser ver 的可用性作的很是好,提供了不少了外圍工具來幫助用戶對數據庫
進行管理,用戶甚至無需直接執行任何SQL 語句就能夠完成數據庫的建立、數據表的建立、
數據的備份/恢復等工做;Microso SQL Ser ver 的開發者社區也是很是龐大的,所以有衆多
能夠參考的學習資料,學習成本很是低,這是其餘數據庫產品作不具備的優點;同時從
Microso SQL Ser ver 2005 開始開發人員能夠使用任何支持.Net 的語言來編寫存儲過程,這進
一步下降了Microso SQL Ser ver 的使用門檻。
不過正如微軟產品的一向風格,Microso SQL Server 的劣勢也是很是明顯的:只能運行
於Windows操做系統,所以咱們沒法在Linux、Unix上運行它;無論微軟給出什麼樣的測試
數據,在實際使用中Microso SQL Ser ver 在大數據量和大交易量的環境中的表現都是不盡人
意的,當企業的業務量到達一個水平後就要考慮升級到Oracle或者DB2 了。
(4)MySQL
MySQL 是一個小型關係型數據庫管理系統,開發者爲瑞典MySQL AB公司。目前MySQL
被普遍地應用在中小型系統中,特別是在網絡應用中用戶羣更多。MySQL 沒有提供一些中
小型系統中不多使用的功能,因此MySQL 的資源佔用很是小,更加易於安裝、使用和管理。
因爲MySQL 是開源的,因此在PHP 和Java 開發人員心中更是首選的數據庫開發搭檔,
目前Internet 上流行的網站構架方式是LAMP(Linux+Apache+MySQL+PHP),即便用Linux做
爲操做系統,Apache做爲Web 服務器,MySQL做爲數據庫,PHP做爲服務器端腳本解釋器。
MySQL 目前還很難用於支撐大業務量的系統,因此目前MySQL 大部分仍是用來運行非
核心業務;同時因爲MySQL 在國內沒有足夠的技術支持力量,因此對MySQL的技術支持工
做是由ISV 或者系統集成商來承擔,這也致使部分客戶對MySQL 比較抵制,他們更傾向於
使用有更強技術支持力量的數據庫產品。
1.2 數據庫基礎概念
要想使用數據庫,咱們必須熟悉一些基本概念,這些概念包括:Catalog、表、列、數據
類型、記錄、主鍵以及表關聯等等。
1.2.1 Catalog
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
數據庫就是數據的倉庫,而DBMS 是數據庫的「管理員」。一些企業即生產食品又生產
農用物資,這些產品都要保存到倉庫中,同時企業內部也有一些辦公用品須要保存到倉庫中。
若是這些物品都保存到同一個倉庫中的話會形成下面的問題:
l 不便於管理。食品的保存和複印紙的保存須要的保存條件是不一樣的,食品須要低溫保鮮
而複印紙則須要除溼,不一樣類的物品放在一塊兒加大了管理的難度;
l 可能會形成貨位衝突。食品要防止陽光直射形成的變質,所以要擺放到背陰面,同時爲
了防止受潮,也要把它們擺放到高處;辦公用膠片也要避免陽光直射,因此一樣要擺放
到背陰面,並且膠片也要防潮,因此一樣要把它們擺放到高處。這就形成兩種貨物佔據
的貨位相沖突了。
l 會有安全問題。因爲全部物品都放到一個倉庫中沒有進行隔離,因此來倉庫領取辦公用
品的人員可能會順手牽羊將食品偷偷帶出倉庫。
既然都是「倉庫」,那麼數據庫系統也存在相似問題。若是企業將人力資源數據和核心
業務數據都保存到一個數據庫中一樣會形成下面的問題:
l 不便於管理。爲了防止數據丟失,企業須要對數據進行按期備份,不過和核心業務數據
比起來人力資源數據的重要性要稍差,因此人力資源數據只要一個月備份一次就能夠了,
而核心業務數據則須要天天都備份。若是將這兩種數據保存在一個數據庫中會給備份工
做帶來麻煩。
l 可能會形成命名衝突。好比人力資源數據中須要將保存員工數據的表命名爲Persons,
而核心業務數據也要將保存客戶數據的表也命名爲Persons,這就會相沖突了。
l 會有數據安全問題。因爲全部的數據都保存在一個數據庫中,這樣人力資源系統的用戶
也能夠訪問核心業務系統中的數據,很容易形成數據安全問題。
顯而易見,對於上邊提到的多種物品保存在一個倉庫中的問題,最好的解決策略就是使
用多個倉庫,食品保存在食品倉庫中,農用物資保存在農用物資倉庫中,而辦公用品則保存
在辦公用品倉庫中,這樣就能夠解決問題了。問了解決一樣的問題,DBMS 也採用了多數據
庫的方式來保存不一樣類別的數據,一個DBMS能夠管理多個數據庫,咱們將人力資源數據保
存在HR 數據庫中,而將核心業務數據保存在BIZ 數據庫中,咱們將這些不一樣數據庫叫作
Catalog(在有的DBMS 中也稱爲Database,即數據庫)。採用多Catalog 之後能夠給咱們帶
來以下好處:
l 便於對各個Catalog進行個性化管理。DBMS都容許咱們指定將不一樣的Catalog保存在不
同的磁盤上,因爲人力資源數據相對次要一些,所以咱們能夠將HR保存在普通硬盤上,
而將BIZ保存在RAID硬盤上。咱們還能夠對每一個Catalog所能佔據的最大磁盤空間、日
志大小甚至優先級進行指定,這樣就能夠針對不一樣的業務數據進行個性化定製了。
l 避免了命名衝突。同一個Catalog 中的表名是不容許重複的,而不一樣Catalog 中的表名
則是能夠重複的,這樣HR中能夠有Persons表,而BIZ中也能夠有Persons表,兩者結
構能夠徹底不相同,保存的數據也不會互相干擾。
l 安全性更高。DBMS 容許爲不一樣的Catalog 指定不一樣的用戶,而且能夠限定用戶能訪問
的Catalog。好比用戶hr123 只能訪問HR,而用戶sales001 只能訪問BIZ。這就大大加
強了系統數據的安全性。
1.2.2 表(Table)
雖然咱們已經將不一樣用途的物品保存在不一樣的倉庫中了,可是在同一個倉庫中數據的保
存仍然存在問題。好比食品分爲熟食、生肉、大米等,若是把他們隨意的堆放在一塊兒,就會
形成咱們沒法很容易的對這些食品進行管理,當要對大米進行提貨的話就必須在一堆的食品
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
中翻來翻去。解決這個問題的方法就是將倉庫劃分爲不一樣的區域,熟食保存在熟食區,生肉
保存在生肉區,而大米則保存在大米區。
DBMS 中也存在相似的問題,雖然咱們將核心業務數據保存在BIZ 數據庫中了,可是核
心業務數據也有不少不一樣類型的數據,好比客戶資料、商品資料、銷售員資料等,若是將這
些數據混雜在一塊兒的話將會管理起來很是麻煩,好比咱們要查詢全部客戶資料的話就必須將
全部數據查詢一遍。解決這個問題的方法就是將不一樣類型的資料放到不一樣的「區域」中,我
們將這種區域叫作「表」(Table)。客戶資料保存到Customers表中,將商品資料保存在Goods
表中,而將銷售員資料保存在SalesMen表中,這樣當須要查找商品的時候只要到Goods 表
中查找就能夠了。
1.2.3 列(Column)
一樣是生肉,不一樣的生肉又有不一樣的特性,有的生肉是裏脊肉,有的生肉是前臀尖,這
塊生肉是18 公斤,而那塊生肉是12 公斤,這塊生肉是12.2 元/公斤,而那塊生肉是13.6
元/公斤。每塊肉都有各自的不一樣的特性,這些特性包括取肉部位、重量、單價。若是不對
每塊肉標註這些特性數據的話,當提貨人要咱們將全部裏脊肉出庫的話咱們就很是麻煩了。
解決這個問題的方法就是製做一些標籤,在這個標籤上標明取肉部位、重量、單價,這樣要
提取貨物就會很是方便了。
不只如此,標籤的格式也要統一,若是第一塊生肉的標籤內容是:
另外一塊生肉的標籤內容是:
採用這種標籤因爲沒有統一的格式,因此閱讀起來很是麻煩,要靠人工去分辨,錯誤率
很是高。若是咱們規定一個統一的標籤格式,好比下面的標籤:
取肉部位
重量
單價(元/公斤)
這樣每塊肉的標籤就能夠按照這個格式來填寫了:
取肉部位 裏脊肉
重量 15.6
單價(元/公斤) 13.2
這種格式閱讀起來很是方便,若是引入自動識別設備的話,甚至能夠實現自動化的物品
分揀。
在數據庫的表中保存的數據也有相似問題,若是不規定格式的話,表中的數據也會很是
閱讀,若是一個員工的資料在表中保存的內容爲:
每市斤8.6
元,前臀尖,
13.6 公斤
的,。
這塊肉是
15.6 公斤的
裏脊肉,13.2
元/公斤。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
另一個員工的資料在表中保存的內容爲:
一般,以這種不標準的格式保存形成數據十分混亂,想要從數據庫中取出合適的數據仍
然很是麻煩。爲了解決這個問題,咱們規定下面這種標準的格式:
姓名
部門
入職時間
這裏的「姓名」、「部門」和「入職時間」就被稱爲員工表的列(Column),有時候也叫
作字段(Field),每一個列描述了數據的一個特性。
1.2.4 數據類型(DataType)
上面咱們爲員工表規定了「姓名」、「部門」和「入職時間」三個列,這樣只要按照這個
格式進行數據填寫就能夠了,可是這裏仍然有一個問題,那就是咱們無法限定用戶向表中填
寫什麼數據,好比用戶填寫成下面的格式:
姓名 33
部門 12.3
入職時間 信息中心
顯然姓名不該該爲一個數字33;不可能有一個名稱爲「12.3」的部門;入職時間更不可
能是「信息中心」。所以咱們必須規則每一列中填寫的數據的格式:姓名必須填寫漢字,最
短2個漢字,最長5個漢字;部門必須填寫「產品開發部」、「技術支持部」、「產品實施部」、
「人力資源部」中的一個;入職時間必須填寫爲正確的時間格式。
這裏就規定了各個列的數據類型(DataType),數據類型規定了一個列中能填寫什麼類
型的數據,減小了不規範數據出現的概率。
除了能夠對數據進行規範以外,數據類型還有下面的做用:
l 提升效率。對不一樣的數據賦予不一樣的類型可以使得數據庫更好的對數據進行存儲和管理,
從而減小空間佔用而且提供數據的訪問速度。好比,若是將數字123454321 以文本類
型存儲的話將會佔用9字節的存儲空間,而以整數類型保存的話將只須要佔用4字節的
存儲空間。
l 可以肯定對數據進行操做所須要的正確處理方式。好比若是是整數類型,那麼123+234
被解釋爲兩個整數的加法運算,因此其結果是357;若是是文本類型,那麼123+234則
會被解釋爲兩個字符串的相連操做,因此其結果是123234。
1.2.5 記錄(Record)
記錄有能夠被稱爲行(Row),能夠通俗的認爲它是數據表中的一行數據。以員工表爲
例,一個公司的員工表中的數據是這樣的:
王二小,技術
支持部,入職
是2005 年7
月。
2003 年5 月
入職,是產品
開發部的,姓
名馬小虎。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
姓名 部門 入職時間
馬小虎 產品開發部 2003 年5 月22日
王二小 技術支持部 2005 年7 月17日
白展堂 後勤部 1998 年3 月27日
錢長貴 銷售部 2001 年3 月3 日
李達最 後勤部 2005 年11月11 日
這裏每一行數據就表明一個員工的資料,這樣的一行數據就叫作一條記錄。表是由行和
列組成的一張二維表,這就是關係數據庫中最基本的數據模型。
1.2.6 主鍵(PrimaryKey)
員工表中的每一行記錄表明了一個員工,通常員工的名字就能惟一標識這一個員工,但
是名字也是有可能重複的,這時咱們就要爲每一名員工分配一個惟一的工號:
工號 姓名 部門 入職時間
001 馬小虎 產品開發部 2003年5月22 日
002 王二小 技術支持部 2005年7月17 日
003 白展堂 後勤部 1998年3月27 日
004 錢長貴 銷售部 2001年3月3日
005 李達最 後勤部 2005年11月11日
006 王二小 產品開發部 2005年3月22 日
這樣就能夠經過這個工號來惟一標識一名員工了。當老闆下令說「把王二小提高爲副總」
的時候,咱們就要問「公司有兩個王二小,您要提高哪個?」,老闆能夠說「技術支持部
的王二小」,可是更好的方式,那就是說「提高工號爲的002 員工爲副總」,由於只有002
這個工號才能惟一標識一名員工。這裏的「工號」被稱爲員工表的「主鍵」(PrimaryKey),
因此咱們能夠說能惟一標識一行記錄的字段就是此表的主鍵。
有的公司比較懶惰,不想爲員工分配工號,只是硬性規定:一個部門中員工的姓名不能
重複,有姓名重複的必須調換到其它部門。這樣「部門」和「姓名」這兩個字段加在一塊兒就
能惟一標識一名員工了,這裏的「部門」和「姓名」兩個字段就被稱爲「複合主鍵」,也就
是任何一個字段都不能惟一標識一行數據,只有構成「複合主鍵」的全部字段組合起來才能
惟一標識這一行數據。
在大多數DBMS 中並無強制規定一個表必須有主鍵,也就是一個表能夠沒有主鍵,但
是爲一個數據表指定一個主鍵是一個很是好的習慣。在後邊的章節咱們將提到用一個無心義
的字段作主鍵將會更加有利於系統的可擴展性。
1.2.7 索引(Index)
無索引的表就是一個無序的行集。好比下面的人員表中有一些數據:
編號 姓名 年齡 身高
001 莫小貝 14 1.33
002 佟湘玉 23 1.77
003 白展堂 17 1.90
004 李秀蓮 13 1.68
005 郭芙蓉 23 1.68
006 邢育森 23 1.72
007 呂秀才 23 1.72
008 燕小六 13 1.44
009 楊蕙蘭 23 1.69
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
010 郭巨俠 14 1.98
011 婁之獻 13 1.62
012 邱小東 17 1.35
這個表上沒有索引,所以若是咱們查找年齡等於17的人員時,必須查看錶中的每一行,看它是
否與所需的值匹配。這是一個全表掃描,很慢,若是表中只有少數幾個記錄與搜索條件相匹配,則其
效率是至關低的。
若是咱們常常要查詢某個年齡的人員,必須查看錶中的每一行,看它是否與所需的值匹
配。這是一個全表掃描,很慢,若是表中只有少數幾個記錄與搜索條件相匹配,則其效率是
至關低的。
若是咱們爲年齡列建立一個索引,注意這裏的索引所採用的值是排序的:
假如咱們要查找年齡爲13 歲的全部人員,那麼能夠掃描索引,結果得出前3行,當到
達年齡爲14 的行的時候,咱們發現它是一個比咱們正在查找的年齡要大的人員。索引值是
排序的,所以在讀到包含14 的記錄時,咱們知道不會再有匹配的記錄,能夠退出了。若是
查找一個值,它在索引表中某個中間點之前不會出現,那麼也有找到其第一個匹配索引項的
定位算法,而不用進行表的順序掃描(如二分查找法)。這樣,能夠快速定位到第一個匹配
的值,以節省大量搜索時間。
能夠把索引想像成漢語字典的的按筆畫查找的目錄。漢語字典中的漢字是按拼音的順序
排列在書頁中的,若是要查詢筆畫數爲18 的漢字的話就必須挨個查找每一個漢字來比較每一個
漢字的筆畫數,這種速度是讓人沒法忍受的。而若是咱們建立一個按筆畫查找的目錄:將筆
畫爲5 的漢字列出來,將筆畫爲6 的漢字列出來……,這樣當咱們要查詢筆畫數爲18 的漢
字的話只要來查找這個目錄就能夠很是快速的查找到須要的數據了。
雖然索引能夠提升數據查詢的速度,可是任何事物都是雙刃劍,它也有一些缺點:
索引佔據必定磁盤空間,就像有按筆畫查找的目錄的書會比沒有這種目錄的書頁數要多一些。
索引減慢了數據插入和刪除的速度。由於每次插入和刪除的時候都須要更新索引,一個
表擁有的索引越多,則寫操做的平均性能降低就越大。
1.2.8 表關聯
咱們來爲貨物建一張表,其中包含規格、名稱、生產廠家等等信息,以下:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
編號 名稱 規格 生產廠家 廠家地址 廠家電話
001 生肉 優質 七俠鎮肉聯

西涼河路3

5555-123456
002 玉米腸 簡裝 七俠鎮肉聯

西涼河路3

5555-123456
003 尿素 60公斤裝 六扇門化工

漢中工業區 5555-654321
004 打印紙 16開 錢氏紙業 縣政府對過 5555-123654
005 磷酸二銨 30公斤裝 六扇門化工

漢中工業區 5555-654321
能夠看到這裏存在大量冗餘信息,好比廠家的名稱、地址、電話等就在表中重複屢次,
這會帶來以下的問題:
l 信息冗餘佔據空間。數據的存儲是佔據必定的空間的,若是存在過多冗餘信息將會使得
存儲系統的利用率太低。
l 信息冗餘使得新數據的加入變得麻煩。每次錄入新的貨物的話必須把廠家地址、廠家電
話等信息從新錄入一次。
l 信息冗餘使得維護數據的正確性變得困難。若是七俠鎮肉聯廠遷址了,那麼必須將表中
全部七俠鎮肉聯廠的廠家地址都要更新一遍。
解決的方法就是即將廠家的信息在一個新的表中維護。咱們建立下邊的廠家表:
廠家編號 廠家名稱 廠家地址 廠家電話
001 七俠鎮肉聯廠 西涼河路3號 5555-123456
002 六扇門化工廠 漢中工業區 5555-654321
003 錢氏紙業 縣政府對過 5555-123654
這裏咱們爲每一個廠家指定了一個廠家編號作爲主鍵,這個編號就能夠惟一標識一個廠家。
有了廠家信息表,貨物表就能夠修改爲以下的新的格式了:
編號 名稱 規格 生產廠家編號
001 生肉 優質 001
002 玉米腸 簡裝 001
003 尿素 60 公斤裝 002
004 打印紙 16 開 003
005 磷酸二銨 30 公斤裝 002
在貨物表中只保留了指向廠家表的主鍵的字段「生產廠家編號」,這樣就避免了數據冗
餘的問題。當進行查詢的時候,只要根據「生產廠家編號」到廠家信息表中查詢就能夠知道
廠家的詳細信息了;當廠家遷址的時候,只要修改廠家信息表中的一條數據就能夠了。
這種將兩張表經過字段關聯起來的方式就被稱爲「表關聯」,關聯到其餘表主鍵的字段
被稱爲「外鍵」,上邊例子中貨物表中的「生產廠家編號」字段就是外鍵。表關聯也是關係
數據庫的核心理念,它使得數據庫中的數據再也不互相孤立,經過表關聯咱們能夠表達很是復
雜的數據關係。
1.2.9 數據庫的語言——SQL
DBMS是一種系統軟件,咱們要與它交互的時候就必須使用某種語言,在數據庫發展初
期每一種DBMS都有本身的特有的語言,不過逐漸的SQL 成爲了全部DBMS 都支持的主流語
言。SQL 是專爲數據庫而創建的操做命令集,是一種功能齊全的數據庫語言。在使用它時,
只須要發出「作什麼」的命令,「怎麼作」是不用使用者考慮的。SQL 功能強大、簡單易學、使
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
用方便,已經成爲了數據庫操做的基礎,而且如今幾乎全部的數據庫均支持SQL。
SQL的英文全稱是Structured Query Language,它是1974年由Boyce和Chamberlin提出
的,而且首先在IBM的關係數據庫原型產品R系統(SYSTEM R)上實現。它的前身是1972
提出的SQUARE(Specifying Queries As Rela onal Expr essesi on )語言,在1974年作了修改,
而且更名爲SEQUEL(Structured English Query Language)語言,後來SEQUEL 簡化爲SQL。
SQL是高級的非過程化編程語言,容許用戶在高層數據結構上工做。使用它,用戶無需
指定對數據的存放方法,也不須要用戶瞭解具體的數據存放方式,因此具備徹底不一樣底層結
構的不一樣數據庫系統能夠使用相同的SQL 語言做爲數據輸入與管理的接口。它以記錄集合
做爲操縱對象,全部SQL 語句接受集合做爲輸入,返回集合做爲輸出,這種集合特性容許
一條SQL 語句的輸出做爲另外一條SQL 語句的輸入,因此SQL 語言能夠嵌套,這使它具備極
大的靈活性和強大的功能,在多數狀況下,在其餘語言中須要一大段程序實現的一個單獨事
件只須要一個SQL語句就能夠達到目的,這也意味着用SQL語言能夠寫出很是複雜的語句。
SQL具備下面4個主要的功能:建立數據庫並定義表的結構;查詢須要的數據;更新或
者刪除指定的數據;控制數據庫的安全。使用SQL咱們能夠完成和DBMS的幾乎全部交互任
務。
好比咱們要查找年齡小於18歲的員工信息,那麼咱們只要執行下面的SQL就能夠:
SELECT * from Employees where age<18
好比咱們要將全部職位爲「名譽總裁」的員工刪除,那麼就能夠執行下面的SQL:
DELETE from Employees where posi on=’ 名譽總裁’
能夠看到咱們只是描述了咱們要作什麼,至於怎麼去作則由DBMS來決定。能夠想一想如
果要是本身編程去實現相似的功能,則須要編寫很是複雜的算法才能完成,並且性能也不一
定會很是好。
咱們能夠經過三種方式執行SQL:
l 在工具中執行。各個DBMS 幾乎都提供了工具用於執行SQL 語句,好比Microso SQL
Server的Management Studio、DB2 的命令中心、Oracle的SqlPlus或者MySQL的Query
Browser。在這些工具中咱們只要輸入要執行的SQL 而後點擊【執行】按鈕就能夠獲得
執行結果。
l 以編譯的方式嵌入到語言中。在這種方式中咱們能夠把SQL 直接寫到代碼中,在編譯
的時候由編譯器來決定和數據庫的交互方式。好比PowerBuild、C 等就採用這種方式。
l 以字符串的形式嵌入到語言中。在這種方式中SQL 語句只是以字符串的形式寫到代碼
中,而後由代碼將其提交到DBMS,而且分析返回的結果。目前這是大部分支持數據庫
操做的語言採用的方式,好比C#、Java、Python、Delphi 和VB等。
因爲嵌入到語言中的執行方式是嚴重依賴宿主語言的,而本書不假定用戶使用任何編程
語言,爲了可以使得使用任何語言的讀者都能學習本書中的知識點,本書將主要以在工具中
執行的方式來執行SQL 語句,讀者能夠根據本身使用的編程語言來靈活運用這些知識點。
不熟悉用工具執行SQL 的讀者能夠參考附錄A中的介紹。
IBM 是SQL 語言的發明者,可是其餘的數據庫廠商都在IBM 的SQL 基礎上提出了本身
的擴展語法,所以造成了不一樣的SQL 語法,對於開發人員來講,使用這些有差別的語法是
很是頭疼的時候。所以在1986年美國國家標準化協會(ANSI)爲SQL制定了標準,而且在
1987 年國際標準化組織(ISO)也爲SQL 指定了標準,迄今爲止已經推出SQL-8六、SQL-8九、
SQL-9二、SQL-9九、SQL-2003等版本的標準。
雖然已經有了國際標準,可是因爲種種緣由,各個數據庫產品的SQL 語法仍然有着很
大差別,在數據庫A 上能成功執行的SQL 放到數據庫B 上就會執行失敗。爲了方便使用不
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
同數據庫產品的讀者都能成功運行本書中的例子,咱們會介紹各類數據庫SQL 的差別性,
而且給出解決方案,並且本書將會安排專門章節講解跨數據庫程序開發的技術。
1.2.10 DBA 與程序員
若是你是一個數據庫開發技術的初學者的話,你會發現到了書店裏有不少數據庫相關的
書你看不懂,你會發現互聯網有一些搞數據庫的人的Blog 上說的東西你感受很陌生,他們
都是在談論數據庫的恢復、數據庫的調優、調整數據庫的安全性,難道他們搞的是更深層次
的東西嗎?不是的,他們就是數據庫系統管理員(Database Administrator,DBA)。圍繞在
DBMS周圍的技術人員有兩類:數據庫系統管理員和開發人員。使用數據庫進行程序開發的
人員是程序員,而對數據庫系統進行管理、維護、調優的則是數據庫系統管理員。
做爲一名開發人員,咱們沒必要知道如何安裝和配置數據庫系統,這應該是DBA 的任務;
當規劃數據庫的備份策略的時候,不要去問開發人員,這也是DBA 的任務;當數據庫系統
崩潰的時候,請當即給DBA 打電話,若是打給開發人員的話,你獲得的回答一般是「怎麼
會呢?天知道怎麼恢復!」。正如一個公司的網絡系統是由網管來負責的同樣,一個公司的數
據庫系統也是由DBA 來進行管理的,它們的主要工做以下:
l 安裝和配置數據庫,建立數據庫以及賬戶;
l 監視數據庫系統,保證數據庫不宕機;
l 收集系通通計和性能信息以便進行調整;
l 發現性能糟糕的SQL,並給開發人員提出調優建議;
l 管理數據庫安全性;
l 備份數據庫,當發生故障時要及時恢復;
l 升級DBMS而且在必要時爲系統安裝補丁;
l 執行存儲和物理設計,均衡設計問題以完成性能優化;
DBA 大部分時間是在監視系統、備份/恢復系統、優化系統,而開發人員則無需精通這
些技能;開發人員大部分時間是在用SQL實現業務邏輯。兩者知識的重合點就是SQL,一個
開發人員若是不熟悉SQL 的話就沒法很好的實現業務邏輯,而一個DBA 若是不熟悉SQL 的
話就沒法完成數據庫的調優工做。因此不管你是想成爲開發人員仍是成爲DBA,那麼都首先
來學好SQL 吧!
進行數據庫的備份/恢復、權限管理等操做也常常須要使用SQL 命令來完成,不過這些
SQL命令都是與特定的DBMS產品相關的,並且不一樣產品的使用方式也是差異很大的,因此
本書不會講解數據庫的備份/恢復、權限管理相關的SQL,有興趣的讀者能夠去參考相關的
資料。
第三章數據的增刪改
上一章中介紹了建立和管理數據表的方法,數據表只是數據的容器,沒有任何數據的表
是沒有任何意義的。主流的數據庫系統都提供了管理數據庫的工具,使用這些工具能夠查看
表中的數據,還能夠添加、修改和刪除表中的數據,可是使用工具進行數據的增刪改一般只
限於測試數據庫時使用,更常見的方式時經過程序或者Web 頁面來向數據庫發出SQL 語句
指令來進行這些操做,所以本章將介紹經過SQL 語句增刪改表中數據的方法。
本章中咱們將使用一些數據表,爲了更容易的運行本章中的例子,必須首先建立所須要
的數據表,所以下面列出本章中要用到數據表的建立SQL語句:
MYSQL:
CREATE TABLE T_Person (FName VARCHAR(20),FAge INT,FRemark VARCHAR(20),PRIMARY
KEY (FName));
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
CREATE TABLE T_Debt (FNumber VARCHAR(20),FAmount DECIMAL(10,2) NOT NULL,
FPerson VARCHAR(20),PRIMARY KEY (FNumber),
FOREIGN KEY (FPerson) REFERENCES T_Person(FName)) ;
MSSQLServer:
CREATE TABLE T_Person (FName VARCHAR(20),FAge INT,FRemark VARCHAR(20),PRIMARY
KEY (FName));
CREATE TABLE T_Debt (FNumber VARCHAR(20),FAmount NUMERIC(10,2) NOT NULL,
FPerson VARCHAR(20),PRIMARY KEY (FNumber),
FOREIGN KEY (FPerson) REFERENCES T_Person(FName)) ;
Oracle:
CREATE TABLE T_Person (FName VARCHAR2(20),FAge NUMBER (10) ,FRemark
VARCHAR2(20),PRIMARY KEY (FName)) ;
CREATE TABLE T_Debt (FNumber VARCHAR2(20),FAmount NUMERIC(10,2) NOT NULL,
FPerson VARCHAR2(20),PRIMARY KEY (FNumber),
FOREIGN KEY (FPerson) REFERENCES T_Person(FName)) ;
DB2:
CREATE TABLE T_Person (FName VARCHAR(20) NOT NULL,FAge INT,FRemark
VARCHAR(20),PRIMARY KEY (FName));
CREATE TABLE T_Debt (FNumber VARCHAR(20) NOT NULL,FAmount DECIMAL(10,2) NOT
NULL,
FPerson VARCHAR(20),PRIMARY KEY (FNumber),
FOREIGN KEY (FPerson) REFERENCES T_Person(FName)) ;
請在不一樣的數據庫系統中運行相應的SQL 語句。T_Person 爲記錄人員信息的數據表,
其中主鍵字段FName爲人員姓名,FAge爲年齡,而FRemark則爲備註信息;T_Debt 記錄了
債務信息,其中主鍵字段FNumber爲債務編號,FAmount爲欠債金額,FPerson字段爲欠債
人姓名,FPerson字段與T_Person中的FName字段創建了外鍵關聯關係。
3.1數據的插入
數據表是數據的容器,沒有任何數據的數據表是沒有意義的,數據表建立完成之後好比
向其中插入有用的數據才能使得系統運轉起來。
3.1.1 簡單的INSERT語句
INSERT INTO 語句用來向數據表中插入數據,好比執行下面的語句就能夠向T_Person 表
中插入一條數據:
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Tom',18,'USA')1
這句SQL向T_Person表中插入了一條數據,其中FName字段的值爲'Tom',FAge字段的
值爲18,而FRemark字段的值爲'USA'。VALUES前邊的括號中列出的是要設置字段的字段名,
字段名之間用逗號隔開;VALUES 後邊的括號中列出的是要設置字段的值,各個值一樣用逗
號隔開。須要注意的是VALUES 前列出的字段名和VALUES 後邊列出的字段值是按順序一一
對應的,也就是第一個值'Tom'設置的是字段FName 的值,第二個值18 設置的是字段FAge
的值,第三個值'USA'設置的是字段FRemark 的值,不能打亂它們之間的對應關係,並且要
保證兩邊的條數是一致的。因爲FName 和FRemark 字段是字符串類型的,因此須要用單引
號2將值包圍起來,而整數類型的FAge字段的值則不須要用單引號包圍起來。
1 須要注意,這裏的單引號是半角字符,若是使用全角字符將會致使執行錯誤。
2有的數據庫系統也支持用雙引號來包圍,不過爲了使得咱們編寫的SQL更容易的在主流數據庫系統中運
行,本書將一概採用單引號來包圍字符串類型數據。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
咱們來檢驗一下數據是否真的插入數據表中了,執行下面的SQL語句:
SELECT * FROM T_Person3
執行完畢咱們將會看到以下的輸出結果(在不一樣的數據庫系統以及管理工具下的顯示效
果會略有不一樣):
FName FAge FRemark
Tom 18 USA
能夠看到插入的數據已經保存在T_Person 表中了,咱們還能夠運行多條SQL 語句來插
入多條數據:
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Jim',20,'USA');
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Lili',22,'China') ;
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('XiaoWang',17,' China ') ;
再次執行SELECT * FROM T_Person 來查看錶中的數據:
FNAME FAGE FREMARK
Tom 18 USA
Jim 20 USA
Lili 22 China
XiaoWang 17 China
INSERT語句中列的順序能夠是任意的,好比咱們也能夠用下面的SQL 來插入數據:
INSERT INTO T_Person(FAge,FName,FRemark) VALUES(21,'Kimisushi','Korea')
執行SELECT * FROM T_Person來查看錶中的數據:
FNAME FAGE FREMARK
Tom 18 USA
Jim 20 USA
Lili 22 China
XiaoWang 17 China
Kimisushi 21 Korea
可見INSET語句中列的順序不會影響數據插入的結果。
3.1.2 簡化的INSERT語句
INSERT 語句中也並不須要咱們指定表中的全部列,好比在插入數據的時候某些字段沒
有值,咱們能夠忽略這些字段。下面咱們插入一條沒有備註信息的數據:
INSERT INTO T_Person(FAge,FName) VALUES(22,'LXF')
執行SELECT * FROM T_Person來查看錶中的數據:
FName FAge FRemark
Tom 18 USA
Jim 20 USA
Lili 22 China
XiaoWang 17 China
Kimisushi 21 Korea
LXF 22 <NULL>
INSERT 語句還有另外一種用法,能夠不用指定要插入的表列,這種狀況下將按照定義表
中字段順序來進行插入,咱們執行下面的SQL:
INSERT INTO T_Person VALUES('luren1',23,'China')
3先不用管這句SQL語句的具體語法,只要知道它是用來查看錶T_Person中的數據便可。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
這裏省略了VALUES 前面的字段定義,VALUES 後面的值列表中按照CREATE TABLE 語句
中的順序排列。執行SELECT * FROM T_Person 來查看錶中的數據:
FNAME FAGE FREMARK
Tom 18 USA
Jim 20 USA
Lili 22 China
XiaoWang 17 China
Kimisushi 21 Korea
LXF 22 <NULL>
luren1 23 China
這種省略字段列表的方法能夠簡化輸入,不過咱們推薦這種用法,由於省略字段列表之
後就沒法很容易的弄清楚值列表中各個值到底對應哪一個字段了,很是容易致使程序出現BUG
而且給程序的調試帶來很是大的麻煩。
3.1.3 非空約束對數據插入的影響
正如「非空約束」表達的意思,若是對一個字段添加了非空約束,那麼咱們是不能向這
個字段中插入NULL 值的。T_Debt 表的FAmount 字段是有非空約束的,若是咱們執行下面
SQL:
INSERT INTO T_Debt (FNumber, FPerson) VALUES ('1', 'Jim')
這句SQL 中沒有爲字段FAmount 賦值,也就是說FAmount 爲空值。咱們執行這句SQL
之後數據庫系統會報出相似以下的錯誤信息:
不能將值 NULL 插入列 'FAmount',表 'demo.dbo.T_Debt';列不容許有空值。INSERT 失
敗。
若是咱們爲FAmount 設置非空值的話,則會插入成功,執行下面的SQL:
INSERT INTO T_Debt (FNumber,FAmount, FPerson) VALUES ('1',200, 'Jim')
此句SQL則能夠正常的執行成功。執行SELECT * FROM T_Debt 來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Jim
能夠看到數據已經被正確的插入到表中了。
3.1.3 主鍵對數據插入的影響
主鍵是在同一張表中必須是惟一的,若是在進行數據插入的時候指定的主鍵與表中已有
的數據重複的話則會致使違反主鍵約束的異常。T_Debt 表中FNumber 字段是主鍵,若是我
們執行下面SQL:
INSERT INTO T_Debt (FNumber,FAmount, FPerson) VALUES ('1',300, 'Jim')
因爲在上一節中咱們已經向表中插入了一條FNumber 字段爲1 的記錄,因此運行這句
SQL的時候會報出相似以下的錯誤信息:
不能在對象'dbo.T_Debt' 中插入重複鍵。
而若是咱們爲FNumber 設置一個不重複值的話,則會插入成功,執行下面的SQL:
INSERT INTO T_Debt (FNumber,FAmount, FPerson) VALUES ('2',300, 'Jim')
此句 SQL則能夠正常的執行成功。執行SELECT * FROM T_Debt 來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Jim
2 300.00 Jim
能夠看到數據已經被正確的插入到表中了。
3.1.4 外鍵對數據插入的影響
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
外鍵是指向另外一個表中已有數據的約束,所以外鍵值必須是在目標表中存在的。若是插
入的數據在目標表中不存在的話則會致使違反外鍵約束異常。T_Debt 表中FPerson 字段是指
向表T_Person 的FName字段的外鍵,若是咱們執行下面SQL:
INSERT INTO T_Debt (FNumber,FAmount, FPerson) VALUES ('3',100, 'Jerry')
因爲在T_Person 表中不存在FName字段等於「Jerry」的數據行,因此會數據庫系統會
報出相似以下的錯誤信息:
INSERT 語句與 FOREIGN KEY 約束"FK__T_Debt__FPerson__1A14E395"衝突。該衝突發生於數
據庫"demo",表"dbo.T_Person", column 'FName'。
而若是咱們爲FPerson 字段設置已經在T_Person表中存在的FName 字段值的話則會插
入成功,執行下面的SQL:
INSERT INTO T_Debt (FNumber,FAmount, FPerson) VALUES ('3',100, 'Tom')
此句SQL則能夠正常的執行成功。執行SELECT * FROM T_Debt 來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Jim
2 300.00 Jim
3 100.00 Tom
能夠看到數據已經被正確的插入到表中了。
3.2 數據的更新
錄入到數據表中的數據不多有一成不變的,隨着系統的運行常常須要更新表中的某些數
據,好比Tom 的家庭住址變化了咱們就要在數據庫中將他的家庭住址更新、新年度到來的
時候咱們就要將全部人員的年齡增長一歲,相似需求都要求對數據庫中現有的數據進行更新。
3.2.1 簡單的數據更新
UPDATE語句用來對數據表中的數據進行更新。下邊的語句用來將表T_Person 中全部人
員的FREMARK字段值更新爲「SuperMan」:
UPDATE T_Person
SET FRemark = 'SuperMan'
執行SELECT * FROM T_Person來查看錶中的數據:
FName FAge FRemark
Jim 20 SuperMan
Kimisushi 21 SuperMan
Lili 22 SuperMan
luren1 23 SuperMan
LXF 22 SuperMan
Tom 18 SuperMan
XiaoWang 17 SuperMan
能夠看到全部行的FRemark字段值都被設置成了「SuperMan」。
來看一下剛纔執行的SQL 語句,首先它聲明瞭要更新的表爲T_Person:
UPDATE T_Person
在SET子句中,咱們指定將FRemark 字段更新爲新值'SuperMan':
SET FRemark = 'SuperMan'
咱們還能夠在SET 語句中定義多個列,這樣就能夠實現多列同時更新了,好比下面的
UPDATE語句用來將全部人員的FRemark 字段更新爲「Sonic」,而且將年齡更新爲25:
UPDATE T_Person
SET FRemark = 'Sonic',
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FAge=25
多個列之間須要使用逗號分隔開。執行完此SQL語句後執行SELECT * FROM T_Person 來
查看錶中的數據的變化:
FName FAge FRemark
Jim 25 Sonic
Kimisushi 25 Sonic
Lili 25 Sonic
luren1 25 Sonic
LXF 25 Sonic
Tom 25 Sonic
XiaoWang 25 Sonic
3.2.2 帶WHERE 子句的UPDATE語句
目前演示的幾個UPDATE 語句都是一次性更新全部行的數據,這沒法知足只更新符合特
定條件的行的需求,好比「將Tom的年齡修改成12歲」。要實現這樣的功能只要使用WHERE
子句就能夠了,在WHERE 語句中咱們設定適當的過濾條件,這樣UPDATE 語句只會更新符
合WHERE子句中過濾條件的行,而其餘行的數據則不被修改。
執行下邊的UPDATE語句:
UPDATE T_Person
SET FAge = 12
WHERE FNAME='Tom'
執行完此SQL語句後執行SELECT * FROM T_Person來查看錶中的數據的變化:
FName FAge FRemark
Jim 25 Sonic
Kimisushi 25 Sonic
Lili 25 Sonic
luren1 25 Sonic
LXF 25 Sonic
Tom 12 Sonic
XiaoWang 25 Sonic
能夠看到只有第一行中的FAGE被更新了。WHERE子句「WHERE FNAME='Tom'」表示我
們只更新FNAME 字段等於'Tom'的行。因爲FNAME 字段等於'Tom'的只有一行,因此僅有一
行記錄被更新,可是若是有多個符合條件的行的話將會有多行被更新,好比下面UPDATE語
句將全部年齡爲25的人員的備註信息修改成「BlaBla」:
UPDATE T_Person
SET FRemark = 'BlaBla'
WHERE FAge =25
執行完此SQL語句後執行SELECT * FROM T_Person來查看錶中的數據的變化:
FName FAge FRemark
Jim 25 BlaBla
Kimisushi 25 BlaBla
Lili 25 BlaBla
luren1 25 BlaBla
LXF 25 BlaBla
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Tom 12 Sonic
XiaoWang 25 BlaBla
目前爲止咱們演示的都是很是簡單的WHERE 子句,咱們能夠使用複雜的WHERE 語句
來知足更加複雜的需求,好比下面的UPDATE語句就用來將FNAME等於’Jim’或者’LXF’的行的
FAge字段更新爲22:
UPDATE T_Person
SET FAge = 22
WHERE FName ='jim' OR FName='LXF'
執行完此SQL語句後執行SELECT * FROM T_Person來查看錶中的數據的變化:
FName FAge FRemark
Jim 22 BlaBla
Kimisushi 25 BlaBla
Lili 25 BlaBla
luren1 25 BlaBla
LXF 22 BlaBla
Tom 12 Sonic
XiaoWang 25 BlaBla
這裏咱們使用OR邏輯運算符來組合兩個條件來實現複雜的過濾邏輯,咱們還能夠使用
OR、NOT 等運算符實現更加複雜的邏輯,甚至可以使用模糊查詢、子查詢等實現高級的數
據過濾,關於這些知識咱們將在後面的章節專門介紹。
3.2.3 非空約束對數據更新的影響
正如「非空約束」表達的意思,若是對一個字段添加了非空約束,那麼咱們是不能將這
個字段中的值更新爲NULL的。T_Debt表的FAmount字段是有非空約束的,若是咱們執行下
面SQL:
UPDATE T_Debt set FAmount = NULL WHERE FPerson='Tom'
這句SQL 爲FAmount 設置空值。咱們執行這句SQL 之後數據庫系統會報出相似以下的
錯誤信息:
不能將值 NULL 插入列 'FAmount',表 'demo.dbo.T_Debt';列不容許有空值。UPDATE 失
敗。
若是咱們爲FAmount 設置非空值的話,則會插入成功,執行下面的SQL:
UPDATE T_Debt set FAmount =123 WHERE FPerson='Tom'
此句SQL 則能夠正常的執行成功。執行SELECT * FROM T_Debt來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Jim
2 300.00 Jim
3 123.00 Tom
能夠看到數據已經被正確的更新到表中了。
3.2.3 主鍵對數據更新的影響
主鍵是在同一張表中必須是惟一的,若是在進行數據更新的時候指定的主鍵與表中已有
的數據重複的話則會致使違反主鍵約束的異常。T_Debt 表中FNumber 字段是主鍵,若是我
們執行下面SQL:
UPDATE T_Debt set FNumber = '2' WHERE FPerson='Tom'
因爲表中已經存在一條FNumber 字段爲2 的記錄,因此運行這句SQL 的時候會報出相似
以下的錯誤信息:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
違反了PRIMARY KEY 約束'PK__T_Debt__1920BF5C'。不能在對象'dbo.T_Debt' 中插入重複鍵。
而若是咱們爲FNumber設置一個不重複值的話,則會插入成功,執行下面的SQL:
UPDATE T_Debt set FNumber = '8' WHERE FPerson='Tom'
此句SQL 則能夠正常的執行成功。執行SELECT * FROM T_Debt來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Jim
2 300.00 Jim
8 123.00 Tom
能夠看到數據已經被正確的更新到表中了。
3.2.4 外鍵對數據更新的影響
外鍵是指向另外一個表中已有數據的約束,所以外鍵值必須是在目標表中存在的。若是更
新後的數據在目標表中不存在的話則會致使違反外鍵約束異常。T_Debt表中FPerson字段是
指向表T_Person 的FName 字段的外鍵,若是咱們執行下面SQL:
UPDATE T_Debt set FPerson = 'Merry' WHERE FNumber='1'
因爲在T_Person表中不存在FName 字段等於「Merry」的數據行,因此會數據庫系統會
報出相似以下的錯誤信息:
UPDATE 語句與FOREIGN KEY 約束"FK__T_Debt__FPerson__1A14E395"衝突。該衝突發生於數據庫"demo",
表"dbo.T_Person", column 'FName'。
而若是咱們爲FPerson字段設置已經在T_Person表中存在的FName字段值的話則會插入成功,執行下面的SQL:
UPDATE T_Debt set FPerson = 'Lili' WHERE FNumber='1'
此句SQL 則能夠正常的執行成功。執行SELECT * FROM T_Debt來查看錶中的數據:
FNumber FAmount FPerson
1 200.00 Lili
2 300.00 Jim
8 123.00 Tom
能夠看到數據已經被正確的更新到表中了。
3.3 數據的刪除
數據庫中的數據通常都有必定的生命週期,當數據再也不須要的時候咱們就要將其刪除,
執行DELETE 語句就能夠將數據從表中刪除。不過須要注意的就是若是被刪除的數據行是某
個外鍵關聯關係中的被引用數據的話,則進行刪除的時候會失敗,若是要刪除成功則必須首
先刪除引用者才能夠。
3.3.1 簡單的數據刪除
刪除數據的SQL 語句很是簡單,咱們只要指定要刪除的表就能夠了,好比咱們要將
T_Debt和T_Person 表中的數據刪除,那麼執行下面的SQL語句便可:
DELETE FROM T_Debt;
DELETE FROM T_Person;
因爲T_Debt表中FPerson字段是指向表T_Person 的FName字段的外鍵,因此必須首先
刪除T_Debt表中的數據後才能刪除T_Person中的數據。
執行SELECT * FROM T_Debt查看T_Debt 表中的數據變化:
FNumber FAmount FPerson
執行完此SQL語句後執行SELECT * FROM T_Person來查看T_Person表中的數據變化:
FName FAge FRemark
能夠見表中全部的數據行都被刪除了,T_Debt和T_Person 中沒有任何數據。
初學者每每容易把DROP TABLE語句和DELETE混淆,雖然兩者名字中都有「刪除」兩個
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
字,不過DELETE語句僅僅是刪除表中的數據行,而表的結構還存在,而DROP TABLE語句則
不只將表中的數據行所有刪除,並且還將表的結構也刪除。能夠形象的比喻成DELETE 語句
僅僅是「吃光碗裏的飯」,而DROP TABLE語句則是「吃光碗裏的飯還將碗砸碎」。若是咱們
執行「DROP TABLE T_Person」的話,那麼再次執行「SELECT * FROM T_Person」的時候數據
庫系統就會報告「數據表T_Person 不存在」。
上邊介紹的DELETE 語句將表中的全部數據都刪除了,若是咱們只想刪除咱們指定的數
據行怎麼辦呢?和UPDATE語句相似,DELETE 語句也提供了WHERE 語句進行數據的過濾,
這樣只有符合過濾條件的數據行纔會被刪除。
3.3.2 帶WHERE 子句的DELETE 語句
因爲前面咱們執行「DELETE FROM T_Person」語句將數據表T_Person中的數據所有刪除
了,爲了演示帶WHERE 子句的DELETE 語句,咱們須要從新插入一些數據到T_Person 中。
請執行下面的SQL語句:
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Jim',20,'USA');
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Lili',22,'China') ;
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('XiaoWang',17,' China ') ;
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('Sam',16,'China') ;
INSERT INTO T_Person(FName,FAge,FRemark) VALUES('BlueFin',12,'Mars') ;
執行完此SQL語句後執行SELECT * FROM T_Person來查看T_Person表中新插入的數據:
FNAME FAGE FREMARK
Jim 20 USA
Lili 22 China
XiaoWang 17 China
Sam 16 China
BlueFin 12 Mars
咱們要刪除年齡大於20歲或者來自火星(Mars)的人員,所以使用帶複合邏輯WHERE
子句,以下:
DELETE FROM T_Person WHERE FAge > 20 or FRemark = 'Mars'
執行完此SQL語句後執行SELECT * FROM T_Person來查看錶中的數據的變化:
FNAME FAGE FREMARK
Jim 20 USA
XiaoWang 17 China
Sam 16 China
能夠看到年齡爲22 歲的Lili 和來自火星的BlueFin 被刪除了。
本章已經結束,咱們再也不須要T_Person、T_Debt這兩張表,所以須要將它們刪除,執
行下面的SQL 便可:
DROP TABLE T_Debt;
DROP TABLE T_Person;
第四章數據的檢索
到目前爲止,咱們已經學習瞭如何建立數據表、如何修改數據表以及如何刪除數據表,
咱們還學習瞭如何將數據插入數據表、如何更新數據表中的數據以及如何數據刪除。建立數
據表是在建立存放數據的容器,修改和刪除數據表是在維護數據模型的正確性,將數據插入
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
數據表、更新數據表以及刪除數據表中的數據則是在維護數據庫中數據與真實業務數據之間
的同步,這些操做都不是常常發生的,它們只佔據數據庫操做中很小的一部分,咱們大部分
時間都是在對數據庫中的數據進行檢索,而且基於檢索結果進行響應的分析,能夠說數據的
檢索是數據庫中最重要的功能。
與數據表結構的管理以及數據表中數據的管理不一樣,數據檢索所須要面對的問題是很是
複雜的,不只要求可以完成「檢索出全部年齡小於12 歲的學生」、「檢索出全部曠工時間超
過3天的職工」等簡單的檢索任務,並且還要完成「檢索出本季度每種商品的出庫入庫詳細
狀況」、「檢索出全部學生家長的工做單位信息」等複雜的任務,甚至還須要完成其餘更加復
雜的檢索任務。數據檢索麪對的場景是異常複雜的,所以數據檢索的語法也是其餘功能所不
能比的,不只語法規則很是複雜,並且使用方式也很是靈活。本書中大部份內容都是講解數
據檢索相關知識的,爲了下降學習的梯度,本章咱們將講解基本的數據檢索語法,這些語法
是數據檢索功能中最基礎也是最核心的部分,所以只有掌握咱們才能繼續學習更加複雜的應
用。
本章中咱們將使用一些數據表,爲了更容易的運行本章中的例子,必須首先建立所須要
的數據表,所以下面列出本章中要用到數據表的建立SQL語句:
MYSQL:
CREATE TABLE T_Employee (FNumber VARCHAR(20),FName VARCHAR(20),FAge INT,FSalary
DECIMAL(10,2),PRIMARY KEY (FNumber))
MSSQLServer:
CREATE TABLE T_Employee (FNumber VARCHAR(20),FName VARCHAR(20),FAge INT,FSalary
NUMERIC(10,2),PRIMARY KEY (FNumber))
Oracle:
CREATE TABLE T_Employee (FNumber VARCHAR2(20),FName VARCHAR2(20),FAge NUMBER
(10),FSalary NUMERIC(10,2),PRIMARY KEY (FNumber))
DB2:
CREATE TABLE T_Employee (FNumber VARCHAR(20) NOT NULL,FName VARCHAR(20),FAge
INT,FSalary DECIMAL(10,2),PRIMARY KEY (FNumber))
請在不一樣的數據庫系統中運行相應的SQL語句。T_Employee爲記錄員工信息的數據表,
其中主鍵字段FNumber爲員工工號,FName爲人員姓名,FAge爲年齡,FSalary爲員工月工
資。
爲了更加直觀的驗證本章中檢索語句的正確性,咱們須要在T_Employee 表中預置一些
初始數據,請在數據庫中執行下面的數據插入SQL語句:
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('DEV001','Tom',25,8300);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('DEV002','Jerry',28,2300.80);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('SALES001','John',23,5000);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('SALES002','Kerry',28,6200);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('SALES003','Stone',22,1200);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('HR001','Jane',23,2200.88);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('HR002','Tina',25,5200.36);
INSERT INTO T_Employee(FNumber,FName,FAge,FSalary) VALUES('IT001','Smith',28,3900);
4.1 SELECT基本用法
SELECT是實現數據檢索的SQL語句,本節咱們學習SELECT語句最基本的用法。
4.1.1 簡單的數據檢索
「取出一張表中全部的數據」是最簡單的數據檢索任務,完成這個最簡單任務的SQL
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
語句也是最簡單的,咱們只要執行「SELECT * FROM 表名」便可。好比咱們執行下面的SQL
語句:
SELECT * FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
執行結果中列出了表中的全部行,並且包含了表中每一列的數據。
4.1.2 檢索出須要的列
上面的SQL 語句執行的結果中包含了表中每一列的數據,有的時候並不須要全部列的
數據。好比咱們只須要檢索全部員工的工號,若是咱們採用「SELECT* FROM T_Employee」
進行檢索的話,數據庫系統會將全部列的數據從數據庫中取出來,而後經過網絡發送給咱們,
這不只會佔用沒必要要的CPU 資源和內存資源,並且會佔用必定的網絡帶寬,這在咱們這種
測試模式下不會有影響,可是若是是在真實的生產環境中的話就會大大下降系統的吞吐量,
所以最好在檢索的以後只檢索須要的列。那麼如何只檢索出須要的列呢?
檢索出全部的列的SQL 語句爲「SELECT * FROM T_Employee」,其中的星號「*」
就意味着「全部列」,那麼咱們只要將星號「*」替換成咱們要檢索的列名就能夠了。好比我
們執行下面的SQL語句:
SELECT FNumber FROM T_Employee
這就表示咱們要檢索出表T_Employee 中的全部數據,而且只取出FNumber 列。執行完
畢咱們就能在輸出結果中看到下面的執行結果:
FNumber
DEV001
DEV002
HR001
HR002
IT001
SALES001
SALES002
SALES003
能夠看到只有FNumber 列中的數據被檢索出來了。
上面的SQL語句列出了FNumber列中的數據,那麼若是想列出不止一個列中的數據呢?
很是簡單,只要在SELECT 語句後列出各個列的列名就能夠了,須要注意的就是各個列之間
要用半角的逗號「,」分隔開。好比咱們執行下面的SQL語句:
SELECT FName,FAge FROM T_Employee
這就表示咱們要檢索出表T_Employee 中的全部數據,而且只取出FName和FAge兩列的
內容。執行完畢咱們就能在輸出結果中看到下面的執行結果:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FName FAge
Tom 25
Jerry 28
Jane 23
Tina 25
Smith 28
John 23
Kerry 28
Stone 22
能夠看到,執行結果中列出了全部員工的姓名和他們的年齡。
若是要用這種顯式指定數據列的方式取出全部列,咱們就能夠編寫下面的SQL:
SELECT FNumber,FName,FAge,FSalary FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
這和「SELECT * FROM T_Employee」的執行結果是一致的,也就是說「SELECT
FNumber,FName,FAge,FSalary FROM T_Employee」和「SELECT* FROM T_Employee」
是等價的。
4.1.3 列別名
因爲編碼命名規範、編程框架要求等的限制,數據表的列名有的時候意思並非很是易
讀,好比T_Employee中的姓名字段名稱爲FName,而若是咱們能用Name 甚至「姓名」來代
表這個字段就更清晰易懂了,但是字段名已經不能更改了,那麼難道就不能用別的名字來使用已
有字段了嗎?
固然不是!就像能夠爲每一個人取一個外號同樣,咱們能夠爲字段取一個別名,這樣就能夠
使用這個別名來引用這個列了。別名的定義格式爲「列名AS 別名」,好比咱們要爲FNumber 字
段取別名爲Number14,FName 字段取別名爲Name、FAge 字段取別名爲Age、爲FSalary
字段取別名爲Salary,那麼編寫下面的SQL 便可:
SELECT FNumber AS Number1,FName AS Name,FAge AS Age,FSalary AS Salary FROM
T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
Number1 Name Age Salary
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
4因爲Number 在Oracle中爲關鍵字,因此若是在爲FNumber 字段取別名爲Number,那麼將
會在Oracle 中運行失敗,因此這裏取別名爲Number1。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
HR002 Tina 25 5200.36
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
這裏的執行結果和「SELECT FNumber,FName,FAge,FSalary FROM T_Employee」
執行結果同樣,惟一不一樣的地方就是表頭中的列名,這裏的表頭的列名就是咱們爲各列設定的別
名。
定義別名的時候「AS」不是必須的,是能夠省略的,好比下面的SQL 也是正確的:
SELECT FNumber Number1,FName Name,FAge Age,FSalary Salary FROM T_Employee
若是數據庫系統支持中文列名,那麼還能夠用中文來爲列設定別名,這樣可讀性就更好了,
好比在MSSQLServer中文版上執行下面的SQL:
SELECT FNumber 工號,FName 姓名,FAge 年齡,FSalary 工資FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
工號 姓名 年齡 工資
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
4.1.4按條件過濾
前面演示的例子都是檢索出表中全部的數據,不過在不少狀況下咱們須要按照必定的過
濾條件來檢索表中的部分數據,這個時候能夠先檢索出表中全部的數據,而後檢查每一行看
是否符合指定的過濾條件。好比咱們要檢索出全部工資少於5000元的員工的姓名,那麼可
以編寫下面的代碼來處理5:
result = executeQuery(「SELECT FName, FSalary FROM T_Employee」);
for(i=0;i<result.count;i++)
{
salary = result[i].get(「FSalary」);
if(salary<5000)
{
name = result[i].get(「FName」);
print(name+」的工資少於5000 元,爲:」+salary);
}
}
這種處理方式很是清晰簡單,在處理小數據量以及簡單的過濾條件的時候沒有什麼不妥
的地方,可是若是數據表中有大量的數據(數以萬計甚至百萬、千萬數量級)或者過濾條件
很是複雜的話就會帶來不少問題:
5爲了避免涉及具體宿主語言的細節,這裏採用的是實例性的類C僞代碼,若是須要您能夠將其翻譯成對應宿
主語言的代碼。本書其餘部分也將採用相同的僞代碼來表示宿主語言無關的一些算法。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
l 因爲將表中全部的數據都從數據庫中檢索出來,因此會有很是大的內存消耗以及網絡資源
消耗。
l 須要逐條檢索每條數據是否符合過濾條件,因此檢索速度很是慢,當數據量大的時候這種
速度是讓人沒法忍受的。
l 沒法實現複雜的過濾條件。若是要實現「檢索工資小於5000或者年齡介於23 歲與28 歲
之間的員工姓名」這樣的邏輯的話就要編寫複雜的判斷語句,而若是要關聯其餘表進行查
詢的話則會更加複雜。
數據檢索是數據庫系統的一個很是重要的任務,它內置了對按條件過濾數據的支持,只要
爲SELECT 語句指定WHERE 語句便可,其語法與上一章中講的數據更新、數據刪除的WHERE
語句很是相似,好比完成「檢索出全部工資少於5000 元的員工的姓名」這樣的功能能夠使用
下面的SQL語句:
SELECT FName FROM T_Employee
WHERE FSalary<5000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName
Jerry
Jane
Smith
Stone
WHERE子句還支持複雜的過濾條件,下面的SQL語句用來檢索出全部工資少於5000 元或
者年齡大於25歲的員工的全部信息:
SELECT * FROM T_Employee
WHERE FSalary<5000 OR FAge>25
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
IT001 Smith 28 3900.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
使用WHERE 子句只需指定過濾條件就能夠,咱們無需關心數據庫系統是若是進行查找的,
數據庫會採用適當的優化算法進行查詢,大大下降了CPU資源的佔用。
4.1.5 數據彙總
有時須要對數據庫中的數據進行一些統計,好比統計員工總數、統計年齡大於25歲的員工
中的最低工資、統計工資大於3800元的員工的平均年齡。SQL 中提供了聚合函數來完成計算統
計結果集條數、某個字段的最大值、某個字段的最小值、某個字段的平均值以及某個字段的合計
值等數據統計的功能,SQL 標準中規定了下面幾種聚合函數:
函數名說明
MAX 計算字段最大值
MIN 計算字段最小值
AVG 計算字段平均值
SUM 計算字段合計值
COUNT 統計數據條數
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
這幾個聚合函數都有一個參數,這個參數表示要統計的字段名,好比要統計工資總額,那
麼就須要把FSalary 作爲SUM 函數的參數。經過例子來看一下聚合函數的用法。第一個例子是
查詢年齡大於25歲的員工的最高工資,執行下面的SQL:
SELECT MAX(FSalary) FROM T_Employee
WHERE FAge>25
執行完畢咱們就能在輸出結果中看到下面的執行結果:
6200.00
爲了方面的引用查詢的結果,也能夠爲聚合函數的計算結果指定一個別名,執行下面的SQL:
SELECT MAX(FSalary) as MAX_SALARY FROM T_Employee
WHERE FAge>25
執行完畢咱們就能在輸出結果中看到下面的執行結果:
MAX_SALARY
6200.00
第二個例子咱們來統計一下工資大於3800 元的員工的平均年齡,執行下面的SQL:
SELECT AVG(FAge) FROM T_Employee
WHERE FSalary>3800
執行完畢咱們就能在輸出結果中看到下面的執行結果:
25
第三個例子咱們來統計一下公司每月應支出工資總額,執行下面的SQL:
SELECT SUM(FSalary) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
34302.04
咱們還能夠屢次使用聚合函數,好比下面的SQL 用來統計公司的最低工資和最高工資:
SELECT MIN(FSalary),MAX(FSalary) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1200.00 8300.00
最後一個介紹的函數就是統計記錄數量的COUNT,這個函數有一點特別,由於它的便可以
像其餘聚合函數同樣使用字段名作參數,也能夠使用星號「*」作爲參數。咱們執行下面的SQL:
SELECT COUNT(*),COUNT(FNumber) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
8 8
能夠看到COUNT(*)、COUNT(FNumber)兩種方式都能統計出記錄的條數,據此爲數很多
的開發人員都認爲COUNT(*)、COUNT(字段名)這兩種使用方式是等價的。下面經過例子來講
明,爲了看到兩種使用方式的區別須要首先向表T_Employee 中插入一條新的數據,執行下面
的SQL:
INSERT INTO T_Employee(FNumber,FAge,FSalary) VALUES('IT002',27,2800)
須要注意的就是這句INSERT語句沒有爲FName 字段賦值,也就是說新插入的這條數據的
FName 字段值爲空,能夠執行SELECT * FROM T_Employee 來查看錶T_Employee 中的
內容:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
IT001 Smith 28 3900.00
IT002 <NULL> 27 2800.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
能夠看到FNumber 爲IT002 的行的FName字段是空值。接着執行下面的SQL:
SELECT COUNT(*),COUNT(FNumber),COUNT(FName) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
9 9 8
能夠看到COUNT(*)、COUNT(FNumber)兩個表達式的計算結果都是9,而COUNT(FName)
的計算結果是8。也就反應出了兩種使用方式的區別:COUNT(*)統計的是結果集的總條數,而
COUNT(FName)統計的則是除告終果集中FName 不爲空值(也就是不等於NULL)的記錄的總
條數。因爲FNumber 爲IT002 的行的FName 字段是空值,因此COUNT(FName)的計算結果
是8。所以在使用聚合函數COUNT 的時候必定要區分兩種使用方式的區別,以防止出現數據錯
誤。
4.1.6 排序
到目前爲止,數據檢索結果的排列順序取決於數據庫系統所決定的排序機制,這種排序機
制多是按照數據的輸入順序決定的,也有多是按照其餘的算法來決定的。在有的狀況下咱們
須要按照某種排序規則來排列檢索結果,好比按照工資從高到低的順序排列或者按照姓名的字符
順序排列等。SELECT語句容許使用ORDER BY 子句來執行結果集的排序方式。
ORDER BY 子句位於SELECT 語句的末尾,它容許指定按照一個列或者多個列進行排序,
還能夠指定排序方式是升序(從小到大排列)仍是降序(從大到小排列)。好比下面的SQL 語
句演示了按照年齡排序全部員工信息的列表:
SELECT * FROM T_Employee
ORDER BY FAge ASC
執行完畢咱們就能在輸出結果中看到下面的執行結果,能夠看到輸出結果已經按照
FAge字段進行升序排列了:
FNumber FName FAge FSalary
SALES003 Stone 22 1200.00
SALES001 John 23 5000.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
DEV001 Tom 25 8300.00
IT002 <NULL> 27 2800.00
SALES002 Kerry 28 6200.00
DEV002 Jerry 28 2300.80
IT001 Smith 28 3900.00
這句SQL 中的「ORDER BY FAge ASC」指定了按照FAge 字段的順序進行升序排列,其
中ASC 表明升序。由於對於ORDER BY 子句來講,升序是默認的排序方式,因此若是要採用升
序的話能夠不指定排序方式,也就是「ASC」是能夠省略的,好比下面的SQL 語句具備和上面
的SQL 語句等效的執行效果:
SELECT * FROM T_Employee
ORDER BY FAge
執行完畢咱們就能在輸出結果中看到下面的執行結果,能夠看到輸出結果一樣按照
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FAge字段進行升序排列了:
FNumber FName FAge FSalary
SALES003 Stone 22 1200.00
SALES001 John 23 5000.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
DEV001 Tom 25 8300.00
IT002 <NULL> 27 2800.00
SALES002 Kerry 28 6200.00
DEV002 Jerry 28 2300.80
IT001 Smith 28 3900.00
若是須要按照降序排列,那麼只要將ASC 替換爲DESC 便可,其中DESC 表明降序。執行
下面的SQL語句:
SELECT * FROM T_Employee
ORDER BY FAge DESC
執行完畢咱們就能在輸出結果中看到下面的執行結果,能夠看到輸出結果已經按照
FAge字段進行降序排序了:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
IT001 Smith 28 3900.00
SALES002 Kerry 28 6200.00
IT002 <NULL> 27 2800.00
DEV001 Tom 25 8300.00
HR002 Tina 25 5200.36
HR001 Jane 23 2200.88
SALES001 John 23 5000.00
SALES003 Stone 22 1200.00
能夠看到上面的檢索結果中有幾組年齡相同的記錄,這些年齡相同的記錄之間的順序是由
數據庫系統決定的,可是有時可能須要須要完成「按照年齡從大到小排序,若是年齡相同則按照
工資從大到小排序」之類的排序功能。這能夠經過指定多個排序規則來完成,由於ORDER BY
語句容許指定多個排序列,各個列之間使用逗號隔開便可。執行下面的SQL 語句:
SELECT * FROM T_Employee
ORDER BY FAge DESC,FSalary DESC
FNumber FName FAge FSalary
SALES002 Kerry 28 6200.00
IT001 Smith 28 3900.00
DEV002 Jerry 28 2300.80
IT002 <NULL> 27 2800.00
DEV001 Tom 25 8300.00
HR002 Tina 25 5200.36
SALES001 John 23 5000.00
HR001 Jane 23 2200.88
SALES003 Stone 22 1200.00
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
能夠看到年齡相同的記錄按照工資從高到低的順序排列了。
對於多個排序規則,數據庫系統會按照優先級進行處理。數據庫系統首先按照第一個排序
規則進行排序;若是按照第一個排序規則沒法區分兩條記錄的順序,則按照第二個排序規則進行
排序;若是按照第二個排序規則沒法區分兩條記錄的順序,則按照第三個排序規則進行排序;……
以此類推。以上面的SQL 語句爲例,數據庫系統首先按照FAge 字段的降序進行排列,若是按
照個排序規則沒法區分兩條記錄的順序,則按照FSalary字段的降序進行排列。
ORDER BY子句徹底能夠與WHERE子句一塊兒使用,惟一須要注意的就是ORDER BY子句要
放到WHERE 子句以後,不能顛倒它們的順序。好比咱們嘗試執行下面的SQL 語句:
SELECT * FROM T_Employee
ORDER BY FAge DESC,FSalary DESC
WHERE FAge>23
執行之後數據庫系統會報錯提示此語句有語法錯誤,若是咱們顛倒ORDER BY 和WHERE 子
句的位置則能夠執行經過:
SELECT * FROM T_Employee
WHERE FAge>23
ORDER BY FAge DESC,FSalary DESC
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
SALES002 Kerry 28 6200.00
IT001 Smith 28 3900.00
DEV002 Jerry 28 2300.80
IT002 <NULL> 27 2800.00
DEV001 Tom 25 8300.00
HR002 Tina 25 5200.36
前面咱們提到,若是沒有爲SELECT語句指定ORDER BY 子句,數據庫系統會按照某種內
置的規則對檢索結果進行排序。若是您對檢索結果的先後排列順序有要求,那麼即便數據庫系統
返回的檢索結果符合要求也最好顯式的指定ORDER BY 子句,由於這種系統提供的排序方式是
不穩定的,不只在不一樣數據庫系統之間存在差別,並且即便對同一種數據庫系統來講在不一樣的條
件下這種排序方式也是有可能發生改變的。
4.2 高級數據過濾
數據檢索是數據庫系統中最複雜的功能,而數據過濾則是數據檢索中最核心的部分,到
目前爲止咱們講解的數據過濾都是「過濾某字段等於某個值的全部記錄」、「過濾某字段小於
某個值或者大於某個值的全部記錄」等簡單的數據過濾方式,這顯然是沒法知足真實業務系
統中的各類數據過濾條件的,所以本節咱們將介紹一些單表查詢時的高級數據過濾技術。需
要注意的是,本節講解的高級數據過濾技巧幾乎一樣適用於Update語句和Delete語句中的
Where子句。
4.2.1 通配符過濾
到目前爲止,咱們講解的數據過濾方式都是針對特定值的過濾,好比「檢索全部年齡爲
25的全部員工信息」、「檢索全部工資介於2500元至3800元之間的全部記錄」,可是這種過
濾方式並不能知足一些模糊的過濾方式。好比,檢索全部姓名中含有「th」的員工或者檢索
全部姓「王」的員工,實現這樣的檢索操做必須使用通配符進行過濾。
SQL中的通配符過濾使用LIKE關鍵字,能夠像使用OR、AND 等操做符同樣使用它,它
是一個二元操做符,左表達式爲待匹配的字段,而右表達式爲待匹配的通配符表達式。通配
符表達式由通配符和普通字符組成,主流數據庫系統支持的通配符有單字符匹配和多字符匹
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
配,有的數據庫系統還支持集合匹配。
4.2.1.1 單字符匹配
進行單字符匹配的通配符爲半角下劃線「_」,它匹配單個出現的字符。好比通配符表達
式「b_d」匹配第一個字符爲b、第二個字符爲任意字符、第三個字符爲d 的字符串,「bed」、
「bad」都能匹配這個表達式,而「bd」、「abc」、「build」等則不能匹配這個表達式;通配符
表達式「_oo_」匹配第一個字符爲任意字符、第二個字符爲o、第三個字符爲o、第四個字
符爲任意字符的字符串,「look」、「took」、「cool」都能匹配這個表達式,而「rom」、「todo」
等則不能匹配這個表達式。
下面來演示一下單字符匹配的用法。咱們來檢索T_Employee 表中FName 字段匹配如
下規則的數據行:以任意字符開頭,剩餘部分爲「erry」。根據通配符表達式語法,咱們得知
這個匹配規則對應的通配符表達式爲「_erry」,所以編寫以下的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '_erry'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
SALES002 Kerry 28 6200.00
「Jerry」、「Kerry」兩個字符串可以匹配通配符表達式「_erry」,因此被顯示到告終果
集中,而其餘數據行則因爲不匹配此通配符表達式,因此被過濾掉了。
單字符匹配在通配符表達式中能夠出現屢次,好比咱們要檢索長度爲四、第三個字符爲「n」、
其它字符爲任意字符的姓名。根據通配符表達式語法,咱們得知這個匹配規則對應的通配符表達
式爲「__n_」(注意前兩個字符爲連續的兩個下劃線),那麼須要編寫以下的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '__n_'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
4.2.1.2 多字符匹配
使用下劃線能夠實現「匹配長度爲五、以ab 開頭、剩餘字符任意」的功能,而對於「匹
配以k 開頭,長度不限,剩餘字符任意」這樣的需求則沒法知足,這時就須要使用多字符匹
配了。進行多字符匹配的通配符爲半角百分號「%」,它匹配任意次數(零或多個)出現的
任意字符。好比通配符表達式「k%」匹配以「k」開頭、任意長度的字符串,「k」、「kerry」、
「kb」 都能匹配這個表達式,而「ark」、「luck」、「3kd」等則不能匹配這個表達式;配符表
達式「b%t」匹配以「b」開頭、以「t」結尾、任意長度的字符串,「but」、「bt」、「belt」 都
能匹配這個表達式,而「turbo」、「tube」、「tb」等則不能匹配這個表達式。
下面來演示一下多字符匹配的用法。咱們來檢索T_Employee 表中FName 字段匹配如
下規則的數據行:以「T」開頭長度,長度任意。根據通配符表達式語法,咱們得知這個匹配規
則對應的通配符表達式爲「T%」,所以編寫以下的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE 'T%'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
HR002 Tina 25 5200.36
接着咱們來檢索姓名中包含字母「n」的員工信息,編寫以下SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '%n%'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
SALES001 John 23 5000.00
SALES003 Stone 22 1200.00
單字符匹配和多字符匹配還能夠一塊兒使用。咱們來檢索T_Employee 表中FName 字段匹
配以下規則的數據行:最後一個字符爲任意字符、倒數第二個字符爲「n」、長度任意的字符串。
根據通配符表達式語法,咱們得知這個匹配規則對應的通配符表達式爲「%n_」,所以編寫以下
的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '%n_'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
SALES003 Stone 22 1200.00
4.2.1.3 集合匹配
集合匹配只在MSSQLServer 上提供支持,在MYSQL、Oracle、DB2 等數據庫中不支持,
必須採用變通的手段來實現。
進行集合匹配的通配符爲「[]」,方括號中包含一個字符集,它匹配與字符集中任意一
個字符相匹配的字符。好比通配符表達式「[bt]%」匹配第一個字符爲b 或者t、長度不限的
字符串,「bed」、「token」、「t」都能匹配這個表達式,而「at」、「lab」、「lot」等則不能匹配
這個表達式。
下面來演示一下多字符匹配的用法。咱們來檢索T_Employee 表中FName 字段匹配如
下規則的數據行:以「S」或者「J「開頭長度,長度任意。根據通配符表達式語法,咱們得知
這個匹配規則對應的通配符表達式爲「[SJ]%」,所以編寫以下的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '[SJ]%'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES003 Stone 22 1200.00
還能夠使用否認符「^」來對集合取反,它匹配不與字符集中任意一個字符相匹配的字
符。好比通配符表達式「[^bt]%」匹配第一個字符不爲b 或者t、長度不限的字符串,「at」、
「lab」、「lot」都能匹配這個表達式,而 「bed」、「token」、「t」等則不能匹配這個表達式。
咱們來檢索T_Employee表中FName 字段匹配以下規則的數據行:不以「S」或者「J「開
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
頭長度,長度任意。根據通配符表達式語法,咱們得知這個匹配規則對應的通配符表達式爲
「[^SJ]%」,所以編寫以下的SQL:
SELECT * FROM T_Employee
WHERE FName LIKE '[^SJ]%'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
HR002 Tina 25 5200.36
SALES002 Kerry 28 6200.00
集合匹配只在MSSQLServer上提供支持,不過在其餘數據庫中咱們能夠經過變通手段來
實現相同的效果。好比下面的SQL能夠實現和本節第一個例子相同的效果:
SELECT * FROM T_Employee
WHERE FName LIKE 'S%' OR FName LIKE 'J%'
而下面的SQL能夠實現和本節第二個例子相同的效果:
SELECT * FROM T_Employee
WHERE NOT(FName LIKE 'S%') AND NOT(FName LIKE 'J%')
通配符過濾一個很是強大的功能,不過在使用通配符過濾進行檢索的時候,數據庫系統
會對全表進行掃描,因此執行速度很是慢。所以不要過度使用通配符過濾,在使用其餘方式
能夠實現的效果的時候就應該避免使用通配符過濾。
4.2.2 空值檢測
沒有添加非空約束列是能夠爲空值的(也就是NULL),有時咱們須要對空值進行檢測,
好比要查詢全部姓名未知的員工信息。既然NULL表明空值,有的開發人員試圖經過下面的
SQL語句來實現:
SELECT * FROM T_Employee
WHERE FNAME=null
這個語句是能夠執行的,不過執行之後咱們看不到任何的執行結果,那個Fnumber爲
「IT002」的數據行中Fname字段爲空,可是沒有被查詢出來。這是由於在SQL語句中對空值的
處理有些特別,不能使用普通的等於運算符進行判斷,而要使用IS NULL關鍵字,使用方法爲
「待檢測字段名IS NULL」,好比要查詢全部姓名未知的員工信息,則運行下面的SQL語句:
SELECT * FROM T_Employee
WHERE FNAME IS NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
IT002 <NULL> 27 2800.00
若是要檢測「字段不爲空」,則要使用IS NOT NULL,使用方法爲「待檢測字段名IS NOT
NULL」,好比要查詢全部姓名已知的員工信息,則運行下面的SQL語句:
SELECT * FROM T_Employee
WHERE FNAME IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
IT001 Smith 28 3900.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
SALES003 Stone 22 1200.00
IS NULL/IS NOT NULL能夠和其餘的過濾條件一塊兒使用。好比要查詢全部姓名已知且工
資小於5000的員工信息,則運行下面的SQL語句:
SELECT * FROM T_Employee
WHERE FNAME IS NOT NULL AND FSalary <5000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
IT001 Smith 28 3900.00
SALES003 Stone 22 1200.00
4.2.3 反義運算符
「=」、「<」、「>」等運算符都是用來進行數值判斷的,有的時候則會想使用這些運算符
的反義,好比「不等於」、「不小於」或者「不大於」,MSSQLServer、DB2提供了「!」運算
符來對運算符求反義,也就是「!=」表示「不等於」、「!<」表示「不小於」,而「!>」表示
「不大於」。
好比要完成下面的功能「檢索全部年齡不等於22歲而且工資不小於2000元」,咱們能夠編
寫下面的SQL語句:
SELECT * FROM T_Employee
WHERE FAge!=22 AND FSALARY!<2000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNUMBER FNAME FAGE FSALARY
DEV001 Tom 25 8300.00
DEV002 Jerry 28 2300.80
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT001 Smith 28 3900.00
「!」運算符可以把「不等於」、「不大於」、「不小於」這樣的語義直接翻譯成SQL運算
符,不過這個運算符只在MSSQLServer和DB2兩種數據庫系統上提供支持,若是在其餘數據庫
系統上則能夠用其餘的變通的方式實現,最經常使用的變通實現方式有兩種:使用同義運算符、使用
NOT運算符。
否認的語義都有對應的同義運算符,好比「不大於」的同義詞是「小於等於」、而「不小
於」的同義詞是「大於等於」,同時SQL提供了通用的表示「不等於」的運算符「<>」,這樣「不
等於」、「不大於」和「不小於」就分別能夠表示成「<>」、「<=」和「>=」。所以要完成
下面的功能「檢索全部年齡不等於22歲而且工資不小於2000元」,咱們能夠編寫下面的SQL語
句:
SELECT * FROM T_Employee
WHERE FAge<>22 AND FSALARY>=2000
NOT運算符用來將一個表達式的值取反,也就是將值爲「真」的表達式結果變爲「假」、將
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
值爲「假」的表達式結果變爲「真」,使用方式也很是簡單「NOT (表達式)」,好比要表達「年
齡不小於20」,那麼能夠以下使用「NOT(Fage<20)」。所以要完成下面的功能「檢索全部年
齡不等於22歲而且工資不小於2000元」,咱們能夠編寫下面的SQL語句:
SELECT * FROM T_Employee
WHERE NOT(FAge=22) AND NOT(FSALARY<2000)
使用「!」運算符的方式因爲只能運行在MSSQLServer和DB2兩種數據庫系統上,因此如
果應用程序有移植到其餘數據庫系統上的需求的話,就應該避免使用這種方式;使用同義運算符
的方式可以運行在全部主流數據庫系統上,不過因爲粗心等緣由,很容易將「不大於」表示成「<」,
而忘記了「不大於」是包含「小於」和「等於」這兩個意思的,這樣就會形成檢索數據的錯誤,
形成應用程序的Bug;而採用NOT運算符的方式能比較容易的表達要實現的需求,並且可以實現
複雜的嵌套,最重要的是避免了潛在的應用程序的Bug,因此除了「<>」這種方式以外,咱們推
薦使用NOT運算符的方式來表示「非」的語義。
4.2.4 多值檢測
「公司要爲年齡爲23歲、25歲和28歲的員工發福利,請將他們的年齡、工號和姓名檢索
出來」,要完成這樣的功能,咱們能夠使用OR語句來鏈接多個等於判斷。SQL語句以下:
SELECT FAge,FNumber,FName FROM T_Employee
WHERE FAge=23 OR FAge=25 OR FAge=28
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge FNumber FName
25 DEV001 Tom
28 DEV002 Jerry
23 HR001 Jane
25 HR002 Tina
28 IT001 Smith
23 SALES001 John
28 SALES002 Kerry
這裏要檢索的年齡值是不多的,只有3個,若是要求咱們「檢索年齡爲21歲、22歲、25歲、
28歲、30歲、33歲、35歲、38歲、46歲的員工信息」,那麼咱們就要用OR鏈接九個等於判斷:
SELECT FAge,FNumber,FName FROM T_Employee
WHERE FAge=21 OR FAge=22 OR FAge=25
OR FAge=28 OR FAge=30 OR FAge=33
OR FAge=35 OR FAge=38 OR FAge=46
這不只寫起來是很是麻煩的,並且維護的難度也至關大,一不當心就會形成數據錯誤。爲
瞭解決進行多個離散值的匹配問題,SQL提供了IN語句,使用IN咱們只要指定要匹配的數據集
合就能夠了,使用方法爲「IN (值1,值2,值3……)」。要完成「公司要爲年齡爲23歲、25歲
和28歲的員工發福利,請將他們的年齡、工號和姓名檢索出來」這樣功能的話,能夠使用下
面的SQL語句:
SELECT FAge,FNumber,FName FROM T_Employee
WHERE FAge IN (23,25,28)
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge FNumber FName
25 DEV001 Tom
28 DEV002 Jerry
23 HR001 Jane
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
25 HR002 Tina
28 IT001 Smith
23 SALES001 John
28 SALES002 Kerry
能夠看到執行結果和使用OR語句來鏈接多個等於判斷的方式是同樣的。
使用IN咱們還可讓字段與其餘表中的值進行匹配,好比「查找全部姓名在遲到記錄表中
的員工信息」,要實現這樣的功能就須要IN來搭配子查詢來使用,關於這一點咱們將在後面的
章節介紹。
4.2.5 範圍值檢測
使用IN語句只能進行多個離散值的檢測,若是要實現範圍值的檢測就很是麻煩甚至不可
能了。好比咱們要完成下面的功能「檢索全部年齡介於23歲到27歲之間的員工信息」,若是
用IN語句來實現的話就必須列出此範圍內的全部可能的值,SQL以下:
SELECT * FROM T_Employee
WHERE FAGE IN(23,24,25,26,27)
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT002 <NULL> 27 2800.00
SALES001 John 23 5000.00
當範圍內的值比較多的時候使用這種方式很是麻煩,好比「檢索全部年齡介於20歲到60
歲之間的員工信息」就要列出20到60之間的每個值,這個工做量是很是大的。並且這種方
式也沒法表達非離散的範圍值,好比要實現「檢索全部工資介於3000元到5000元之間的員
工信息」的話就是不可能的,由於介於3000到5000之間的值是無數的。
這種狀況下咱們能夠使用普通的「大於等於」和「小於等於」來實現範圍值檢測,好比
完成下面的功能「檢索全部年齡介於23歲到27歲之間的員工信息」,能夠使用下面的SQL語
句:
SELECT * FROM T_Employee
WHERE FAGE>=23 AND FAGE <=27
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT002 <NULL> 27 2800.00
SALES001 John 23 5000.00
這種方式可以實現幾乎全部的範圍值檢測的功能,不過SQL提供了一個專門用語範圍值檢
測的語句「BETTWEEN AND」,它能夠用來檢測一個值是否處於某個範圍中(包括範圍的邊界
值,也就是閉區間)。使用方法以下「字段名BETTWEEN 左範圍值AND 右範圍值」,其等價
於「字段名>=左範圍值AND 字段名<=右範圍值」。 好比完成下面的功能「檢索全部年齡介
於23歲到27歲之間的員工信息」,能夠使用下面的SQL語句:
SELECT * FROM T_Employee
WHERE FAGE BETWEEN 23 AND 27
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV001 Tom 25 8300.00
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT002 <NULL> 27 2800.00
SALES001 John 23 5000.00
使用「BETTWEEN AND」咱們還可以進行多個不連續範圍值的檢測,好比要實現「檢索所
有工資介於2000元到3000元之間以及5000元到8000元的員工信息」,能夠使用下面的SQL語
句:
SELECT * FROM T_Employee
WHERE (FSalary BETWEEN 2000 AND 3000)
OR (FSalary BETWEEN 5000 AND 8000)
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary
DEV002 Jerry 28 2300.80
HR001 Jane 23 2200.88
HR002 Tina 25 5200.36
IT002 <NULL> 27 2800.00
SALES001 John 23 5000.00
SALES002 Kerry 28 6200.00
數據庫系統對「BETTWEEN AND」進行了查詢優化,使用它進行範圍值檢測將會獲得比其
他方式更好的性能,所以在進行範圍值檢測的時候應該優先使用「BETTWEEN AND」。須要注
意的就是「BETTWEEN AND」在進行檢測的時候是包括了範圍的邊界值的(也就是閉區間),
若是須要進行開區間或者半開半閉區間的範圍值檢測的話就必須使用其餘的解決方案了。
4.2.6 低效的「WHERE 1=1」
網上有很多人提出過相似的問題:「看到有人寫了WHERE 1=1這樣的SQL,究竟是什麼意
思?」。其實使用這種用法的開發人員通常都是在使用動態組裝的SQL。
讓咱們想像以下的場景:用戶要求提供一個靈活的查詢界面來根據各類複雜的條件來查詢
員工信息,界面以下圖:
界面中列出了四個查詢條件,包括按工號查詢、按姓名查詢、按年齡查詢以及按工資查詢,
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
每一個查詢條件前都有一個複選框,若是複選框被選中,則表示將其作爲一個過濾條件。好比上圖
就表示「檢索工號介於DEV001和DEV008之間、姓名中含有J而且工資介於3000元到6000元的
員工信息」。若是不選中姓名前的複選框,好比下圖表示「檢索工號介於DEV001和DEV008之
間而且工資介於3000元到6000元的員工信息」:
若是將全部的複選框都不選中,則表示表示「檢索全部員工信息」,好比下圖:
這裏的數據檢索與前面的數據檢索都不同,由於前邊例子中的數據檢索的過濾條件都是
肯定的,而這裏的過濾條件則隨着用戶設置的不一樣而有變化,這時就要根據用戶的設置來動態組
裝SQL了。當不選中年齡前的複選框的時候要使用下面的SQL語句:
SELECT * FROM T_Employee
WHERE FNumber BETWEEN 'DEV001' AND 'DEV008'
AND FName LIKE '%J%'
AND FSalary BETWEEN 3000 AND 6000
而若是不選中姓名和年齡前的複選框的時候就要使用下面的SQL語句:
SELECT * FROM T_Employee
WHERE FNumber BETWEEN 'DEV001' AND 'DEV008'
AND FSalary BETWEEN 3000 AND 6000
而若是將全部的複選框都不選中的時候就要使用下面的SQL語句:
SELECT * FROM T_Employee
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
要實現這種動態的SQL語句拼裝,咱們能夠在宿主語言中創建一個字符串,而後逐個判斷各
個複選框是否選中來向這個字符串中添加SQL語句片斷。這裏有一個問題就是當有複選框被選中
的時候SQL語句是含有WHERE子句的,而當全部的複選框都沒有被選中的時候就沒有WHERE子句
了,所以在添加每個過濾條件判斷的時候都要判斷是否已經存在WHERE語句了,若是沒有
WHERE語句則添加WHERE語句。在判斷每個複選框的時候都要去判斷,這使得用起來很是麻煩,
「聰明的程序員是會偷懶的程序員」,所以開發人員想到了一個捷徑:爲SQL語句指定一個永遠
爲真的條件語句(好比「1=1」),這樣就不用考慮WHERE語句是否存在的問題了。僞代碼以下
6:
String sql = " SELECT * FROM T_Employee WHERE 1=1";
if(工號複選框選中)
{
sql.appendLine("AND FNumber BETWEEN '"+工號文本框1內容+"' AND '"+工號
文本框2內容+"'");
}
if(姓名複選框選中)
{
sql.appendLine("AND FName LIKE '%"+姓名文本框內容+"%'");
}
if(年齡複選框選中)
{
sql.appendLine("AND FAge BETWEEN "+年齡文本框1內容+" AND "+年齡文本框2
內容);
}
executeSQL(sql);
這樣若是不選中姓名和年齡前的複選框的時候就會執行下面的SQL語句:
SELECT * FROM T_Employee WHERE 1=1
AND FNumber BETWEEN 'DEV001' AND 'DEV008'
AND FSalary BETWEEN 3000 AND 6000
而若是將全部的複選框都不選中的時候就會執行下面的SQL語句:
SELECT * FROM T_Employee WHERE 1=1
這看似很是優美的解決了問題,卻不知這樣極可能會形成很是大的性能損失,由於使用添
加了「1=1」的過濾條件之後數據庫系統就沒法使用索引等查詢優化策略,數據庫系統將會被迫
對每行數據進行掃描(也就是全表掃描)以比較此行是否知足過濾條件,當表中數據量比較大的
時候查詢速度會很是慢。所以若是數據檢索對性能有比較高的要求就不要使用這種「簡便」的方
式。下面給出一種參考實現,僞代碼以下:
private void doQuery()
{
Bool hasWhere = false;
StringBuilder sql = new StringBuilder(" SELECT * FROM T_Employee");
if(工號複選框選中)
{
hasWhere = appendWhereIfNeed(sql, hasWhere);
6 這裏演示的將檢索參數值直接拼接到SQL中的作法是有必定的問題的,會形成性能問題以及注入漏洞攻
擊。爲了下降問題的複雜度,這裏規避了這個問題,在本書的後續章節將會詳細講解。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
sql.appendLine("FNumber BETWEEN '"+工號文本框1內容+"' AND '"+工號
文本框2內容+"'");
}
if(姓名複選框選中)
{
hasWhere = appendWhereIfNeed(sql, hasWhere);
sql.appendLine("FName LIKE '%"+姓名文本框內容+"%'");
}
if(年齡複選框選中)
{
hasWhere = appendWhereIfNeed(sql, hasWhere);
sql.appendLine("FAge BETWEEN "+年齡文本框1內容+" AND "+年齡文本框2
內容);
}
executeSQL(sql);
}
private Bool appendWhereIfNeed(StringBuilder sql,Bool hasWhere)
{
if(hasWhere==false)
{
sql. appendLine("WHERE");
}
else
{
sql. appendLine("AND");
}
}
4.3 數據分組
前面咱們講解了聚合函數的使用,好比要查看年齡爲23歲員工的人數,只要執行下面的SQL
就能夠:
SELECT COUNT(*) FROM T_Employee
WHERE FAge=23
但是若是咱們想查看每一個年齡段的員工的人數怎麼辦呢?一個辦法是先獲得全部員工的年
齡段信息,而後分別查詢每一個年齡段的人數,顯然這樣是很是低效且煩瑣的。這時候就是數組分
組開始顯現威力的時候了。
爲了更好的演示本節中的例子,咱們爲T_Employee表增長兩列,分別爲表示其所屬分公
司的FSubCompany字段和表示其所屬部門的FDepartment,在不一樣的數據庫下執行相應的SQL
語句:
MYSQL,MSSQLServer,DB2:
ALTER TABLE T_Employee ADD FSubCompany VARCHAR(20);
ALTER TABLE T_Employee ADD FDepartment VARCHAR(20);
Oracle:
ALTER TABLE T_Employee ADD FSubCompany VARCHAR2(20);
ALTER TABLE T_Employee ADD FDepartment VARCHAR2(20);
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
兩個字段添加完畢後還須要將表中原有數據行的這兩個字段值更新,執行下面的SQL語句:
UPDATE T_Employee SET FSubCompany='Beijing',FDepartment='Development'
WHERE FNumber='DEV001';
UPDATE T_Employee SET FSubCompany='ShenZhen',FDepartment='Development'
WHERE FNumber='DEV002';
UPDATE T_Employee SET
FSubCompany='Beijing',FDepartment='HumanResource'
WHERE FNumber='HR001';
UPDATE T_Employee SET
FSubCompany='Beijing',FDepartment='HumanResource'
WHERE FNumber='HR002';
UPDATE T_Employee SET FSubCompany='Beijing',FDepartment='InfoTech'
WHERE FNumber='IT001';
UPDATE T_Employee SET FSubCompany='ShenZhen',FDepartment='InfoTech'
WHERE FNumber='IT002';
UPDATE T_Employee SET FSubCompany='Beijing',FDepartment='Sales'
WHERE FNumber='SALES001';
UPDATE T_Employee SET FSubCompany='Beijing',FDepartment='Sales'
WHERE FNumber='SALES002';
UPDATE T_Employee SET FSubCompany='ShenZhen',FDepartment='Sales'
WHERE FNumber='SALES003';
4.3.1 數據分組入門
數據分組用來將數據分爲多個邏輯組,從而能夠對每一個組進行聚合運算。SQL語句中使用
GROUP BY子句進行分組,使用方式爲「GROUP BY 分組字段」。分組語句必須和聚合函數一
起使用,GROUP BY子句負責將數據分紅邏輯組,而聚合函數則對每個組進行統計計算。
雖然GROUP BY子句經常和聚合函數一塊兒使用,不過GROUP BY子句並非不能離開聚合函
數而單獨使用的,雖然不使用聚合函數的GROUP BY子句看起來用處不大,不過它可以幫助咱們
更好的理解數據分組的原理,因此本小節咱們將演示GROUP BY子句的分組能力。
咱們首先來看一下若是經過SQL語句實現「查看公司員工有哪些年齡段的」,由於這裏只需
要列出員工的年齡段,因此使用GROUP BY子句就徹底能夠實現:
SELECT FAge FROM T_Employee
GROUP BY FAge
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge
22
23
25
27
28
這個SQL語句處理表中的全部記錄,而且將FAge相同的數據行放到一組,分組後的數據可
以看做一個臨時的結果集,而SELECT FAge語句則取出每組的FAge字段的值,這樣咱們就獲得
上表的員工年齡段表了。
GROUP BY子句將檢索結果劃分爲多個組,每一個組是全部記錄的一個子集。上面的SQL例子
在執行的時候數據庫系統將數據分紅了下面的分組:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FNumber FName FAge FSalary FSubCompany FDepartment 分組
SALES003 Stone 22 1200.00 ShenZhen Sales 22 歲

SALES001 John 23 5000.00 Beijing Sales 23 歲
HR001 Jane 23 2200.88 Beijing HumanResource 組
HR002 Tina 25 5200.36 Beijing HumanResource 25 歲
DEV001 Tom 25 8300.00 Beijing Development 組
IT002 <NULL> 27 2800.00 ShenZhen InfoTech 27 歲

SALES002 Kerry 28 6200.00 Beijing Sales 28 歲
DEV002 Jerry 28 2300.80 ShenZhen Development 組
IT001 Smith 28 3900.00 Beijing InfoTech
須要注意的是GROUP BY子句的位置,GROUP BY子句必須放到SELECT語句的以後,若是
SELECT語句有WHERE子句,則GROUP BY子句必須放到WHERE語句的以後。好比下面的SQL語
句是錯誤的:
SELECT FAge FROM T_Employee
GROUP BY FAge
WHERE FSubCompany = 'Beijing'
而下面的SQL語句則是正確的:
SELECT FAge FROM T_Employee
WHERE FSubCompany = 'Beijing'
GROUP BY FAge
須要分組的全部列都必須位於GROUP BY子句的列名列表中,也就是沒有出如今GROUP BY
子句中的列(聚合函數除外)是不能放到SELECT語句後的列名列表中的。好比下面的SQL語句
是錯誤的:
SELECT FAge,FSalary FROM T_Employee
GROUP BY FAge
道理很是簡單,由於採用分組之後的查詢結果集是以分組形式提供的,因爲每組中人員的
員工工資都不同,因此就不存在可以統一表明本組工資水平的FSalary字段了,因此上面的
SQL語句是錯誤的。不過每組中員工的平均工資倒是可以表明本組統一工資水平的,因此能夠對
FSalary使用聚合函數,下面的SQL語句是正確的:
SELECT FAge,AVG(FSalary) FROM T_Employee
GROUP BY FAge
GROUP BY子句中能夠指定多個列,只須要將多個列的列名用逗號隔開便可。指定多個分組
規則之後,數據庫系統將按照定義的分組順序來對數據進行逐層分組,首先按照第一個分組列進
行分組,而後在每一個小組內按照第二個分組列進行再次分組……逐層分組,從而實現「組中組」
的效果,而查詢的結果集是以最末一級分組來進行輸出的。好比下面的SQL語句將會列出全部分
公司的全部部門狀況:
SELECT FSubCompany,FDepartment FROM T_Employee
GROUP BY FSubCompany,FDepartment
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FSubCompany FDepartment
Beijing Development
Beijing HumanResource
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Beijing InfoTech
Beijing Sales
ShenZhen Development
ShenZhen InfoTech
ShenZhen Sales
上面的SQL例子在執行的時候數據庫系統將數據分紅了下面的分組:
FNumber FName FAge FSalary FSubCompany FDepartment FSubCompany
分組
FDepartment
分組
DEV001 Tom 25 8300.00 Beijing Development
Beijing 組
Development

HR001 Jane 23 2200.88 Beijing HumanResource HumanResour
HR002 Tina 25 5200.36 Beijing HumanResource ce組
IT001 Smith 28 3900.00 Beijing InfoTech InfoTech 組
SALES001 John 23 5000.00 Beijing Sales Sales組
SALES002 Kerry 28 6200.00 Beijing Sales
DEV002 Jerry 28 2300.80 ShenZhen Development
ShenZhen 組
Development

IT002 <NULL> 27 2800.00 ShenZhen InfoTech InfoTech 組
SALES003 Stone 22 1200.00 ShenZhen Sales Sales組
4.3.2 數據分組與聚合函數
到目前爲止咱們使用的聚合函數都是對普通結果集進行統計的,咱們一樣能夠使用聚合函
數來對分組後的數據進行統計,也就是統計每個分組的數據。咱們甚至能夠認爲在沒有使用
GROUP BY語句中使用聚合函數不過是在一個整個結果集是一個組的分組數據中進行數據統計分
析罷了。
讓咱們來看一下「查看每一個年齡段的員工的人數」如何用數據分組來實現,下面是實現此
功能的SQL語句:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge CountOfThisAge
22 1
23 2
25 2
27 1
28 3
GROUP BY子句將檢索結果按照年齡劃分爲多個組,每一個組是全部記錄的一個子集。上面的
SQL例子在執行的時候數據庫系統將數據分紅了下面的分組:
FNumber FName FAge FSalary FSubCompany FDepartment 分組
SALES003 Stone 22 1200.00 ShenZhen Sales 22 歲

SALES001 John 23 5000.00 Beijing Sales 23 歲
HR001 Jane 23 2200.88 Beijing HumanResource 組
HR002 Tina 25 5200.36 Beijing HumanResource 25 歲
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
DEV001 Tom 25 8300.00 Beijing Development 組
IT002 <NULL> 27 2800.00 ShenZhen InfoTech 27 歲

SALES002 Kerry 28 6200.00 Beijing Sales 28 歲
DEV002 Jerry 28 2300.80 ShenZhen Development 組
IT001 Smith 28 3900.00 Beijing InfoTech
能夠看到年齡相同的員工被分到了一組,接着使用「COUNT(*)」來統計每一組中的條數,
這樣就獲得了每一個年齡段的員工的個數了。
能夠使用多個分組來實現更精細的數據統計,好比下面的SQL語句就能夠統計每一個分公司的
年齡段的人數:
SELECT FSubCompany,FAge,COUNT(*) AS CountOfThisSubCompAge FROM
T_Employee
GROUP BY FSubCompany,FAge
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FSubCompany FAge CountOfThisAge
ShenZhen 22 1
Beijing 23 2
Beijing 25 2
ShenZhen 27 1
Beijing 28 2
ShenZhen 28 1
上面的執行結果是按照數據庫系統默認的年齡進行排序的,爲了更容易的按照每一個分公司
進行查看,咱們能夠指定按照FSubCompany字段進行排序,帶ORDER BY的SQL語句以下:
SELECT FSubCompany,FAge,COUNT(*) AS CountOfThisSubCompAge FROM
T_Employee
GROUP BY FSubCompany,FAge
ORDER BY FSubCompany
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FSubCompany FAge CountOfThisSubCompAge
Beijing 23 2
Beijing 25 2
Beijing 28 2
ShenZhen 22 1
ShenZhen 27 1
ShenZhen 28 1
上面的SQL語句中,GROUP BY子句將檢索結果首先按照FSubCompany進行分組,而後在
每個分組內又按照FAge進行分組,數據庫系統將數據分紅了下面的分組:
FNumber FName FAge FSalary FSubCompany FDepartment FSubCompany
分組
FAge
分組
HR001 Jane 23 2200.88 Beijing HumanResource Beijing組 23 歲
SALES001 John 23 5000.00 Beijing Sales 組
DEV001 Tom 25 8300.00 Beijing Development 25 歲
HR002 Tina 25 5200.36 Beijing HumanResource 組
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
IT001 Smith 28 3900.00 Beijing InfoTech 28 歲
SALES002 Kerry 28 6200.00 Beijing Sales 組
SALES003 Stone 22 1200.00 ShenZhen Sales ShenZhen 組 22 歲

IT002 <NULL> 27 2800.00 ShenZhen InfoTech 27 歲

DEV002 Jerry 28 2300.80 ShenZhen Development 28 歲

「COUNT(*)」對每個分組統計總數,這樣就能夠統計出每一個公司每一個年齡段的員工的
人數了。
SUM、AVG、MIN、MAX也能夠在分組中使用。好比下面的SQL能夠統計每一個公司中的工資
的總值:
SELECT FSubCompany,SUM(FSalary) AS FSalarySUM FROM T_Employee
GROUP BY FSubCompany
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FSubCompany FSalarySUM
Beijing 30801.24
ShenZhen 6300.80
下面的SQL能夠統計每一個垂直部門中的工資的平均值:
SELECT FDepartment,SUM(FSalary) AS FSalarySUM FROM T_Employee
GROUP BY FDepartment
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FDepartment FSalarySUM
Development 10600.80
HumanResource 7401.24
InfoTech 6700.00
Sales 12400.00
下面的SQL能夠統計每一個垂直部門中員工年齡的最大值和最小值:
SELECT FDepartment,MIN(FAge) AS FAgeMIN,MAX(FAge) AS FAgeMAX FROM
T_Employee
GROUP BY FDepartment
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FDepartment FAgeMIN FAgeMAX
Development 25 28
HumanResource 23 25
InfoTech 27 28
Sales 22 28
4.3.3 HAVING 語句
有的時候須要對部分分組進行過濾,好比只檢索人數多餘1個的年齡段,有的開發人員會使
用下面的SQL語句:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
WHERE COUNT(*)>1
能夠在數據庫系統中執行下面的SQL的時候,數據庫系統會提示語法錯誤,這是由於聚合函
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
數不能在WHERE語句中使用,必須使用HAVING子句來代替,好比:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
HAVING COUNT(*)>1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge CountOfThisAge
23 2
25 2
28 3
HAVING語句中也能夠像WHERE語句同樣使用複雜的過濾條件,好比下面的SQL用來檢索人
數爲1個或者3個的年齡段,能夠使用下面的SQL:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
HAVING COUNT(*) =1 OR COUNT(*) =3
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge CountOfThisAge
22 1
27 1
28 3
也能夠使用IN操做符來實現上面的功能,SQL語句以下:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
HAVING COUNT(*) IN (1,3)
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge CountOfThisAge
22 1
27 1
28 3
HAVING語句可以使用的語法和WHERE幾乎是同樣的,不過使用WHERE的時候GROUP BY
子句要位於WHERE子句以後,而使用HAVING子句的時候GROUP BY子句要位於HAVING子句之
後,好比下面的SQL是錯誤的:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
HAVING COUNT(*) IN (1,3)
GROUP BY FAge
須要特別注意,在HAVING語句中不能包含未分組的列名,好比下面的SQL語句是錯誤的:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
GROUP BY FAge
HAVING FName IS NOT NULL
執行的時候數據庫系統會提示相似以下的錯誤信息:
HAVING 子句中的列'T_Employee.FName' 無效,由於該列沒有包含在聚合函數或GROUP BY 子句中。
須要用WHERE語句來代替HAVING,修改後的SQL語句以下:
SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee
WHERE FName IS NOT NULL
GROUP BY FAge
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FAge CountOfThisAge
22 1
23 2
25 2
28 3
4.4 限制結果集行數
在進行數據檢索的時候有時候須要只檢索結果集中的部分行,好比說「檢索成績排前三
名的學生」、「檢索工資水平排在第3位到第7位的員工信息」,這種功能被稱爲「限制結果集
行數」。在雖然主流的數據庫系統中都提供了限制結果集行數的方法,可是不管是語法仍是
使用方式都存在着很大的差別,即便是同一個數據庫系統的不一樣版本(好比MSSQLServer2000
和MSSQLServer2005)也存在着必定的差別。所以本節將按照數據庫系統來說解每種數據庫
系統對限制結果集行數的特性支持。
4.4.1 MYSQL
MYSQL中提供了LIMIT關鍵字用來限制返回的結果集,LIMIT放在SELECT語句的最後位置,
語法爲「LIMIT 首行行號,要返回的結果集的最大數目」。好比下面的SQL語句將返回按照工
資降序排列的從第二行開始(行號從0開始)的最多五條記錄:
SELECT * FROM T_Employee ORDER BY FSalary DESC LIMIT 2,5
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
HR002 Tina 25 5200.36 Beijing HumanResource
SALES001 John 23 5000.00 Beijing Sales
IT001 Smith 28 3900.00 Beijing InfoTech
IT002 <NULL> 27 2800.00 ShenZhen InfoTech
DEV002 Jerry 28 2300.80 ShenZhen Development
很顯然,下面的SQL語句將返回按照工資降序排列的前五條記錄:
SELECT * FROM T_Employee ORDER BY FSalary DESC LIMIT 0,5
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
DEV001 Tom 25 8300.00 Beijing Development
SALES002 Kerry 28 6200.00 Beijing Sales
HR002 Tina 25 5200.36 Beijing HumanResource
SALES001 John 23 5000.00 Beijing Sales
IT001 Smith 28 3900.00 Beijing InfoTech
4.4.2 MSSQLServer2000
MSSQLServer2000中提供了TOP關鍵字用來返回結果集中的前N條記錄,其語法爲
「SELECT TOP 限制結果集數目 字段列表 SELECT語句其他部分」,好比下面的SQL語句用來
檢索工資水平排在前五位(按照工資從高到低)的員工信息:
select top 5 * from T_Employee order by FSalary Desc
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
DEV001 Tom 25 8300.00 Beijing Development
SALES002 Kerry 28 6200.00 Beijing Sales
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
HR002 Tina 25 5200.36 Beijing HumanResource
SALES001 John 23 5000.00 Beijing Sales
IT001 Smith 28 3900.00 Beijing InfoTech
MSSQLServer2000沒有直接提供返回提供「檢索從第5行開始的10條數據」、「檢索第五行
至第十二行的數據」等這樣的取區間範圍的功能,不過能夠採用其餘方法來變通實現,最常
使用的方法就是用子查詢7,好比要實現檢索按照工資從高到低排序檢索從第六名開始一共
三我的的信息,那麼就能夠首先將前五名的主鍵取出來,在檢索的時候檢索排除了這五名員
工的前三我的,SQL以下:
SELECT top 3 * FROM T_Employee
WHERE FNumber NOT IN
(SELECT TOP 5 FNumber FROM T_Employee ORDER BY FSalary DESC)
ORDER BY FSalary DESC
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
IT002 <NULL> 27 2800.00 ShenZhen InfoTech
DEV002 Jerry 28 2300.80 ShenZhen Development
HR001 Jane 23 2200.88 Beijing HumanResource
4.4.3 MSSQLServer2005
MSSQLServer2005兼容幾乎全部的MSSQLServer2000的語法,因此能夠使用上個小節提到
的方式來在MSSQLServer2005中實現限制結果集行數,不過MSSQLServer2005提供了新的特性
來幫助更好的限制結果集行數的功能,這個新特性就是窗口函數ROW_NUMBER()。
ROW_NUMBER()函數能夠計算每一行數據在結果集中的行號(從1開始計數),其使用語法
以下:
ROW_NUMBER OVER(排序規則)
好比咱們執行下面的SQL語句:
SELECT ROW_NUMBER() OVER(ORDER BY FSalary),FNumber,FName,FSalary,FAge
FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FSalary FAge
1 DEV001 Tom 8300.00 25
2 SALES002 Kerry 6200.00 28
3 HR002 Tina 5200.36 25
4 SALES001 John 5000.00 23
5 IT001 Smith 3900.00 28
6 IT002 <NULL> 2800.00 27
7 DEV002 Jerry 2300.80 28
8 HR001 Jane 2200.88 23
9 SALES003 Stone 1200.00 22
能夠看到第一列中的數據就是經過ROW_NUMBER()計算出來的行號。有的開發人員想使用
以下的方式來實現返回第3行到第5行的數據(按照工資降序):
SELECT ROW_NUMBER() OVER(ORDER BY FSalary
7 在本書的後面章節將會詳細介紹子查詢。對於子查詢不熟悉的讀者能夠暫時認爲子查詢就是將另一個
查詢結果看成一個新的表進行檢索。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
DESC),FNumber,FName,FSalary,FAge
FROM T_Employee
WHERE (ROW_NUMBER() OVER(ORDER BY FSalary DESC))>=3
AND (ROW_NUMBER() OVER(ORDER BY FSalary DESC))<=5
可是在運行的時候數據庫系統會報出下面的錯誤信息:
開窗函數只能出如今SELECT 或ORDER BY 子句中。
也就是說ROW_NUMBER()不能用在WHERE語句中。咱們能夠用子查詢來解決這個問題,下面
的SQL語句用來返回第3行到第5行的數據:
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY FSalary DESC) AS rownum,
FNumber,FName,FSalary,FAge FROM T_Employee
) AS a
WHERE a.rownum>=3 AND a.rownum<=5
執行完畢咱們就能在輸出結果中看到下面的執行結果:
rownum FNumber FName FSalary FAge
3 HR002 Tina 5200.36 25
4 SALES001 John 5000.00 23
5 IT001 Smith 3900.00 28
4.4.4 Oracle
Oracle中支持窗口函數ROW_NUMBER(),其用法和MSSQLServer2005中相同,好比咱們執
行下面的SQL語句:
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY FSalary DESC) row_num,
FNumber,FName,FSalary,FAge FROM T_Employee
) a
WHERE a.row_num>=3 AND a.row_num<=5
執行完畢咱們就能在輸出結果中看到下面的執行結果:
ROW_NUM FNUMBER FNAME FSALARY FAGE
3 HR002 Tina 5200.36 25
4 SALES001 John 5000 23
5 IT001 Smith 3900 28
注意:rownum在Oracle中爲保留字,因此這裏將MSSQLServer2005中用到的rownum替換
爲row_num;Oracle中定義表別名的時候不能使用AS關鍵字,因此這裏也去掉了AS。
Oracle支持標準的函數ROW_NUMBER(),不過Oracle中提供了更方便的特性用來計算行號,
也就在Oracle中能夠無需自行計算行號,Oracle爲每一個結果集都增長了一個默認的表示行號
的列,這個列的名稱爲rownum。好比咱們執行下面的SQL語句:
SELECT rownum,FNumber,FName,FSalary,FAge FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
ROWNUM FNUMBER FNAME FSALARY FAGE
1 DEV001 Tom 8300 25
2 DEV002 Jerry 2300.8 28
3 SALES001 John 5000 23
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
4 SALES002 Kerry 6200 28
5 SALES003 Stone 1200 22
6 HR001 Jane 2200.88 23
7 HR002 Tina 5200.36 25
8 IT001 Smith 3900 28
9 IT002 <NULL> 2800 27
使用rownum咱們能夠很輕鬆的取得結果集中前N條的數據行,好比咱們執行下面的SQL
語句能夠獲得按工資從高到底排序的前6名員工的信息:
SELECT * FROM T_Employee
WHERE rownum<=6
ORDER BY FSalary Desc
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNUMBER FNAME FAGE FSALARY FSUBCOMPANY FDEPARTMENT
DEV001 Tom 25 8300 Beijing Development
SALES002 Kerry 28 6200 Beijing Sales
SALES001 John 23 5000 Beijing Sales
DEV002 Jerry 28 2300.8 ShenZhen Development
HR001 Jane 23 2200.88 Beijing HumanResource
SALES003 Stone 22 1200 ShenZhen Sales
看到這裏,您可能認爲下面的SQL就能夠很是容易的實現「按照工資從高到低的順序取
出第三個到第五個員工信息」的功能了:
SELECT rownum,FNumber,FName,FSalary,FAge FROM T_Employee
WHERE rownum BETWEEN 3 AND 5
ORDER BY FSalary DESC
執行完畢咱們就能在輸出結果中看到下面的執行結果:
ROWNUM FNUMBER FNAME FSALARY FAGE
檢索結果爲空!!!這很是出乎咱們的意料。讓咱們來回顧一下rownum的含義:rownum
爲結果集中每一行的行號(從1開始計數)。對於下面的SQL:
SELECT * FROM T_Employee
WHERE rownum<=6
ORDER BY FSalary Desc
當進行檢索的時候,對於第一條數據,其rownum爲1,由於符合「WHERE rownum<=6」
因此被放到了檢索結果中;當檢索到第二條數據的時候,其rownum爲2,由於符合「WHERE
rownum<=6」因此被放到了檢索結果中……依次類推,直到第七行。因此這句SQL語句可以實
現「按照工資從高到低的順序取出第三個到第五個員工信息」的功能。
而對於這句SQL語句:
SELECT rownum,FNumber,FName,FSalary,FAge FROM T_Employee
WHERE rownum BETWEEN 3 AND 5
ORDER BY FSalary DESC
當進行檢索的時候,對於第一條數據,其rownum爲1,由於不符合「WHERE rownum
BETWEEN 3 AND 5」,因此沒有被放到了檢索結果中;當檢索到第二條數據的時候,由於第
一條數據沒有放到結果集中,因此第二條數據的rownum仍然爲1,而不是咱們想像的2,因此因
爲不符合「WHERE rownum<=6」,沒有被放到了檢索結果中;當檢索到第三條數據的時候,
由於第1、二條數據沒有放到結果集中,因此第三條數據的rownum仍然爲1,而不是咱們想像
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
的3,因此由於不符合「WHERE rownum<=6」,沒有被放到了檢索結果中……依此類推,這樣
全部的數據行都沒有被放到結果集中。
所以若是要使用rownum來實現「按照工資從高到低的順序取出第三個到第五個員工信息」
的功能,就必須藉助於窗口函數ROW_NUMBER()。
4.4.5 DB2
DB2中支持窗口函數ROW_NUMBER(),其用法和MSSQLServer2005以及Oracle中相同,比
如咱們執行下面的SQL語句:
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY FSalary DESC) row_num,
FNumber,FName,FSalary,FAge FROM T_Employee
) a
WHERE a.row_num>=3 AND a.row_num<=5
執行完畢咱們就能在輸出結果中看到下面的執行結果:
ROW_NUM FNUMBER FNAME FSALARY FAGE
3 HR002 Tina 5200.36 25
4 SALES001 John 5000.00 23
5 IT001 Smith 3900.00 28
除此以外,DB2還提供了FETCH關鍵字用來提取結果集的前N行,其語法爲「FETCH FIRST
條數 ROWS ONLY」,好比咱們執行下面的SQL語句能夠獲得按工資從高到底排序的前6名員工
的信息:
SELECT * FROM T_Employee
ORDER BY FSalary Desc
FETCH FIRST 6 ROWS ONLY
須要注意的是FETCH子句要放到ORDER BY語句的後面,執行完畢咱們就能在輸出結果中
看到下面的執行結果:
FNUMBER FNAME FAGE FSALARY FSUBCOMPANY FDEPARTMENT
DEV001 Tom 25 8300.00 Beijing Development
SALES002 Kerry 28 6200.00 Beijing Sales
HR002 Tina 25 5200.36 Beijing HumanResource
SALES001 John 23 5000.00 Beijing Sales
IT001 Smith 28 3900.00 Beijing InfoTech
IT002 <NULL> 27 2800.00 ShenZhen InfoTech
DB2沒有直接提供返回提供「檢索從第5行開始的10條數據」、「檢索第五行至第十二行
的數據」等這樣的取區間範圍的功能,不過能夠採用其餘方法來變通實現,最常使用的方法
就是用子查詢,好比要實現檢索按照工資從高到低排序檢索從第六名開始一共三我的的信息,
那麼就能夠首先將前五名的主鍵取出來,在檢索的時候檢索排除了這五名員工的前三我的,
SQL以下:
SELECT * FROM T_Employee
WHERE FNumber NOT IN
(
SELECT FNumber FROM T_Employee
ORDER BY FSalary DESC
FETCH FIRST 5 ROWS ONLY
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
)
ORDER BY FSalary DESC
FETCH FIRST 3 ROWS ONLY
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNUMBER FNAME FAGE FSALARY FSUBCOMPANY FDEPARTMENT
IT002 <NULL> 27 2800.00 ShenZhen InfoTech
DEV002 Jerry 28 2300.80 ShenZhen Development
HR001 Jane 23 2200.88 Beijing HumanResource
4.4.6 數據庫分頁
在進行信息檢索的時候,檢索結果的數量一般會很是多,達到成百上千條,甚至更多,
這麼多的檢索結果同時顯示在同一個界面中,不只查看起來很是麻煩,並且過多的數據顯示
在界面上也會形成佔用過多的系統資源。解決這個問題的最經常使用方案就是數據庫分頁,就像
咱們在論壇上查看帖子的時候,一個網頁中只顯示50條帖子,論壇會提供【上一頁】、【下一
頁】、【首頁】以及【尾頁】等按鈕用來顯示不一樣的頁。實現數據庫分頁的核心技術就是「限
制結果集行數」。
假設每一頁顯示的數據條數爲PageSize,當前頁數(從0開始技術)爲CurrentIndex,那
麼咱們只要查詢從第PageSize*CurrentIndex開始的PageSize條數據獲得的結果就是當前頁中
的數據;當用戶點擊【上一頁】按鈕的時候,將CurrentIndex設置爲CurrentIndex-1,而後重
新檢索;當用戶點擊【下一頁】按鈕的時候,將CurrentIndex設置爲CurrentIndex+1,而後重
新檢索;當用戶點擊【首頁】按鈕的時候,將CurrentIndex設置爲0,而後從新檢索;當用戶
點擊【首頁】按鈕的時候,將CurrentIndex設置爲「總條數/PageSize」,而後從新檢索。
下面咱們將要用僞代碼來演示數據庫分頁功能,其中的數據庫系統使用的MYSQL:
int CurrentIndex=0;
PageSize=10;
//按鈕【首頁】被點擊
private void btnFirstBu onCl ick( )
{
CurrentIndex=0;
DoSearch();
}
//按鈕【尾頁】被點擊
private void btnLastBu onCl ick( )
{
CurrentIndex=GetTotalCount()/PageSize;
DoSearch();
}
//按鈕【下一頁】被點擊
private void btnNextBu onCl ick( )
{
CurrentIndex= CurrentIndex+1;
DoSearch();
}
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
//按鈕【上一頁】被點擊
private void btnNextBu onCl ick( )
{
CurrentIndex= CurrentIndex-1;
DoSearch();
}
//計算表中的總數據條數
private int GetTotalCount()
{
ResultSet rs = ExecuteSQL("SELECT COUNT(*) AS TOTALCOUNT FROM T_Employee ");
return rs.getInt("TOTALCOUNT");
}
//查詢當前頁中的數據
private void DoSearch()
{
//計算當前頁的起始行數
String startIndex = (CurrentIndex* PageSize).ToString();
String size = PageSize.ToString()
ResultSet rs = ExecuteSQL("SELECT * FROM T_Employee LIMIT "
+ startIndex +","+ size);
//顯示查詢結果
DisplayResult(rs);
}
4.5 抑制數據重複
若是要檢索公司裏有哪些垂直部門,那麼能夠執行下面的SQL語句:
SELECT FDepartment FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FDepartment
Development
Development
HumanResource
HumanResource
InfoTech
InfoTech
Sales
Sales
Sales
這裏列出了公司全部的垂直部門,不過不少部門名稱是重複的,咱們必須去掉這些重複
的部門名稱,每一個重複部門只保留一個名稱。DISTINCT關鍵字是用來進行重複數據抑制的最
簡單的功能,並且全部的數據庫系統都支持DISTINCT,DISTINCT的使用也很是簡單,只要在
SELECT以後增長DISTINCT便可。好比下面的SQL語句用於檢索公司裏有哪些垂直部門,而且
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
抑制了重複數據的產生:
SELECT DISTINCT FDepartment FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FDepartment
Development
HumanResource
InfoTech
Sales
DISTINCT是對整個結果集進行數據重複抑制的,而不是針對每個列,執行下面的SQL語
句:
SELECT DISTINCT FDepartment,FSubCompany FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FDepartment FSubCompany
Development Beijing
Development ShenZhen
HumanResource Beijing
InfoTech Beijing
InfoTech ShenZhen
Sales Beijing
Sales ShenZhen
檢索結果中不存在FDepartment和FSubCompany列都重複的數據行,可是卻存在
FDepartment列重複的數據行,這就驗證了「DISTINCT是對整個結果集進行數據重複抑制的」
這句話。
4.6 計算字段
存在數據庫系統中的數據的默認展示方式不必定徹底符合應用的要求,好比:
l 數據庫系統中姓名、工號是單獨存儲在兩個字段的,可是在顯示的時候想顯示成「姓名
+工號」的形式。
l 數據庫系統中金額的顯示格式是普通的數字顯示方式(好比668186.99),可是顯示的時
候想以千分位的形式顯示(好比668,186.99)。
l 數據庫系統中基本工資、獎金是單獨存儲在兩個字段的,可是但願顯示員工的工資總額。
l 要檢索工資總額的80%超過5000元的員工信息。
l 要升級員工工號,須要將全部員工的工號前增長兩位0。
全部這些功能都不能經過簡單的SQL語句來完成的,由於須要的數據不是數據表中原本
就有的,必須通過必定的計算、轉換或者格式化,這種狀況下咱們能夠在宿主語言中經過編
寫代碼的方式來進行這些計算、轉換或者格式化的工做,可是能夠想象當數據量比較大的時
候這樣處理的速度是很是慢的。計算字段是數據庫系統提供的對數據進行計算、轉換或者格
式化的功能,因爲是在數據庫系統內部進行的這些工做,並且數據庫系統都這些工做進行了
優化,因此其處理效率比在宿主語言中經過編寫代碼的方式進行處理要高效的多。本節將介
紹計算字段的基本使用以及在SELECT、Update、Delete等語句中的應用。
4.6.1 常量字段
軟件協會要求各個公司提供全部員工的資料信息,其中包括公司名稱、註冊資本、員工
姓名、年齡、所在子公司,並且出於特殊考慮,要求每一個員工都列出這些資料信息。對於單
個公司而言,公司名稱、註冊資本這兩部分信息不是能從現有的T_Employee,可是它們是
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
肯定的值,所以咱們編寫下面的SQL語句:
SELECT 'CowNew集團',918000000,FName,FAge,FSubCompany FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FAge FSubCompany
CowNew 集團 918000000 Tom 25 Beijing
CowNew 集團 918000000 Jerry 28 ShenZhen
CowNew 集團 918000000 Jane 23 Beijing
CowNew 集團 918000000 Tina 25 Beijing
CowNew 集團 918000000 Smith 28 Beijing
CowNew 集團 918000000 <NULL> 27 ShenZhen
CowNew 集團 918000000 John 23 Beijing
CowNew 集團 918000000 Kerry 28 Beijing
CowNew 集團 918000000 Stone 22 ShenZhen
這裏的'CowNew集團'和918000000並非一個實際的存在的列,可是在查詢出來的數據
中它們看起來是一個實際存在的字段,這樣的字段被稱爲「常量字段」(也稱爲「常量值」),
它們徹底能夠被當作一個值肯定的字段,好比能夠爲常量字段指定別名,執行下面的SQL語
句:
SELECT 'CowNew集團' AS CompanyName,918000000 AS RegAmount,FName,FAge,FSubCompany
FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CompanyName RegAmount FName FAge FSubCompany
CowNew 集團 918000000 Tom 25 Beijing
CowNew 集團 918000000 Jerry 28 ShenZhen
CowNew 集團 918000000 Jane 23 Beijing
CowNew 集團 918000000 Tina 25 Beijing
CowNew 集團 918000000 Smith 28 Beijing
CowNew 集團 918000000 <NULL> 27 ShenZhen
CowNew 集團 918000000 John 23 Beijing
CowNew 集團 918000000 Kerry 28 Beijing
CowNew 集團 918000000 Stone 22 ShenZhen
4.6.2 字段間計算
人力資源部要求統計全體員工的工資指數,工資指數的計算公式爲年齡與工資的乘積,
這就須要計算將FAge和FSalary的乘積作爲一個工資指數列體現到檢索結果中,執行下面的
SQL語句:
SELECT FNumber,FName,FAge * FSalary FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName
DEV001 Tom 207500.00
DEV002 Jerry 64422.40
HR001 Jane 50620.24
HR002 Tina 130009.00
IT001 Smith 109200.00
IT002 <NULL> 75600.00
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SALES001 John 115000.00
SALES002 Kerry 173600.00
SALES003 Stone 26400.00
一樣,這裏的FAge * FSalary並非一個實際的存在的列,可是在查詢出來的數據中
它們看起來是一個實際存在的字段,它們徹底能夠被當作一個普通字段,好比能夠爲此字段
指定別名,執行下面的SQL語句:
SELECT FNumber,FName,FAge * FSalary AS FSalaryIndex FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FSalaryIndex
DEV001 Tom 207500.00
DEV002 Jerry 64422.40
HR001 Jane 50620.24
HR002 Tina 130009.00
IT001 Smith 109200.00
IT002 <NULL> 75600.00
SALES001 John 115000.00
SALES002 Kerry 173600.00
SALES003 Stone 26400.00
前面提到常量字段徹底能夠看成普通字段來看待,那麼普通字段也能夠和常量字段進行
計算,甚至常量字段之間也能夠進行計算。好比人力資源部要求統計每一個員工的工資幸福指
數,工資幸福指數的計算公式爲工資/(年齡-21),並且要求在每行數據前添加一列,這列
的值等於125與521的和。咱們編寫下面的SQL:
SELECT 125+521,FNumber,FName,FSalary/(FAge-21) AS FHappyIndex
FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果8:
FNumber FName FHappyIndex
646 DEV001 Tom 2075.0000000000000
646 DEV002 Jerry 328.6857142857142
646 HR001 Jane 1100.4400000000000
646 HR002 Tina 1300.0900000000000
646 IT001 Smith 557.1428571428571
646 IT002 <NULL> 466.6666666666666
646 SALES001 John 2500.0000000000000
646 SALES002 Kerry 885.7142857142857
646 SALES003 Stone 1200.0000000000000
計算字段也能夠在WHERE語句等子句或者UPDATE、DELETE中使用。好比下面的SQL用來
檢索全部資幸福指數大於1000的員工信息:
SELECT * FROM T_Employee
WHERE FSalary/(FAge-21)>1000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
DEV001 Tom 25 8300.00 Beijing Development
8 因爲不一樣的數據庫系統對於除法的精度處理不一樣,因此FHappyIndex的顯示精度也不同。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
HR001 Jane 23 2200.88 Beijing HumanResource
HR002 Tina 25 5200.36 Beijing HumanResource
SALES001 John 23 5000.00 Beijing Sales
SALES003 Stone 22 1200.00 ShenZhen Sales
4.6.3 數據處理函數
像普通編程語言同樣,SQL也支持使用函數處理數據,函數使用若干字段名或者常量值
作爲參數;參數的數量是不固定的,有的函數的參數爲空,甚至有的函數的參數個數可變;
幾乎全部函數都有返回值,返回值即爲函數的數據處理結果。
其實在前面的章節中咱們已經用到函數了,最典型的就是「聚合函數」, 「聚合函數」
是函數的一種,它們能夠對一組數據進行統計計算。除了「聚合函數」,SQL中還有其餘類
型的函數,好比進行數值處理的數學函數、進行日期處理的日期函數、進行字符串處理的字
符串函數等。
咱們來演示幾個函數使用的典型場景。
主流數據庫系統都提供了計算字符串長度的函數,在MYSQL、Oracle、DB2中這個函數
名稱爲LENGTH,而在MSSQLServer中這個函數的名稱則爲LEN。這個函數接受一個字符串類
型的字段值作爲參數,返回值爲這個字符串的長度。下面的SQL語句計算每個名稱不爲空
的員工的名字以及名字的長度:
MYSQL、Oracle、DB2:
SELECT FName, LENGTH(FName) AS namelength FROM T_Employee
WHERE FName IS NOT NULL
MSSQLServer:
SELECT FName, LEN(FName) AS namelength FROM T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName namelength
Tom 3
Jerry 5
Jane 4
Tina 4
Smith 5
John 4
Kerry 5
Stone 5
主流系統都提供了取得字符串的子串的函數,在MYSQL、MSSQLServer中這個函數名稱
爲SUBSTRING,而在Oracle、DB2這個函數名稱爲SUBSTR。這個函數接受三個參數,第一個
參數爲要取的主字符串,第二個參數爲字串的起始位置(從1開始計數),第三個參數爲字串
的長度。下面的SQL語句取得每個名稱不爲空的員工的名字以及名字中從第二個字符開始、
長度爲3的字串:
MYSQL、MSSQLServer:
SELECT FName, SUBSTRING(FName,2,3) FROM T_Employee
WHERE FName IS NOT NULL
Oracle、DB2:
SELECT FName, SUBSTR(FName,2,3) FROM T_Employee
WHERE FName IS NOT NULL
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName namelength
Tom om
Jerry er
Jane an
Tina in
Smith mi
John oh
Kerry er
Stone to
多個函數還能夠嵌套使用。主流系統都提供了計算正弦函數值的函數SIN和計算絕對值
的函數ABS,它們都接受一個數值類型的參數。下面的SQL語句取得每一個員工的姓名、年齡、
年齡的正弦函數值以及年齡的正弦函數值的絕對值,其中計算「年齡的正弦函數值的絕對值」
時就要使用嵌套函數,SQL語句以下:
SELECT FName,FAge, SIN(FAge) , ABS(SIN(FAge)) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FAge
Tom 25 -0.13235175009777303 0.13235175009777303
Jerry 28 0.27090578830786904 0.27090578830786904
Jane 23 -0.8462204041751706 0.8462204041751706
Tina 25 -0.13235175009777303 0.13235175009777303
Smith 28 0.27090578830786904 0.27090578830786904
<NULL> 27 0.956375928404503 0.956375928404503
John 23 -0.8462204041751706 0.8462204041751706
Kerry 28 0.27090578830786904 0.27090578830786904
Stone 22 -0.008851309290403876 0.008851309290403876
數據庫系統提供的函數是很是豐富的,並且不一樣的數據庫系統提供的函數差別也很是大,
本書後面章節將對這些函數進行詳細講解。
4.6.4 字符串的拼接
SQL容許兩個或者多個字段之間進行計算,字符串類型的字段也不例外。好比咱們須要
以「工號+姓名」的方式在報表中顯示一個員工的信息,那麼就須要把工號和姓名兩個字符
串類型的字段拼接計算;再如咱們須要在報表中在每一個員工的工號前增長「Old」這個文本。
這時候就須要咱們對字符串類型的字段(包括字符串類型的常量字段)進行拼接。在不一樣的
數據庫系統下的字符串拼接是有很大差別的,所以這裏咱們將講解主流數據庫下的字符串拼
接的差別。
須要注意的是,在Java、C#等編程語言中字符串是用半角的雙引號來包圍的,可是在有
的數據庫系統的SQL語法中雙引號有其餘的含義(好比列的別名),而全部的數據庫系統都
支持用單引號包圍的形式定義的字符串,因此建議讀者使用以單引號包圍的形式定義的字符
串,並且本書也將使用這種方式。
4.6.4.1 MYSQL
在Java、C#等編程語言中字符串的拼接能夠經過加號「+」來實現,好比:"1"+"3"、"a"+"b"。
在MYSQL中也能夠使用加號「+」來鏈接兩個字符串,好比下面的SQL:
SELECT '12'+'33',FAge+'1' FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
'12'+'33' FAge+'1'
45 26
45 29
45 24
45 26
45 29
45 28
45 24
45 29
45 23
仔細觀察第一列,驚訝嗎?這個列的顯示結果並非咱們但願的「1233」,而是把「12」
和「33」兩個字符串當成數字來求兩個數的和了;一樣將一個數字與一個字符串用加號「+」
鏈接也是一樣的效果,好比這裏的第二列。
在MYSQL中,當用加號「+」鏈接兩個字段(或者多個字段)的時候,MYSQL會嘗試將
字段值轉換爲數字類型(若是轉換失敗則認爲字段值爲0),而後進行字段的加法運算。所以,
當計算的'12'+'33'的時候,MYSQL會將「12」和「33」兩個字符串嘗試轉換爲數字類型的
12和33,而後計算12+33的值,這就是爲何咱們會獲得45的結果了。一樣道理,在計算
FAge+'1'的時候,因爲FAge爲數字類型,因此不須要進行轉換,而'1'爲字符串類型,因此
MYSQL將'1'嘗試轉換爲數字1,而後計算FAge+1作爲計算列的值。
MYSQL會嘗試將加號兩端的字段值嘗試轉換爲數字類型,若是轉換失敗則認爲字段值爲
0,好比咱們執行下面的SQL語句:
SELECT 'abc'+'123',FAge+'a' FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
'abc'+'123' FAge+'a'
123 25
123 28
123 23
123 25
123 28
123 27
123 23
123 28
123 22
在MYSQL中進行字符串的拼接要使用CONCAT函數,CONCAT函數支持一個或者多個參數,
參數類型能夠爲字符串類型也能夠是非字符串類型,對於非字符串類型的參數MYSQL將嘗試
將其轉化爲字符串類型,CONCAT函數會將全部參數按照參數的順序拼接成一個字符串作爲
返回值。好比下面的SQL語句用於將用戶的多個字段信息以一個計算字段的形式查詢出來:
SELECT CONCAT('工號爲:',FNumber,'的員工的幸福指數:',FSalary/(FAge-21))
FROM T_Employee 執行完畢咱們就能在輸出結果中看到下面的執行結果:
CONCAT('工號爲:',FNumber,'的員工的幸福指數:',FSalary/(FAge-21))
工號爲:DEV001的員工的幸福指數:2075.000000
工號爲:DEV002的員工的幸福指數:328.685714
工號爲:HR001的員工的幸福指數:1100.440000
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
工號爲:HR002的員工的幸福指數:1300.090000
工號爲:IT001的員工的幸福指數:557.142857
工號爲:IT002的員工的幸福指數:466.666667
工號爲:SALES001 的員工的幸福指數:2500.000000
工號爲:SALES002 的員工的幸福指數:885.714286
工號爲:SALES003 的員工的幸福指數:1200.000000
CONCAT支持只有一個參數的用法,這時的CONCAT能夠看做是一個將這個參數值嘗試轉
化爲字符串類型值的函數。MYSQL中還提供了另一個進行字符串拼接的函數CONCAT_WS,
CONCAT_WS能夠在待拼接的字符串之間加入指定的分隔符,它的第一個參數值爲採用的分
隔符,而剩下的參數則爲待拼接的字符串值,好比執行下面的SQL:
SELECT CONCAT_WS(',',FNumber,FAge,FDepartment,FSalary) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CONCAT_WS(',',FNumber,FAge,FDepartment,FSalary)
DEV001,25,Development,8300.00
DEV002,28,Development,2300.80
HR001,23,HumanResource,2200.88
HR002,25,HumanResource,5200.36
IT001,28,InfoTech,3900.00
IT002,27,InfoTech,2800.00
SALES001,23,Sales,5000.00
SALES002,28,Sales,6200.00
SALES003,22,Sales,1200.00
4.6.4.2 MSSQLServer
與MYSQL不一樣,MSSQLServer中能夠直接使用加號「+」來拼接字符串。好比執行下面的
SQL語句:
SELECT '工號爲'+FNumber+'的員工姓名爲'+Fname FROM T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
工號爲DEV001的員工姓名爲Tom
工號爲DEV002的員工姓名爲Jerry
工號爲HR001的員工姓名爲Jane
工號爲HR002的員工姓名爲Tina
工號爲IT001的員工姓名爲Smith
工號爲SALES001 的員工姓名爲John
工號爲SALES002 的員工姓名爲Kerry
工號爲SALES003 的員工姓名爲Stone
4.6.4.3 Oracle
Oracle中使用「||」進行字符串拼接,其使用方式和MSSQLServer中的加號「+」同樣。
好比執行下面的SQL語句:
SELECT '工號爲'||FNumber||'的員工姓名爲'||FName FROM T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
工號爲||FNUMBER||的員工姓名爲||FNAME
工號爲DEV001的員工姓名爲Tom
工號爲DEV002的員工姓名爲Jerry
工號爲SALES001 的員工姓名爲John
工號爲SALES002 的員工姓名爲Kerry
工號爲SALES003 的員工姓名爲Stone
工號爲HR001的員工姓名爲Jane
工號爲HR002的員工姓名爲Tina
工號爲IT001的員工姓名爲Smith
除了「||」,Oracle還支持使用CONCAT()函數進行字符串拼接,好比執行下面的SQL語句:
SELECT CONCAT('工號:',FNumber) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CONCAT(工號:,FNUMBER)
工號:DEV001
工號:DEV002
工號:HR001
工號:HR002
工號:IT001
工號:IT002
工號:SALES001
工號:SALES002
工號:SALES003
若是CONCAT中鏈接的值不是字符串,Oracle會嘗試將其轉換爲字符串,好比執行下面的
SQL語句:
SELECT CONCAT('年齡:',FAge) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CONCAT(年齡:,FAGE)
年齡:25
年齡:28
年齡:23
年齡:28
年齡:22
年齡:23
年齡:25
年齡:28
年齡:27
與MYSQL的CONCAT()函數不一樣,Oracle的CONCAT()函數只支持兩個參數,不支持兩個以
上字符串的拼接,好比下面的SQL語句在Oracle中是錯誤的:
SELECT CONCAT('工號爲',FNumber,'的員工姓名爲',FName) FROM T_Employee
WHERE FName IS NOT NULL
運行之後Oracle會報出下面的錯誤信息:
參數個數無效
若是要進行多個字符串的拼接的話,能夠使用多個CONCAT()函數嵌套使用,上面的SQL能夠如
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
下改寫:
SELECT CONCAT(CONCAT(CONCAT('工號爲',FNumber),'的員工姓名爲'),FName) FROM
T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CONCAT(CONCAT(CONCAT(工號爲,FNUMBER),的員工姓名爲),FNAME)
工號爲DEV001的員工姓名爲Tom
工號爲DEV002的員工姓名爲Jerry
工號爲SALES001的員工姓名爲John
工號爲SALES002的員工姓名爲Kerry
工號爲SALES003的員工姓名爲Stone
工號爲HR001的員工姓名爲Jane
工號爲HR002的員工姓名爲Tina
工號爲IT001的員工姓名爲Smith
4.6.4.4 DB2
DB2中使用「||」進行字符串拼接,其使用方式和MSSQLServer中的加號「+」同樣。比
如執行下面的SQL語句:
SELECT '工號爲'||FNumber||'的員工姓名爲'||FName FROM T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
工號爲DEV001的員工姓名爲Tom
工號爲DEV002的員工姓名爲Jerry
工號爲SALES001 的員工姓名爲John
工號爲SALES002 的員工姓名爲Kerry
工號爲SALES003 的員工姓名爲Stone
工號爲HR001的員工姓名爲Jane
工號爲HR002的員工姓名爲Tina
工號爲IT001的員工姓名爲Smith
除了「||」,DB2還支持使用CONCAT()函數進行字符串拼接,好比執行下面的SQL語句:
SELECT CONCAT('工號:',FNumber) FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
工號:DEV001
工號:DEV002
工號:HR001
工號:HR002
工號:IT001
工號:IT002
工號:SALES001
工號:SALES002
工號:SALES003
與Oracle不一樣,若是CONCAT中鏈接的值不是字符串,則DB2不會嘗試進行類型轉換而是
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
報出錯誤信息,好比執行下面的SQL語句是錯誤的:
SELECT CONCAT('年齡:',FAge) FROM T_Employee
運行之後DB2會報出下面的錯誤信息:
未找到類型爲"FUNCTION" 命名爲 "CONCAT" 且具備兼容自變量的已受權例程
與MYSQL的CONCAT()函數不一樣,DB2的CONCAT()函數只支持兩個參數,不支持兩個以上
字符串的拼接,好比下面的SQL語句在Oracle中是錯誤的:
SELECT CONCAT('工號爲',FNumber,'的員工姓名爲',FName) FROM T_Employee
WHERE FName IS NOT NULL
運行之後Oracle會報出下面的錯誤信息:
未找到類型爲"FUNCTION" 命名爲 "CONCAT" 且具備兼容自變量的已受權例程
若是要進行多個字符串的拼接的話,能夠使用多個CONCAT()函數嵌套使用,上面的SQL能夠如
下改寫:
SELECT CONCAT(CONCAT(CONCAT('工號爲',FNumber),'的員工姓名爲'),FName) FROM
T_Employee
WHERE FName IS NOT NULL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
工號爲DEV001的員工姓名爲Tom
工號爲DEV002的員工姓名爲Jerry
工號爲SALES001的員工姓名爲John
工號爲SALES002的員工姓名爲Kerry
工號爲SALES003的員工姓名爲Stone
工號爲HR001的員工姓名爲Jane
工號爲HR002的員工姓名爲Tina
工號爲IT001的員工姓名爲Smith
4.6.5 計算字段的其餘用途
咱們不只能在SELECT語句中使用計算字段,咱們一樣能夠在進行數據過濾、數據刪除以
及數據更新的時候使用計算字段,下面咱們舉幾個例子。
4.6.5.1 計算處於合理工資範圍內的員工
咱們規定一個合理工資範圍:上限爲年齡的1.8倍加上5000元,下限爲年齡的1.5倍加上
2000元,介於這二者之間的即爲合理工資。咱們須要查詢全部處於合理工資範圍內的員工信
息。所以編寫以下的SQL語句:
SELECT * FROM T_Employee
WHERE Fsalary BETWEEN Fage*1.5+2000 AND Fage*1.8+5000
這裏咱們在BETWEEN……AND……語句中使用了計算表達式。執行完畢咱們就能在輸出結
果中看到下面的執行結果:
FNumber FName FAge FSalary FSubCompany FDepartment
DEV002 Jerry 28 2300.80 ShenZhen Development
HR001 Jane 23 2200.88 Beijing HumanResource
IT001 Smith 28 3900.00 Beijing InfoTech
IT002 <NULL> 27 2800.00 ShenZhen InfoTech
SALES001 John 23 5000.00 Beijing Sales
4.6.5.2 查詢「工資年齡指數」
咱們定義「工資年齡指數」爲「工資除以年齡」。咱們須要查詢「工資年齡指數」的最
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
高值和最低值。所以編寫以下的SQL語句:
SELECT MAX(FSalary/FAge) AS MAXVALUE,MIN(FSalary/FAge) AS MINVALUE
FROM T_Employee
這裏咱們在MAX、MIN函數中使用了計算字段。執行完畢咱們就能在輸出結果中看到下
面的執行結果:
MAXVALUE MINVALUE
332.0000000000000 54.5454545454545
4.6.5.3 年齡所有加1
新的一年到來了,系統須要自動將員工的年齡所有加1。這個工做若是使用代碼來完成
的話會是這樣:
result = executeQuery(「SELECT * FROM T_Employee」);
for(i=0;i<result.count;i++)
{
age = result[i].get(「FAge」);
number = result[i].get(「FNumber」);
age=age+1;
executeUpdate(「UPDATE T_Employee SET FAge=」+age+」 WHERE
FNumber=」+number);
}
這種方式在數據量比較大的時候速度是很是慢的,而在UPDATE中使用計算字段則能夠
很是簡單快速的完成任務,編寫下面的SQL語句:
UPDATE T_Employee SET FAge=FAge+1
這裏在SET子句中採用計算字段的方式爲FAge字段設定了新值。執行完畢後執行SELECT*
FROM T_Employee來查看修改後的數據:
FNumber FName FAge FSalary FSubCompany FDepartment
DEV001 Tom 26 8300.00 Beijing Development
DEV002 Jerry 29 2300.80 ShenZhen Development
HR001 Jane 24 2200.88 Beijing HumanResource
HR002 Tina 26 5200.36 Beijing HumanResource
IT001 Smith 29 3900.00 Beijing InfoTech
IT002 <NULL> 28 2800.00 ShenZhen InfoTech
SALES001 John 24 5000.00 Beijing Sales
SALES002 Kerry 29 6200.00 Beijing Sales
SALES003 Stone 23 1200.00 ShenZhen Sales
4.7 不從實體表中取的數據
有的時候咱們須要查詢一些不能從任何實體表中可以取得的數據,好比將數字1做爲結
果集或者計算字符串「abc」的長度。
有的開發人員嘗試使用下面的SQL來完成相似的功能:
SELECT 1 FROM T_Employee
但是執行之後卻獲得了下面的執行結果集
1
1
1
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1
1
1
1
1
1
結果集中出現了不止一個1,這時由於經過這種方式獲得的結果集數量是取決於
T_Employee表中的數據條目數的,必需要藉助於DISTINCT關鍵字來將結果集條數限定爲一條,
SQL語句以下:
SELECT DISTINCT 1 FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
這種實現方式很是麻煩,並且若是數據庫中一張表都沒有的時候這樣就不湊效了。主流
數據庫系統對這種需求提供了比較好的支持,這一節咱們就來看一下主流數據庫系統對此的
支持。
MYSQL和MSSQLServer容許使用不帶FROM子句的SELECT語句來查詢這些不屬於任何實
體表的數據,好比下面的SQL將1做爲結果集:
SELECT 1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
還能夠在不帶FROM子句的SELECT語句中使用函數,好比下面的SQL將字符串「abc」的
長度做爲結果集:
MYSQL:
SELECT LENGTH('abc')
MSSQLServer:
SELECT LEN('abc')
執行完畢咱們就能在輸出結果中看到下面的執行結果:
3
還能夠在SELECT語句中同時計算多個表達式,好比下面的SQL語句將一、二、三、’a’、’b’、’c’
做爲結果集:
SELECT 1,2,3,'a','b','c'
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 2 3 a b c
在Oracle中是不容許使用這種不帶FROM子句的SELECT語句,不過咱們能夠使用Oracle的
系統表來做爲FROM子句中的表名,系統表是Oracle內置的特殊表,最經常使用的系統表爲DUAL。
好比下面的SQL將1以及字符串'abc'的長度做爲結果集:
SELECT 1, LENGTH('abc') FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 LENGTH(ABC)
1 3
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
在DB2中也一樣不支持不帶FROM子句的SELECT語句,它也是採用和Oracle相似的系統表,
最經常使用的系統表爲SYSIBM.SYSDUMMY1。好比下面的SQL將1以及字符串'abc'的長度做爲結果
集:
SELECT 1, LENGTH('abc') FROM SYSIBM.SYSDUMMY1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 2
1 3
4.8 聯合結果集
有的時候咱們須要組合兩個徹底不一樣的查詢結果集,而這兩個查詢結果之間沒有必然的
聯繫,只是咱們須要將他們顯示在一個結果集中而已。在SQL中能夠使用UNION運算符來將
兩個或者多個查詢結果集聯合爲一個結果集中。
爲了更好的講解本節的內容,須要首先建立一張用來存儲臨時工信息的新表,在數據庫
系統下執行下面的SQL語句:
MYSQL:
CREATE TABLE T_TempEmployee (FIdCardNumber VARCHAR(20),FName VARCHAR(20),FAge
INT ,PRIMARY KEY (FIdCardNumber))
MSSQLServer:
CREATE TABLE T_TempEmployee (FIdCardNumber VARCHAR(20),FName VARCHAR(20),FAge INT,
PRIMARY KEY (FIdCardNumber))
Oracle:
CREATE TABLE T_TempEmployee (FIdCardNumber VARCHAR2(20),FName VARCHAR2(20),FAge
NUMBER (10), PRIMARY KEY (FIdCardNumber))
DB2:
CREATE TABLE T_TempEmployee (FIdCardNumber VARCHAR(20) Not NULL,FName
VARCHAR(20),FAge INT, PRIMARY KEY (FIdCardNumber))
因爲臨時工沒有分配工號,因此使用身份證號碼FIdCardNumber來標識一個臨時工,同
時因爲臨時工不是實行月薪制,因此這裏也沒有記錄月薪信息。咱們還須要一些初始數據,
執行下面的SQL語句以插入初始數據:
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890121','Sarani',33);
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890122','Tom',26);
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890123','Yalaha',38);
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890124','Tina',26);
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890125','Konkaya',29);
INSERT INTO T_TempEmployee(FIdCardNumber,FName,FAge)
VALUES('1234567890126','Fo fa' ,46) ;
插入初始數據完畢之後執行SELECT * FROM T_TempEmployee查看錶中的數據:
FIdCardNumber FName FAge
1234567890121 Sarani 33
1234567890122 Tom 26
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1234567890123 Yalaha 38
1234567890124 Tina 26
1234567890125 Konkaya 29
1234567890126 Fo fa 4 6
4.8.1 簡單的結果集聯合
UNION運算符要放置在兩個查詢語句之間。好比咱們要查詢公司全部員工(包括臨時工)
的標識號碼、姓名、年齡信息。
查詢正式員工信息的SQL語句以下:
SELECT FNumber,FName,FAge FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge
DEV001 Tom 26
DEV002 Jerry 29
HR001 Jane 24
HR002 Tina 26
IT001 Smith 29
IT002 <NULL> 28
SALES001 John 24
SALES002 Kerry 29
SALES003 Stone 23
而查詢臨時工信息的SQL語句以下:
SELECT FIdCardNumber,FName,FAge FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FIdCardNumber FName FAge
1234567890121 Sarani 33
1234567890122 Tom 26
1234567890123 Yalaha 38
1234567890124 Tina 26
1234567890125 Konkaya 29
1234567890126 Fo fa 4 6
只要用UNION操做符鏈接這兩個查詢語句就能夠將兩個查詢結果集聯合爲一個結果集,
SQL語句以下:
SELECT FNumber,FName,FAge FROM T_Employee
UNION
SELECT FIdCardNumber,FName,FAge FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge
1234567890121 Sarani 33
1234567890122 Tom 26
1234567890123 Yalaha 38
1234567890124 Tina 26
1234567890125 Konkaya 29
1234567890126 Fo fa 4 6
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
DEV001 Tom 26
DEV002 Jerry 29
HR001 Jane 24
HR002 Tina 26
IT001 Smith 29
IT002 <NULL> 28
SALES001 John 24
SALES002 Kerry 29
SALES003 Stone 23
能夠看到UNION操做符將兩個獨立的結果集聯合成爲了一個結果集。
UNION能夠鏈接多個結果集,就像「+」能夠鏈接多個數字同樣簡單,只要在每一個結果
集之間加入UNION便可,好比下面的SQL語句就鏈接了三個結果集:
SELECT FNumber,FName,FAge FROM T_Employee
WHERE FAge<30
UNION
SELECT FIdCardNumber,FName,FAge FROM T_TempEmployee
WHERE FAge>40
UNION
SELECT FIdCardNumber,FName,FAge FROM T_TempEmployee
WHERE FAge<30
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge
1234567890122 Tom 26
1234567890124 Tina 26
1234567890125 Konkaya 29
1234567890126 Fo fa 4 6
DEV001 Tom 26
DEV002 Jerry 29
HR001 Jane 24
HR002 Tina 26
IT001 Smith 29
IT002 <NULL> 28
SALES001 John 24
SALES002 Kerry 29
SALES003 Stone 23
4.8.2 聯合結果集的原則
聯合結果集沒必要受被聯合的多個結果集之間的關係限制,不過使用UNION仍然有兩個基
本的原則須要遵照:一是每一個結果集必須有相同的列數;二是每一個結果集的列必須類型相容。
首先看第一個原則,每一個結果集必須有相同的列數,兩個不一樣列數的結果集是不能聯合
在一塊兒的。好比下面的SQL語句是錯誤的:
SELECT FNumber,FName,FAge,FDepartment FROM T_Employee
UNION
SELECT FIdCardNumber,FName,FAge FROM T_TempEmployee
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
執行之後數據庫系統會報出以下的錯誤信息:
使用UNION、INTERSECT 或EXCEPT 運算符合並的全部查詢必須在其目標列表中有相同數目的表達式。
由於第一個結果集返回了4列數據,而第二個結果集則返回了3列數據,數據庫系統並不會用空值將第
二個結果集補足爲4列。若是須要將未知列補足爲一個默認值,那麼能夠使用常量字段,好比下面的SQL語
句就將第二個結果集的與FDepartment對應的字段值設定爲「臨時工,不屬於任何一個部門」:
SELECT FNumber,FName,FAge,FDepartment FROM T_Employee
UNION
SELECT FIdCardNumber,FName,FAge,'臨時工,不屬於任何一個部門' FROM
T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FName FAge FDepartment
1234567890121 Sarani 33 臨時工,不屬於任何一個部門
1234567890122 Tom 26 臨時工,不屬於任何一個部門
1234567890123 Yalaha 38 臨時工,不屬於任何一個部門
1234567890124 Tina 26 臨時工,不屬於任何一個部門
1234567890125 Konkaya 29 臨時工,不屬於任何一個部門
1234567890126 Fo fa 4 6 臨時工,不屬於任何一個部門
DEV001 Tom 26 Development
DEV002 Jerry 29 Development
HR001 Jane 24 HumanResource
HR002 Tina 26 HumanResource
IT001 Smith 29 InfoTech
IT002 <NULL> 28 InfoTech
SALES001 John 24 Sales
SALES002 Kerry 29 Sales
聯合結果集的第二個原則是:每一個結果集的列必須類型相容,也就是說結果集的每一個對
應列的數據類型必須相同或者可以轉換爲同一種數據類型。好比下面的SQL語句在MYSQL中
能夠正確的執行:
SELECT FIdCardNumber,FAge,FName FROM T_TempEmployee
UNION
SELECT FNumber,FName,FAge FROM T_Employee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FIdCardNumber FAge FName
1234567890121 33 Sarani
1234567890122 26 Tom
1234567890123 38 Yalaha
1234567890124 26 Tina
1234567890125 29 Konkaya
1234567890126 46 Fo fa
DEV001 Tom 26
DEV002 Jerry 29
HR001 Jane 24
HR002 Tina 26
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
IT001 Smith 29
IT002 <NULL> 28
SALES001 John 24
SALES002 Kerry 29
SALES003 Stone 23
能夠看到MYSQL將FAge轉換爲了文本類型,以便於與FName字段值匹配。不過這句SQL語
句在MSSQLServer、Oracle、DB2中執行則會報出相似以下的錯誤信息:
表達式必須具備與對應表達式相同的數據類型。
由於這些數據庫系統不會像MYSQL那樣進行默認的數據類型轉換。因爲不一樣數據庫系統中數據類型轉
換規則是各不相同的,所以若是開發的應用程序要考慮跨多數據庫移植的話最好保證結果集的每一個對應
列的數據類型徹底相同。
4.8.3 UNION ALL
咱們想列出公司中全部員工(包括臨時工)的姓名和年齡信息,那麼咱們能夠執行下面
的SQL語句:
SELECT FName,FAge FROM T_Employee
UNION
SELECT FName,FAge FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FAge
<NULL> 28
Fo fa 4 6
Jane 24
Jerry 29
John 24
Kerry 29
Konkaya 29
Sarani 33
Smith 29
Stone 23
Tina 26
Tom 26
Yalaha 38
仔細觀察結果集,咱們發現輸出的結果和預想的是不一致的,在正式員工中有姓名爲
Tom、年齡爲26以及姓名爲Tina、年齡爲26的兩名員工,而臨時工中也有姓名爲Tom、年齡
爲26以及姓名爲Tina、年齡爲26的兩名員工,也就是說正式員工的臨時工中存在重名和年齡
重複的現象,可是在查詢結果中卻將重複的信息只保留了一條,也就是隻有一個姓名爲Tom、
年齡爲26的員工和一個姓名爲Tina、年齡爲26的員工。
這時由於默認狀況下,UNION運算符合並了兩個查詢結果集,其中徹底重複的數據行被
合併爲了一條。若是須要在聯合結果集中返回全部的記錄而無論它們是否惟一,則須要在
UNION運算符後使用ALL操做符,好比下面的SQL語句:
SELECT FName,FAge FROM T_Employee
UNION ALL
SELECT FName,FAge FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FName FAge
Tom 26
Jerry 29
Jane 24
Tina 26
Smith 29
<NULL> 28
John 24
Kerry 29
Stone 23
Sarani 33
Tom 26
Yalaha 38
Tina 26
Konkaya 29
Fo fa 4 6
4.8.4 聯合結果集應用舉例
聯合結果集在製做報表的時候常常被用到,使用聯合結果集咱們能夠將沒有直接關係的
數據顯示到同一張報表中。使用UNION運算符,只要被聯合的結果集符合聯合結果集的原則,
那麼被鏈接的兩個SQL語句能夠是很是複雜,也能夠是很是簡單的。本小節將展現幾個實用
的例子來看一下聯合結果集在實際開發中的應用。
4.8.4.1 員工年齡報表
要求查詢員工的最低年齡和最高年齡,臨時工和正式員工要分別查詢。
實現SQL語句以下:
SELECT '正式員工最高年齡',MAX(FAge) FROM T_Employee
UNION
SELECT '正式員工最低年齡',MIN(FAge) FROM T_Employee
UNION
SELECT '臨時工最高年齡',MAX(FAge) FROM T_TempEmployee
UNION
SELECT '臨時工最低年齡',MIN(FAge) FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
臨時工最低年齡 26
臨時工最高年齡 46
正式員工最低年齡 23
正式員工最高年齡 29
4.8.4.2 正式員工工資報表
要求查詢每位正式員工的信息,包括工號、工資,而且在最後一行加上全部員工工資額
合計。
實現SQL語句以下:
SELECT FNumber,FSalary FROM T_Employee
UNION
SELECT '工資合計',SUM(FSalary) FROM T_Employee
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNumber FSalary
DEV001 8300.00
DEV002 2300.80
HR001 2200.88
HR002 5200.36
IT001 3900.00
IT002 2800.00
SALES001 5000.00
SALES002 6200.00
SALES003 1200.00
工資合計 37102.04
4.8.4.3 打印5之內天然數的平方
要求打印出打印5之內天然數以及它們的平方數。
實現SQL語句以下:
MYSQL、MSSQLServer:
SELECT 1,1 * 1
UNION
SELECT 2,2 * 2
UNION
SELECT 3,3 * 3
UNION
SELECT 4,4 * 4
UNION
SELECT 5,5 * 5
Oracle:
SELECT 1,1 * 1 FROM DUAL
UNION
SELECT 2,2 * 2 FROM DUAL
UNION
SELECT 3,3 * 3 FROM DUAL
UNION
SELECT 4,4 * 4 FROM DUAL
UNION
SELECT 5,5 * 5 FROM DUAL
DB2:
SELECT 1,1 * 1 FROM SYSIBM.SYSDUMMY1
UNION
SELECT 2,2 * 2 FROM SYSIBM.SYSDUMMY1
UNION
SELECT 3,3 * 3 FROM SYSIBM.SYSDUMMY1
UNION
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SELECT 4,4 * 4 FROM SYSIBM.SYSDUMMY1
UNION
SELECT 5,5 * 5 FROM SYSIBM.SYSDUMMY1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 1
2 4
3 9
4 16
5 25
4.8.4.4 列出員工姓名
要求列出公司中全部員工(包括臨時工)的姓名,將重複的姓名過濾掉。
實現SQL語句以下:
SELECT FName FROM T_Employee
UNION
SELECT FName FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName
<NULL>
Fo fa
Jane
Jerry
John
Kerry
Konkaya
Sarani
Smith
Stone
Tina
Tom
Yalaha
4.8.4.5 分別列出正式員工和臨時工的姓名
要求分別列出正式員工和臨時工的姓名,要保留重複的姓名。
實現SQL語句以下:
MYSQL、MSSQLServer:
SELECT '如下是正式員工的姓名'
UNION ALL
SELECT FName FROM T_Employee
UNION ALL
SELECT '如下是臨時工的姓名'
UNION ALL
SELECT FName FROM T_TempEmployee
Oracle:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SELECT '如下是正式員工的姓名' FROM DUAL
UNION ALL
SELECT FName FROM T_Employee
UNION ALL
SELECT '如下是臨時工的姓名' FROM DUAL
UNION ALL
SELECT FName FROM T_TempEmployee
DB2:
SELECT '如下是正式員工的姓名' FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT FName FROM T_Employee
UNION ALL
SELECT '如下是臨時工的姓名' FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT FName FROM T_TempEmployee
執行完畢咱們就能在輸出結果中看到下面的執行結果:
如下是正式員工的姓名
Tom
Jerry
Jane
Tina
Smith
<NULL>
John
Kerry
Stone
如下是臨時工的姓名
Sarani
Tom
Yalaha
Tina
Konkaya
本章即將結束,請執行下面的SQL語句將本章用到的數據表刪除:
Drop TABLE T_Employee;
Drop TABLE T_TempEmployee;
第五章函數
第四章中咱們講解了SQL中函數的用法。SQL中可供使用的函數是很是多的,這些函
數的功能包括轉換字符串大小寫、求一個數的對數、計算兩個日期之間的天數間隔等,數量
的掌握這些函數將可以幫助咱們更快的完成業務功能。本章將講解這些函數的使用,而且對
它們在不一樣數據庫系統中的差別性進行比較。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
爲了更容易的運行本章中的例子,必須首先建立所須要的數據表,所以下面列出本章中
要用到數據表的建立SQL語句:
MYSQL:
CREATE TABLE T_Person (FIdNumber VARCHAR(20),
FName VARCHAR(20),FBirthDay DATETIME,
FRegDay DATETIME,FWeight DECIMAL(10,2))
MSSQLServer:
CREATE TABLE T_Person (FIdNumber VARCHAR(20),
FName VARCHAR(20),FBirthDay DATETIME,
FRegDay DATETIME,FWeight NUMERIC(10,2))
Oracle:
CREATE TABLE T_Person (FIdNumber VARCHAR2(20),
FName VARCHAR2(20),FBirthDay DATE,
FRegDay DATE,FWeight NUMERIC(10,2))
DB2:
CREATE TABLE T_Person (FIdNumber VARCHAR(20),
FName VARCHAR(20),FBirthDay DATE,
FRegDay DATE,FWeight DECIMAL(10,2))
請在不一樣的數據庫系統中運行相應的SQL 語句。T_Person 爲記錄人員信息的數據表,
其中字段FIdNumber 爲人員的身份證號碼,FName 爲人員姓名,FBirthDay 爲出生日期,
FRegDay 爲註冊日期,FWeight爲體重。
爲了更加直觀的驗證本章中函數使用方法的正確性,咱們須要在T_Person 表中預置一
些初始數據,請在數據庫中執行下面的數據插入SQL語句:
MYSQL、MSSQLServer、DB2:
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789120','Tom','1981-03-22','1998-05-01',56.67);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789121','Jim','1987-01-18','1999-08-21',36.17);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789122','Lily','1987-11-08','2001-09-18',40.33);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789123','Kelly','1982-07-12','2000-03-01',46.23);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789124','Sam','1983-02-16','1998-05-01',48.68);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789125','Kerry','1984-08-07','1999-03-01',66.67);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789126','Smith','1980-01-09','2002-09-23',51.28);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789127','BillGates','1972-07-18','1995-06-19',60.32);
Oracle:
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789120','Tom',TO_DATE('1981-03-22', 'YYYY-MM-DD HH24:MI:SS'),
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
TO_DATE('1998-05-01', 'YYYY-MM-DD HH24:MI:SS'),56.67);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789121','Jim',TO_DATE('1987-01-18', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('1999-08-21', 'YYYY-MM-DD HH24:MI:SS'),36.17);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789122','Lily',TO_DATE('1987-11-08', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2001-09-18', 'YYYY-MM-DD HH24:MI:SS'),40.33);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789123','Kelly',TO_DATE('1982-07-12', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2000-03-01', 'YYYY-MM-DD HH24:MI:SS'),46.23);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789124','Sam',TO_DATE('1983-02-16', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('1998-05-01', 'YYYY-MM-DD HH24:MI:SS'),48.68);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789125','Kerry',TO_DATE('1984-08-07', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('1999-03-01', 'YYYY-MM-DD HH24:MI:SS'),66.67);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789126','Smith',TO_DATE('1980-01-09', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2002-09-23', 'YYYY-MM-DD HH24:MI:SS'),51.28);
INSERT INTO T_Person(FIdNumber,FName,FBirthDay,FRegDay,FWeight)
VALUES ('123456789127','BillGates',TO_DATE('1972-07-18', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('1995-06-19', 'YYYY-MM-DD HH24:MI:SS'),60.32);
初始數據預置完畢之後執行SELECT * FROM T_Person來查看錶中的數據,內容以下:
FIdNumber FName FBirthDay FRegDay FWeight
123456789120 Tom 1981-03-22
00:00:00.0
1998-05-01
00:00:00.0
56.67
123456789121 Jim 1987-01-18
00:00:00.0
1999-08-21
00:00:00.0
36.17
123456789122 Lily 1987-11-08
00:00:00.0
2001-09-18
00:00:00.0
40.33
123456789123 Kelly 1982-07-12
00:00:00.0
2000-03-01
00:00:00.0
46.23
123456789124 Sam 1983-02-16
00:00:00.0
1998-05-01
00:00:00.0
48.68
123456789125 Kerry 1984-08-07
00:00:00.0
1999-03-01
00:00:00.0
66.67
123456789126 Smith 1980-01-09
00:00:00.0
2002-09-23
00:00:00.0
51.28
123456789127 BillGates 1972-07-18
00:00:00.0
1995-06-19
00:00:00.0
60.32
5.1 數學函數
本節內容試讀版不提供。請購買《程序員的SQL金典》。
5.2 字符串函數
本節內容試讀版不提供。請購買《程序員的SQL金典》。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
5.3 日期時間函數
日期時間類型的數據也是常常用到的,好比員工出生日期、結帳日期、入庫日期等等,
並且常常須要對這些數據進行處理,好比檢索全部超過保質期的商品、將結帳日期向後延遲
3 天、檢索全部每月18 日的入庫記錄,進行這些處理就須要使用日期時間函數。SQL中
提供了豐富的日期時間函數用於完成這些功能,本節將對這些日期時間函數進行詳細講解。
5.3.1 日期、時間、日期時間與時間戳
根據表示的類型、精度的不一樣,數據庫中的日期時間數據類型分爲日期、時間、日期時
間以及時間戳四種類型。
日期類型是用來表示「年-月-日」信息的數據類型,其精度精確到「日」,其中包含了
年、月、日三個信息,好比「2008-08-08」。日期類型能夠用來表示「北京奧運會開幕式日
期」、「王小明的出生年月日」等信息,可是沒法表示「最近一次遲到的時間」、「徐總抵京時
間」等精確到小時甚至分秒的數據。在數據庫中,通常用Date來表示日期類型。
時間類型是用來表示「小時:分:秒」 信息的數據類型,其精度精確到「秒」,其中包含
了小時、分、秒三個信息,好比「19:00:00」。時間類型能夠用來表示「天天《新聞聯播》
的播出時間」、「天天的下班時間」等信息,可是沒法表示「盧溝橋事變爆發日期」、「上次結
帳時間」等包含「年-月-日」等信息的數據。在數據庫中,通常用Time來表示時間類型。
日期時間類型是用來表示「年-月-日小時:分:秒」信息的數據類型,其精度精確到「秒」,
其中包含了年、月、日、小時、分、秒六個信息,好比「2008-08-08 08:00:00」。日期時間
類型能夠用來表示「北京奧運會開幕式準確時間」、「上次遲到時間」等信息。在數據庫中,
通常用DateTime 來表示日期時間類型。
日期時間類型的精度精確到「秒」,這在一些狀況下可以知足基本的要求,可是對於精
度要求更加高的日期時間信息則沒法表示,好比「劉翔跑到終點的時間」、「貨物A 通過射
頻識別器的時間」等更高精度要求的信息。數據庫中提供了時間戳類型用於表示這些對精度
要求更加高的場合。時間戳類型還能夠用於標記表中數據的版本信息,好比咱們想區分表中
兩條記錄插入表中的前後順序,因爲數據庫操做速度很是快,若是用DateTime 類型記錄輸
入插入時間的話,若兩條記錄插入的時間間隔很是短的話是沒法區分它們的,這時就能夠使
用時間戳類型。在有的數據庫系統中,若是對數據表中的記錄進行了更新的話,數據庫系統
會自動更新其中的時間戳字段的值。數據庫中,通常用TimeStamp 來表示日期時間類型。
不一樣的數據庫系統對日期、時間、日期時間與時間戳等數據類型的支持差別性很是大,
有的數據類型在有的數據庫系統中不被支持,而有的數據類型的表示精度則和其類型名稱所
暗示的精度不一樣,好比MSSQLServer 中不支持Time 類型、Oracle 中的Date 類型中包含時
間信息。數據庫中的日期時間函數對這些類型的支持差異是很是小的,所以在通常狀況下我
們將這些類型統一稱爲「日期時間類型」。
5.3.2 主流數據庫系統中日期時間類型的表示方式
在 MYSQL、MSSQLServer 和DB2 中能夠用字符串來表示日期時間類型,數據庫系統
會自動在內部將它們轉換爲日期時間類型,好比「'2008-08-08'」、「2008-08-08 08:00:00」、
「08:00:00」 、「2008-08-08 08:00:00.000000」等。
在 Oracle 中以字符串表示的數據是不能自動轉換爲日期時間類型的,必須使用
TO_DATE()函數來手動將字符串轉換爲日期時間類型的,好比TO_DATE('2008-08-08',
'YYYY-MM-DD HH24:MI:SS') 、TO_DATE('2008-08-08 08:00:00', 'YYYY-MM-DD
HH24:MI:SS')、TO_DATE('08:00:00', 'YYYY-MM-DD HH24:MI:SS')等。
5.3.3 取得當前日期時間
在系統中常常須要使用當前日期時間進行處理,好比將「入庫時間」字段設定爲當前日
期時間,在SQL 中提供了取得當前日期時間的方式,不過各個數據庫中的實現方式各不相
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
同。
5.3.3.1 MYSQL
MYSQL中提供了NOW()函數用於取得當前的日期時間,NOW()函數還有SYSDATE()、
CURRENT_TIMESTAMP等別名。以下:
SELECT NOW(),SYSDATE(),CURRENT_TIMESTAMP
執行完畢咱們就能在輸出結果中看到下面的執行結果:
NOW() SYSDATE() CURRENT_TIMESTAMP
2008-01-12 01:13:19 2008-01-12 01:13:19 2008-01-12 01:13:19
若是想獲得不包括時間部分的當前日期,則能夠使用CURDATE()函數,CURDATE()
函數還有CURRENT_DATE等別名。以下:
SELECT CURDATE(),CURRENT_DATE
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CURDATE() CURRENT_DATE
2008-01-12 2008-01-12
若是想獲得不包括日期部分的當前時間,則能夠使用CURTIME()函數,CURTIME ()
函數還有CURRENT_TIME等別名。以下:
SELECT CURTIME(),CURRENT_TIME
執行完畢咱們就能在輸出結果中看到下面的執行結果:
CURTIME() CURRENT_TIME
01:17:09 01:17:09
5.3.3.2 MSQLServer
MSSQLServer 中用於取得當前日期時間的函數爲GETDATE()。以下:
SELECT GETDATE() as 當前日期時間
執行完畢咱們就能在輸出結果中看到下面的執行結果:
當前日期時間
2008-01-12 01:02:04.78
能夠看到GETDATE()返回的信息是包括了日期、時間(精確到秒之後部分)的時間戳
信息。MSSQLServer 沒有專門提供取得當前日期、取得當前時間的函數,不過咱們能夠將
GETDATE()的返回值進行處理,這裏須要藉助於Convert()函數,這個函數的詳細介紹後面
章節介紹,這裏只介紹它在日期處理方面的應用。
使用 CONVERT(VARCHAR(50) ,日期時間值, 101)能夠獲得日期時間值的日期部分,因
此下面的SQL 語句能夠獲得當前的日期值:
SELECT CONVERT(VARCHAR(50) ,GETDATE( ), 101) as 當前日期
執行完畢咱們就能在輸出結果中看到下面的執行結果:
當前日期
01/14/2008
使用CONVERT(VARCHAR(50) ,日期時間值, 108)能夠獲得日期時間值的日期部分,因
此下面的SQL 語句能夠獲得當前的日期值:
SELECT CONVERT(VARCHAR(50) ,GETDATE(), 108) as 當前時間
執行完畢咱們就能在輸出結果中看到下面的執行結果:
當前時間
21:37:19
5.3.3.3 Oracle
Oracle 中沒有提供取得當前日期時間的函數,不過咱們能夠到系統表DUAL 中查詢
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SYSTIMESTAMP的值來獲得當前的時間戳。以下:
SELECT SYSTIMESTAMP
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
SYSTIMESTAMP
2008-1-14 21.46.42.78000000 8:0
一樣,咱們能夠到系統表DUAL中查詢SYSDATE 的值來獲得當前日期時間。以下:
SELECT SYSDATE
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
SYSDATE
2008-01-14 21:47:16.0
一樣,Oracle 中也沒有專門提供取得當前日期、取得當前時間的函數,不過咱們能夠將
SYSDATE 的值進行處理,這裏須要藉助於TO_CHAR()函數,這個函數的詳細介紹後面章節
介紹,這裏只介紹它在日期處理方面的應用。
使用 TO_CHAR(時間日期值, 'YYYY-MM-DD') 能夠獲得日期時間值的日期部分,所以下
面的SQL 語句能夠獲得當前的日期值:
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD')
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
TO_CHAR(SYSDATE,YYYY-MM-DD)
2008-01-14
使用TO_CHAR(時間日期值, 'HH24:MI:SS') 能夠獲得日期時間值的時間部分,所以下
面的SQL 語句能夠獲得當前的日期值:
SELECT TO_CHAR(SYSDATE, 'HH24:MI:SS')
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
TO_CHAR(SYSDATE,HH24:MI:SS)
21:56:13
5.3.3.4 DB2
DB2 中一樣沒有提供取得當前日期時間的函數,不過咱們能夠到系統表
SYSIBM.SYSDUMMY1中查詢CURRENT TIMESTAMP的值來獲得當前時間戳。以下:
SELECT CURRENT TIMESTAMP
FROM SYSIBM.SYSDUMMY1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
2008-01-14-21.58.20.01515000
從系統表SYSIBM.SYSDUMMY1 中查詢CURRENT DATE的值來獲得當前日期值。如
下:
SELECT CURRENT DATE
FROM SYSIBM.SYSDUMMY1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
2008-01-14
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
從系統表SYSIBM.SYSDUMMY1 中查詢CURRENT TIME的值來獲得當前日期值。如
下:
SELECT CURRENT TIME
FROM SYSIBM.SYSDUMMY1
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1
22:05:48
5.3.4 日期增減
有時咱們須要在一個日期的基礎上增長某個時間長度或者減去某個時間長度,好比咱們
知道每一個員工的出生日期,而想計算出他出生後10000 天的日期,再如咱們想計算全部合同
的到期日的三月後的日期。因爲存在每月天數不一樣、閏月等複雜的歷法規則,因此不能使
用簡單的數字加減法進行計算,主流的數據庫系統中都提供了對日期增減的計算,下面分別
進行介紹。
5.3.4.1 MYSQL
MYSQL中提供了DATE_ADD()函數用於進行日期時間的加法運算,這個函數還有一個別名
爲ADDDATE(),DATE_ADD()函數的參數格式以下:
DATE_ADD (date,INTERVAL expr type)
其中參數date爲待計算的日期;參數expr爲待進行加法運算的增量,它能夠是數值類型
或者字符串類型,取決於type參數的取值;參數type則爲進行加法運算的單位,type參數可
選值以及對應的expr參數的格式以下表:
type參數值 expr參數的格式 說明
MICROSECOND 數值類型 以微秒爲計算單位
SECOND 數值類型以秒爲計算單位
MINUTE 數值類型以分鐘爲計算單位
HOUR 數值類型以小時爲計算單位
DAY 數值類型以天爲計算單位
WEEK 數值類型以周爲計算單位
MONTH 數值類型以月爲計算單位
QUARTER 數值類型以季度爲計算單位
YEAR 數值類型以年爲計算單位
SECOND_MICROSECOND
字符串類型,格式爲:
'SECONDS.MICROSECON
DS'
以秒、微秒爲計算單位,要求expr參
數必須是「秒.微秒」的格式,好比
「30.10」表示增長30秒10微秒。
MINUTE_MICROSECOND
字符串類型,格式爲:
'MINUTES.MICROSECON
DS'
以分鐘、毫秒爲計算單位,要求expr
參數必須是「分鐘.微秒」的格式,
好比「30.10」表示增長30分鐘10微
秒。
MINUTE_SECOND
字符串類型,格式爲:
'MINUTES:SECONDS'
以分鐘、秒爲計算單位,要求expr參
數必須是「分鐘:秒」的格式,好比
「30:10」表示增長30分鐘10秒。
HOUR_MICROSECOND
字符串類型,格式爲:
'HOURS.MICROSECONDS
'
以小時、微秒爲計算單位,要求expr
參數必須是「小時.微秒」的格式,
好比「30.10」表示增長30小時10微
秒。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
HOUR_SECOND
字符串類型,格式爲:
'HOURS:MINUTES:SECO
NDS'
以小時、分鐘、秒爲計算單位,要求
expr參數必須是「小時:分鐘:秒」的
格式,好比「1:30:10」表示增長1小
時30分鐘10秒。
HOUR_MINUTE
字符串類型,格式爲:
'HOURS:MINUTES'
以小時、秒爲計算單位,要求expr參
數必須是「小時:秒」的格式,好比
「30:10」表示增長30小時10秒。
DAY_MICROSECOND
字符串類型,格式爲:
'DAYS.MICROSECONDS'
以天、微秒爲計算單位,要求expr參
數必須是「天.微秒」的格式,好比
「30.10」表示增長30天10微秒。
DAY_SECOND
字符串類型,格式爲:
'DAYS
HOURS:MINUTES:SECON
DS'
以天、小時、分鐘、秒爲計算單位,
要求expr參數必須是「天 小時:分鐘:
秒」的格式,好比「1 3:28:36」表示
增長1天3小時28分鐘36秒。
DAY_MINUTE
字符串類型,格式爲:
'DAYS
HOURS:MINUTES'
以天、小時、分鐘爲計算單位,要求
expr參數必須是「天 小時:分鐘」的
格式,好比「1 3:15」表示增長1天3
小時15分鐘。
DAY_HOUR
字符串類型,格式爲:
'DAYS HOURS'
以天、小時爲計算單位,要求expr參
數必須是「天 小時」的格式,好比
「30 10」表示增長30天10小時。
YEAR_MONTH
字符串類型,格式爲:
'YEARS-MONTHS'
以年、月爲計算單位,要求expr參數
必須是「年-月」的格式,好比「2-8」
表示增長2年8個月。
表中前九種用法都很是簡單,好比DATE_ADD(date,INTERVAL 1 HOUR)就能夠獲得在日期
date基礎上增長一小時後的日期時間,而DATE_ADD(date,INTERVAL 1 WEEK)就能夠獲得在日
期date基礎上增長一週後的日期時間。下面的SQL語句用來計算每一個人出生一週、兩個月以
及5個季度後的日期:
SELECT FBirthDay,
DATE_ADD(FBirthDay,INTERVAL 1 WEEK) as w1,
DATE_ADD(FBirthDay,INTERVAL 2 MONTH) as m2,
DATE_ADD(FBirthDay,INTERVAL 5 QUARTER) as q5
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay w1 m2 q5
1981-03-22 00:00:00 1981-03-29 00:00:00 1981-05-22 00:00:00 1982-06-22 00:00:00
1987-01-18 00:00:00 1987-01-25 00:00:00 1987-03-18 00:00:00 1988-04-18 00:00:00
1987-11-08 00:00:00 1987-11-15 00:00:00 1988-01-08 00:00:00 1989-02-08 00:00:00
1982-07-12 00:00:00 1982-07-19 00:00:00 1982-09-12 00:00:00 1983-10-12 00:00:00
1983-02-16 00:00:00 1983-02-23 00:00:00 1983-04-16 00:00:00 1984-05-16 00:00:00
1984-08-07 00:00:00 1984-08-14 00:00:00 1984-10-07 00:00:00 1985-11-07 00:00:00
1980-01-09 00:00:00 1980-01-16 00:00:00 1980-03-09 00:00:00 1981-04-09 00:00:00
1972-07-18 00:00:00 1972-07-25 00:00:00 1972-09-18 00:00:00 1973-10-18 00:00:00
相對於前九種用法來講,後面幾種用法就相對複雜一些,須要根據格式化的類型來決定
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
expr參數的值。好比若是想爲日期增長3天2小時10分鐘,那麼就能夠以下使用DATE_ADD()
函數:
DATE_ADD(date,INTERVAL '3 2:10' DAY_MINUTE)
好比下面的SQL語句分別計算出生日期後3天2小時10分鐘、1年6個月的日期時間:
SELECT FBirthDay,
DATE_ADD(FBirthDay,INTERVAL '3 2:10' DAY_MINUTE) as dm,
DATE_ADD(FBirthDay,INTERVAL '1-6' YEAR_MONTH) as ym
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay dm ym
1981-03-22 00:00:00 1981-03-25 02:10:00 1982-09-22 00:00:00
1987-01-18 00:00:00 1987-01-21 02:10:00 1988-07-18 00:00:00
1987-11-08 00:00:00 1987-11-11 02:10:00 1989-05-08 00:00:00
1982-07-12 00:00:00 1982-07-15 02:10:00 1984-01-12 00:00:00
1983-02-16 00:00:00 1983-02-19 02:10:00 1984-08-16 00:00:00
1984-08-07 00:00:00 1984-08-10 02:10:00 1986-02-07 00:00:00
1980-01-09 00:00:00 1980-01-12 02:10:00 1981-07-09 00:00:00
1972-07-18 00:00:00 1972-07-21 02:10:00 1974-01-18 00:00:00
幾乎全部版本的MYSQL都支持DATE_ADD()函數的前九種用法,可是MYSQL的早期版
本不徹底支持DATE_ADD()函數的後幾種用法,不過在MYSQL的早期版本中能夠嵌套調用
DATE_ADD()函數來實現後幾種用法的效果。下面的SQL語句使用嵌套函數的方式來分別計算出
生日期後1年6個月的日期時間:
SELECT FBirthDay,
DATE_ADD(DATE_ADD(FBirthDay,INTERVAL 1 YEAR),INTERVAL 6 MONTH) as dm
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay dm
1981-03-22 00:00:00 1982-09-22 00:00:00
1987-01-18 00:00:00 1988-07-18 00:00:00
1987-11-08 00:00:00 1989-05-08 00:00:00
1982-07-12 00:00:00 1984-01-12 00:00:00
1983-02-16 00:00:00 1984-08-16 00:00:00
1984-08-07 00:00:00 1986-02-07 00:00:00
1980-01-09 00:00:00 1981-07-09 00:00:00
1972-07-18 00:00:00 1974-01-18 00:00:00
DATE_ADD()函數不只能夠用來在日期基礎上增長指定的時間段,並且還能夠在日期基礎
上減小指定的時間段,只要在expr參數中使用負數就能夠,下面的SQL語句用來計算每一個人出
生一週、兩個月以及5個季度前的日期:
SELECT FBirthDay,
DATE_ADD(FBirthDay,INTERVAL -1 WEEK) as w1,
DATE_ADD(FBirthDay,INTERVAL -2 MONTH) as m2,
DATE_ADD(FBirthDay,INTERVAL -5 QUARTER) as q5
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FBirthDay w1 m2 q5
1981-03-22 00:00:00 1981-03-15 00:00:00 1981-01-22 00:00:00 1979-12-22 00:00:00
1987-01-18 00:00:00 1987-01-11 00:00:00 1986-11-18 00:00:00 1985-10-18 00:00:00
1987-11-08 00:00:00 1987-11-01 00:00:00 1987-09-08 00:00:00 1986-08-08 00:00:00
1982-07-12 00:00:00 1982-07-05 00:00:00 1982-05-12 00:00:00 1981-04-12 00:00:00
1983-02-16 00:00:00 1983-02-09 00:00:00 1982-12-16 00:00:00 1981-11-16 00:00:00
1984-08-07 00:00:00 1984-07-31 00:00:00 1984-06-07 00:00:00 1983-05-07 00:00:00
1980-01-09 00:00:00 1980-01-02 00:00:00 1979-11-09 00:00:00 1978-10-09 00:00:00
1972-07-18 00:00:00 1972-07-11 00:00:00 1972-05-18 00:00:00 1971-04-18 00:00:00
在MYSQL中提供了DATE_SUB()函數用於計算指定日期前的特定時間段的日期,
其效果和在DATE_ADD()函數中使用負數的expr參數值的效果同樣,其用法也和DATE_ADD()
函數幾乎相同。下面的SQL語句用來計算每一個人出生一週、兩個月以及3天2小時10分鐘前的
日期:
SELECT FBirthDay,
DATE_SUB(FBirthDay,INTERVAL 1 WEEK) as w1,
DATE_SUB(FBirthDay,INTERVAL 2 MONTH) as m2,
DATE_SUB(FBirthDay, INTERVAL '3 2:10' DAY_MINUTE) as dm
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay w1 m2 dm
1981-03-22 00:00:00 1981-03-15 00:00:00 1981-01-22 00:00:00 1981-03-18 21:50:00
1987-01-18 00:00:00 1987-01-11 00:00:00 1986-11-18 00:00:00 1987-01-14 21:50:00
1987-11-08 00:00:00 1987-11-01 00:00:00 1987-09-08 00:00:00 1987-11-04 21:50:00
1982-07-12 00:00:00 1982-07-05 00:00:00 1982-05-12 00:00:00 1982-07-08 21:50:00
1983-02-16 00:00:00 1983-02-09 00:00:00 1982-12-16 00:00:00 1983-02-12 21:50:00
1984-08-07 00:00:00 1984-07-31 00:00:00 1984-06-07 00:00:00 1984-08-03 21:50:00
1980-01-09 00:00:00 1980-01-02 00:00:00 1979-11-09 00:00:00 1980-01-05 21:50:00
1972-07-18 00:00:00 1972-07-11 00:00:00 1972-05-18 00:00:00 1972-07-14 21:50:00
5.3.4.2 MSSQLServer
MSSQLServer中提供了DATEADD()函數用於進行日期時間的加法運算, DATEADD ()函數
的參數格式以下:
DATEADD (datepart , number, date )
其中參數date爲待計算的日期;參數date制定了用於與 datepart 相加的值,若是指定
了非整數值,則將捨棄該值的小數部分;參數datepart指定要返回新值的日期的組成部分,
下表列出了 Microso SQL Ser ver 2005 可識別的日期部分及其縮寫:
取值 別名 說明
year yy,yyyy 年份
quarter qq,q 季度
month mm,m 月份
dayofyear dy,y 當年度的第幾天
day dd,d 日
week wk,ww 當年度的第幾周
weekday dw,w 星期幾
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
hour hh 小時
minute mi,n 分
second ss,s 秒
millisecond ms 毫秒
比 如DATEADD(DAY, 3,date) 爲計算日期date 的3 天后的日期, 而
DATEADD(MONTH ,-8,date)爲計算日期date的8 個月以前的日期。
下面的SQL語句用於計算每一個人出生後3 年、20 個季度、68 個月以及1000 個周前的
日期:
SELECT FBirthDay, DATEADD (YEAR ,3,FBirthDay) as threeyrs,
DATEADD(QUARTER ,20,FBirthDay) as ttqutrs,
DATEADD(MONTH ,68,FBirthDay) as sxtmonths,
DATEADD(WEEK, -1000,FBirthDay) as thweeik
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay threeyrs qut rs s xtmo nt hs t hwe ei k
1981-03-22
00:00:00.0
1984-03-22
00:00:00.0
1986-03-22
00:00:00.0
1986-11-22
00:00:00.0
1962-01-21
00:00:00.0
1987-01-18
00:00:00.0
1990-01-18
00:00:00.0
1992-01-18
00:00:00.0
1992-09-18
00:00:00.0
1967-11-19
00:00:00.0
1987-11-08
00:00:00.0
1990-11-08
00:00:00.0
1992-11-08
00:00:00.0
1993-07-08
00:00:00.0
1968-09-08
00:00:00.0
1982-07-12
00:00:00.0
1985-07-12
00:00:00.0
1987-07-12
00:00:00.0
1988-03-12
00:00:00.0
1963-05-13
00:00:00.0
1983-02-16
00:00:00.0
1986-02-16
00:00:00.0
1988-02-16
00:00:00.0
1988-10-16
00:00:00.0
1963-12-18
00:00:00.0
1984-08-07
00:00:00.0
1987-08-07
00:00:00.0
1989-08-07
00:00:00.0
1990-04-07
00:00:00.0
1965-06-08
00:00:00.0
1980-01-09
00:00:00.0
1983-01-09
00:00:00.0
1985-01-09
00:00:00.0
1985-09-09
00:00:00.0
1960-11-09
00:00:00.0
1972-07-18
00:00:00.0
1975-07-18
00:00:00.0
1977-07-18
00:00:00.0
1978-03-18
00:00:00.0
1953-05-19
00:00:00.0
5.3.4.3 Oracle
Oracle中能夠直接使用加號「+」來進行日期的加法運算,其計算單位爲「天」,好比date+3
就表示在日期date的基礎上增長三天;同理使用減號「-」則能夠用來計算日期前的特定時
間段的時間,好比date+3就表示在日期date的三天前的日期。好比下面的SQL語句用於計算
每一個人出生日期3天后以及10天前的日期:
SELECT FBirthDay,
FBirthDay+3,
FBirthDay-10
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY FBIRTHDAY+3 FBIRTHDAY-10
1981-03-22 00:00:00.0 1981-03-25 00:00:00.0 1981-03-12 00:00:00.0
1987-01-18 00:00:00.0 1987-01-21 00:00:00.0 1987-01-08 00:00:00.0
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1987-11-08 00:00:00.0 1987-11-11 00:00:00.0 1987-10-29 00:00:00.0
1982-07-12 00:00:00.0 1982-07-15 00:00:00.0 1982-07-02 00:00:00.0
1983-02-16 00:00:00.0 1983-02-19 00:00:00.0 1983-02-06 00:00:00.0
1984-08-07 00:00:00.0 1984-08-10 00:00:00.0 1984-07-28 00:00:00.0
1980-01-09 00:00:00.0 1980-01-12 00:00:00.0 1979-12-30 00:00:00.0
1972-07-18 00:00:00.0 1972-07-21 00:00:00.0 1972-07-08 00:00:00.0
能夠使用換算的方式來進行以周、小時、分鐘等爲單位的日期加減運算,好比下面的SQL
語句用於計算每一個人出生日期2小時10分鐘後以及3周後的日期:
SELECT FBirthDay,
FBirthDay+(2/24+10/60/24),
FBirthDay+(3*7)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY FBIRTHDAY+(2/24+10/60/24) FBIRTHDAY+(3*7)
1981-03-22 00:00:00.0 1981-03-22 02:10:00.0 1981-04-12 00:00:00.0
1987-01-18 00:00:00.0 1987-01-18 02:10:00.0 1987-02-08 00:00:00.0
1987-11-08 00:00:00.0 1987-11-08 02:10:00.0 1987-11-29 00:00:00.0
1982-07-12 00:00:00.0 1982-07-12 02:10:00.0 1982-08-02 00:00:00.0
1983-02-16 00:00:00.0 1983-02-16 02:10:00.0 1983-03-09 00:00:00.0
1984-08-07 00:00:00.0 1984-08-07 02:10:00.0 1984-08-28 00:00:00.0
1980-01-09 00:00:00.0 1980-01-09 02:10:00.0 1980-01-30 00:00:00.0
1972-07-18 00:00:00.0 1972-07-18 02:10:00.0 1972-08-08 00:00:00.0
使用加減運算咱們能夠很容易的實現以周、天、小時、分鐘、秒等爲單位的日期的增減運
算,不過因爲每月的天數是不一樣的,也就是在天和月之間不存在固定的換算率,因此沒法使用
加減運算實現以月爲單位的計算,爲此Oracle中提供了ADD_MONTHS()函數用於以月爲單位的
日期增減運算,ADD_MONTHS()函數的參數格式以下:
ADD_MONTHS(date,number)
其中參數date爲待計算的日期,參數number爲要增長的月份數,若是number爲負數則表
示進行日期的減運算。下面的SQL語句用於計算每一個人的出生日期兩個月後以及10個月前的日期:
SELECT FBirthDay,
ADD_MONTHS(FBirthDay,2),
ADD_MONTHS(FBirthDay,-10)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY ADD_MONTHS(FBIRTHDAY,2) ADD_MONTHS(FBIRTHDAY,-10)
1981-03-22 00:00:00.0 1981-05-22 00:00:00.0 1980-05-22 00:00:00.0
1987-01-18 00:00:00.0 1987-03-18 00:00:00.0 1986-03-18 00:00:00.0
1987-11-08 00:00:00.0 1988-01-08 00:00:00.0 1987-01-08 00:00:00.0
1982-07-12 00:00:00.0 1982-09-12 00:00:00.0 1981-09-12 00:00:00.0
1983-02-16 00:00:00.0 1983-04-16 00:00:00.0 1982-04-16 00:00:00.0
1984-08-07 00:00:00.0 1984-10-07 00:00:00.0 1983-10-07 00:00:00.0
1980-01-09 00:00:00.0 1980-03-09 00:00:00.0 1979-03-09 00:00:00.0
1972-07-18 00:00:00.0 1972-09-18 00:00:00.0 1971-09-18 00:00:00.0
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
綜合使用ADD_MONTHS()函數和加、減號運算符則能夠實現更加複雜的日期增減運算,比
以下面的SQL語句用於計算每一個人的出生日期兩個月零10天后以及3個月零10個小時前的日期
時間:
SELECT FBirthDay,
ADD_MONTHS(FBirthDay,2)+10 as bfd,
ADD_MONTHS(FBirthDay,-3)-(10/24) as afd
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY BFD AFD
1981-03-22 00:00:00.0 1981-06-01 00:00:00.0 1980-12-21 14:00:00.0
1987-01-18 00:00:00.0 1987-03-28 00:00:00.0 1986-10-17 14:00:00.0
1987-11-08 00:00:00.0 1988-01-18 00:00:00.0 1987-08-07 14:00:00.0
1982-07-12 00:00:00.0 1982-09-22 00:00:00.0 1982-04-11 14:00:00.0
1983-02-16 00:00:00.0 1983-04-26 00:00:00.0 1982-11-15 14:00:00.0
1984-08-07 00:00:00.0 1984-10-17 00:00:00.0 1984-05-06 14:00:00.0
1980-01-09 00:00:00.0 1980-03-19 00:00:00.0 1979-10-08 14:00:00.0
1972-07-18 00:00:00.0 1972-09-28 00:00:00.0 1972-04-17 14:00:00.0
5.3.4.4 DB2
DB2 中能夠直接使用加減運算符進行日期的增減運算,只要在要增減的數目後加上單
位就能夠了。其使用格式以下:
date+length unit
其中date參數爲待計算的日期;
length爲進行增減運算的日期,當length爲正值的時候爲向時間軸正向計算,而當length
爲負值的時候爲向時間軸負向計算;
unit 爲進行計算的單位,此參數可取值以及響應函數以下:
計算單位說明
YEAR 年
MONTH 月
DAY 日
HOUR 小時
MINUTE 分
SECOND 秒
好比date+3 DAY 爲計算日期date的3 天后的日期,而date-8 MONTH爲計算日期date
的8 個月以前的日期,並且咱們還能夠連續使用加減運算符進行更加複雜的日期運算,好比
date+3 YEAR+10 DAY 用於計算date的3 個月零10天后的日期。
下面的SQL語句用於計算每一個人出生後3 年、20 個季度、68 個月以及1000 個周前的
日期:
SELECT FBirthDay, FBirthDay+3 YEAR + 10 DAY,
FBirthDay-100 MONTH
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY 2 3
1981-03-22 1984-04-01 1972-11-22
1987-01-18 1990-01-28 1978-09-18
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1987-11-08 1990-11-18 1979-07-08
1982-07-12 1985-07-22 1974-03-12
1983-02-16 1986-02-26 1974-10-16
1984-08-07 1987-08-17 1976-04-07
1980-01-09 1983-01-19 1971-09-09
1972-07-18 1975-07-28 1964-03-18
5.3.5 計算日期差額
有時候咱們須要計算兩個日期的差額,好比計算「回款日」和「驗收日」之間所差的天
數或者檢索全部「最後一次登陸日期」與當前日期的差額大於100天的用戶信息。主流的數
據庫系統中都提供了對計算日期差額的支持,下面分別進行介紹。
5.3.5.1 MYSQL
MYSQL中使用DATEDIFF()函數用於計算兩個日期之間的差額,其參數調用格式以下:
DATEDIFF(date1,date2)
函數將返回date1與date2之間的天數差額,若是date2在date1以後返回正值,不然返回
負值。
好比下面的SQL語句用於計算註冊日期和出生日期之間的天數差額:
SELECT FRegDay,FBirthDay, DATEDIFF(FRegDay, FBirthDay) , DATEDIFF(FBirthDay ,FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FRegDay FBirthDay DATEDIFF(FRegDay,
FBirthDay)
DATEDIFF(FBirthDay ,FRegDay)
1998-05-01
00:00:00
1981-03-22
00:00:00
6249 -6249
1999-08-21
00:00:00
1987-01-18
00:00:00
4598 -4598
2001-09-18
00:00:00
1987-11-08
00:00:00
5063 -5063
2000-03-01
00:00:00
1982-07-12
00:00:00
6442 -6442
1998-05-01
00:00:00
1983-02-16
00:00:00
5553 -5553
1999-03-01
00:00:00
1984-08-07
00:00:00
5319 -5319
2002-09-23
00:00:00
1980-01-09
00:00:00
8293 -8293
1995-06-19
00:00:00
1972-07-18
00:00:00
8371 -8371
DATEDIFF()函數只能計算兩個日期之間的天數差額,若是要計算兩個日期的周差額等就
須要進行換算,好比下面的SQL語句用於計算註冊日期和出生日期之間的週數差額:
SELECT FRegDay,FBirthDay, DATEDIFF(FRegDay, FBirthDay)/7
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FRegDay FBirthDay DATEDIFF(FRegDay,
FBirthDay)/7
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1998-05-01 00:00:00 1981-03-22 00:00:00 892.7143
1999-08-21 00:00:00 1987-01-18 00:00:00 656.8571
2001-09-18 00:00:00 1987-11-08 00:00:00 723.2857
2000-03-01 00:00:00 1982-07-12 00:00:00 920.2857
1998-05-01 00:00:00 1983-02-16 00:00:00 793.2857
1999-03-01 00:00:00 1984-08-07 00:00:00 759.8571
2002-09-23 00:00:00 1980-01-09 00:00:00 1184.7143
1995-06-19 00:00:00 1972-07-18 00:00:00 1195.8571
5.3.5.2 MSSQLServer
MSSQLServer中一樣提供了DATEDIFF()函數用於計算兩個日期之間的差額,與MYSQL中的
DATEDIFF()函數不一樣,它提供了一個額外的參數用於指定計算差額時使用的單位,其參數調
用格式以下:
DATEDIFF ( datepart , startdate , enddate )
其中參數datepart爲計算差額時使用的單位,可選值以下:
單位 別名 說明
year yy, yyyy 年
quarter qq, q 季度
month mm, m 月
dayofyear dy, y 工做日
day dd, d 天數
week wk, ww 周
Hour hh 小時
minute mi, n 分鐘
second ss, s 秒
millisecond ms 毫秒
參數startdate爲起始日期;參數enddate爲結束日期。
下面的SQL語句用於計算註冊日期和出生日期之間的週數差額:
SELECT FRegDay,FBirthDay,DATEDIFF(WEEK, FBirthDay, FRegDay) FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FRegDay FBirthDay
1998-05-01 00:00:00.0 1981-03-22 00:00:00.0 892
1999-08-21 00:00:00.0 1987-01-18 00:00:00.0 656
2001-09-18 00:00:00.0 1987-11-08 00:00:00.0 723
2000-03-01 00:00:00.0 1982-07-12 00:00:00.0 920
1998-05-01 00:00:00.0 1983-02-16 00:00:00.0 793
1999-03-01 00:00:00.0 1984-08-07 00:00:00.0 760
2002-09-23 00:00:00.0 1980-01-09 00:00:00.0 1185
1995-06-19 00:00:00.0 1972-07-18 00:00:00.0 1196
5.3.5.3 Oracle
在Oracle中,能夠在兩個日期類型的數據之間使用減號運算符「-」,其計算結果爲兩個
日期之間的天數差,好比執行下面的SQL語句用於計算註冊日期FRegDay和出生日期
FBirthDay之間的時間間隔:
SELECT FRegDay,FBirthDay,FRegDay-FBirthDay
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FREGDAY FBIRTHDAY FREGDAY-FBIRTHDAY
1998-05-01 00:00:00.0 1981-03-22 00:00:00.0 6249
1999-08-21 00:00:00.0 1987-01-18 00:00:00.0 4598
2001-09-18 00:00:00.0 1987-11-08 00:00:00.0 5063
2000-03-01 00:00:00.0 1982-07-12 00:00:00.0 6442
1998-05-01 00:00:00.0 1983-02-16 00:00:00.0 5553
1999-03-01 00:00:00.0 1984-08-07 00:00:00.0 5319
2002-09-23 00:00:00.0 1980-01-09 00:00:00.0 8293
1995-06-19 00:00:00.0 1972-07-18 00:00:00.0 8371
注意經過減號運算符「-」計算的兩個日期之間的天數差是包含有小數部分的,小數部
分表示不足一天的部分,好比執行下面的SQL語句用於計算當前時刻和出生日期FBirthDay
之間的時間間隔:
SELECT SYSDATE,FBirthDay,SYSDATE-FBirthDay
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
SYSDATE FBIRTHDAY SYSDATE-FBIRTHDAY
2008-01-16
23:11:52.0
1981-03-22
00:00:00.0
9796.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1987-01-18
00:00:00.0
7668.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1987-11-08
00:00:00.0
7374.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1982-07-12
00:00:00.0
9319.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1983-02-16
00:00:00.0
9100.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1984-08-07
00:00:00.0
8562.966574074074074074074074074074074074
2008-01-16
23:11:52.0
1980-01-09
00:00:00.0
10234.9665740740740740740740740740740741
2008-01-16
23:11:52.0
1972-07-18
00:00:00.0
12965.9665740740740740740740740740740741
能夠看到天數差的小數部分是很是精確的,因此徹底能夠精確的表示兩個日期時間值之
間差的小時、分、秒甚至毫秒部分。因此若是要計算兩個日期時間值之間的小時、分、秒以
及毫秒差的話,只要進行相應的換算就能夠,好比下面的SQL用來計算當前時刻和出生日期
FBirthDay之間的時間間隔(小時、分以及秒):
SELECT (SYSDATE-FBirthDay)*24,(SYSDATE-FBirthDay)*24*60,
(SYSDATE-FBirthDay)*24*60*60
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
(SYSDATE-FBIRTHDAY)*24 (SYSDATE-FBIRTHDAY)*24*60 (SYSDATE-FBIRTHDAY)*24*60*
60
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
235127.289166666666666666
6666666666666667
14107637.35 846458241
184055.289166666666666666
6666666666666667
11043317.35 662599041
176999.289166666666666666
6666666666666667
10619957.35 637197441
223679.289166666666666666
6666666666666667
13420757.35 805245441
218423.289166666666666666
6666666666666667
13105397.35 786323841
205511.289166666666666666
6666666666666667
12330677.35 739840641
245639.289166666666666666
6666666666666656
14738357.3499999999999999
9999999999999994
884301440.999999999999999
999999999999996
311183.289166666666666666
6666666666666656
18670997.3499999999999999
9999999999999994
1120259840.99999999999999
9999999999999996
下面的SQL語句用來計算當前時刻和出生日期FBirthDay之間的周間隔:
SELECT SYSDATE,FBirthDay,(SYSDATE-FBirthDay)/7
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
SYSDATE FBIRTHDAY (SYSDATE-FBIRTHDAY)/7
2008-01-16
23:22:17.0
1981-03-22
00:00:00.0
1399.567686838624338624338624338624338624
2008-01-16
23:22:17.0
1987-01-18
00:00:00.0
1095.567686838624338624338624338624338624
2008-01-16
23:22:17.0
1987-11-08
00:00:00.0
1053.567686838624338624338624338624338624
2008-01-16
23:22:17.0
1982-07-12
00:00:00.0
1331.424829695767195767195767195767195767
2008-01-16
23:22:17.0
1983-02-16
00:00:00.0
1300.139115410052910052910052910052910053
2008-01-16
23:22:17.0
1984-08-07
00:00:00.0
1223.28197255291005291005291005291005291
2008-01-16
23:22:17.0
1980-01-09
00:00:00.0
1462.139115410052910052910052910052910057
2008-01-16
23:22:17.0
1972-07-18
00:00:00.0
1852.281972552910052910052910052910052914
能夠看到計算結果含有很是精確的小數部分,不過若是對這些小數部分沒有需求的話則
能夠使用數值函數進行四捨五入、取最大整數等處理,好比下面的SQL用來計算當前時刻和
出生日期FBirthDay之間的時間間隔(小時、分以及秒),而且對於計算結果進行四捨五入運
算:
SELECT
ROUND((SYSDATE-FBirthDay)*24),ROUND((SYSDATE-FBirthDay)*24*60),
ROUND((SYSDATE-FBirthDay)*24*60*60)
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
ROUND((SYSDATE-FBIRTHD
AY)*24)
ROUND((SYSDATE-FBIRTHDAY)
*24*60)
ROUND((SYSDATE-FBIRTHDAY)*2
4*60*60)
235127 14107641 846458455
184055 11043321 662599255
176999 10619961 637197655
223679 13420761 805245655
218423 13105401 786324055
205511 12330681 739840855
245639 14738361 884301655
311183 18671001 1120260055
5.3.5.4 DB2
DB2中提供了DAYS()函數,這個函數接受一個時間日期類型的參數,返回結果爲從0001
年1月1日到此日期的天數,好比下面的SQL語句用於計算出生日期FBirthDay、註冊日期
FRegDay以及當前日期距0001年1月1日的天數差:
SELECT DAYS(FBirthDay),DAYS(FRegDay),DAYS(CURRENT DATE)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 2 3
723261 729510 733057
725389 729987 733057
725683 730746 733057
723738 730180 733057
723957 729510 733057
724495 729814 733057
722823 731116 733057
720092 728463 733057
藉助於DAYS()函數咱們能夠輕鬆計算兩個日期之間的天數間隔,很顯然
DAYS(date1)-DAYS(date2)的計算結果就是日期date1和日期date2之間的天數間隔,好比下面的
SQL語句用於計算出生日期FBirthDay與註冊日期FRegDay之間的天數間隔:
SELECT FBirthDay,FRegDay,
DAYS(FRegDay)-DAYS(FBirthDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY FREGDAY 3
1981-03-22 1998-05-01 6249
1987-01-18 1999-08-21 4598
1987-11-08 2001-09-18 5063
1982-07-12 2000-03-01 6442
1983-02-16 1998-05-01 5553
1984-08-07 1999-03-01 5319
1980-01-09 2002-09-23 8293
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1972-07-18 1995-06-19 8371
若是要計算兩個日期時間值之間的周間隔的話,只要進行相應的換算就能夠,好比下面
的SQL用來計算出生日期FBirthDay和註冊日期FRegDay之間的週數間隔:
SELECT FBirthDay,FRegDay,
(DAYS(FRegDay)-DAYS(FBirthDay))/7
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY FREGDAY 3
1981-03-22 1998-05-01 892
1987-01-18 1999-08-21 656
1987-11-08 2001-09-18 723
1982-07-12 2000-03-01 920
1983-02-16 1998-05-01 793
1984-08-07 1999-03-01 759
1980-01-09 2002-09-23 1184
1972-07-18 1995-06-19 1195
5.3.6 計算一個日期是星期幾
計算一個日期是星期幾是很是有用的,好比若是安排的報到日期是週末那麼就向後拖延
報到日期,在主流數據庫中對這個功能都提供了很好的支持,下面分別進行介紹。
5.3.6.1 MYSQL
MYSQL中提供了DAYNAME()函數用於計算一個日期是星期幾,好比下面的SQL語句用於
計算出生日期和註冊日期各是星期幾:
SELECT FBirthDay,DAYNAME(FBirthDay),
FRegDay,DAYNAME(FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay DAYNAME(FBirthDay) FRegDay DAYNAME(FRegDay)
1981-03-22 00:00:00 Sunday 1998-05-01 00:00:00 Friday
1987-01-18 00:00:00 Sunday 1999-08-21 00:00:00 Saturday
1987-11-08 00:00:00 Sunday 2001-09-18 00:00:00 Tuesday
1982-07-12 00:00:00 Monday 2000-03-01 00:00:00 Wednesday
1983-02-16 00:00:00 Wednesday 1998-05-01 00:00:00 Friday
1984-08-07 00:00:00 Tuesday 1999-03-01 00:00:00 Monday
1980-01-09 00:00:00 Wednesday 2002-09-23 00:00:00 Monday
1972-07-18 00:00:00 Tuesday 1995-06-19 00:00:00 Monday
注意MYSQL中DAYNAME()函數返回的是英文的日期表示法。
5.3.6.2 MSQLServer
MSQLServer中提供了DATENAME()函數,這個函數能夠返回一個日期的特定部分,而且
儘可能用名稱來表述這個特定部分,其參數格式以下:
DATENAME(datepart,date)
其中參數date爲待計算日期,date 參數也能夠是日期格式的字符串;參數datepart指定
要返回的日期部分的參數,其可選值以下:
可選值 別名 說明
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Year yy、yyyy 年份
Quarter qq, q 季度
Month mm, m 月份
Dayofyear dy, y 每一年的某一日
Day dd, d 日期
Week wk, ww 星期
Weekday dw 工做日
Hour hh 小時
Minute mi, n 分鐘
Second ss, s 秒
Millisecond ms 毫秒
若是使用Weekday(或者使用別名dw)作爲datepart參數調用DATENAME()函數就能夠得
到一個日期是星期幾,好比下面的SQL語句用於計算出生日期和註冊日期各是星期幾:
SELECT FBirthDay,DATENAME(Weekday,FBirthDay),
FRegDay,DATENAME(DW, FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay FRegDay
1981-03-22
00:00:00.0
星期日 1998-05-01
00:00:00.0
星期五
1987-01-18
00:00:00.0
星期日 1999-08-21
00:00:00.0
星期六
1987-11-08
00:00:00.0
星期日 2001-09-18
00:00:00.0
星期二
1982-07-12
00:00:00.0
星期一 2000-03-01
00:00:00.0
星期三
1983-02-16
00:00:00.0
星期三 1998-05-01
00:00:00.0
星期五
1984-08-07
00:00:00.0
星期二 1999-03-01
00:00:00.0
星期一
1980-01-09
00:00:00.0
星期三 2002-09-23
00:00:00.0
星期一
1972-07-18
00:00:00.0
星期二 1995-06-19
00:00:00.0
星期一
5.3.6.3 Oracle
Oracle中提供了TO_CHAR()函數用於將數據轉換爲字符串類型,當針對時間日期類型數
據進行轉換的時候,它接受兩個參數,其參數格式以下:
TO_CHAR(date,format)
其中參數date爲待轉換的日期,參數format爲格式化字符串,數據庫系統將按照這個字
符串對date進行轉換,格式化字符串中能夠採用以下的佔位符:
佔位符 說明
YEAR 年份(英文拼寫),好比NINETEEN NINETY-EIGHT
YYYY 4位年份,好比1998
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
YYY 年份後3位,好比998
YY 年份後2位,好比98
Y 年份後1位,好比8
IYYY 符合ISO標準的4位年份,好比1998
IYY 符合ISO標準的年份後3位,好比998
IY 符合ISO標準的年份後2位,好比98
I 符合ISO標準的年份後1位,好比8
Q 以整數表示的季度,好比1
MM 以整數表示的月份,好比01
MON 月份的名稱,好比2月
MONTH 月份的名稱,補足9個字符
RM 羅馬錶示法的月份,好比VIII
WW 日期屬於當年的第幾周,好比30
W 日期屬於當月的第幾周,好比2
IW 日期屬於當年的第幾周(按照ISO標準),好比30
D 日期屬於周幾,以整數表示,返回值範圍爲1至7
DAY 日期屬於周幾,以名字的形式表示,好比星期五
DD 日期屬於當月的第幾天,好比2
DDD 日期屬於當年的第幾天,好比168
DY 日期屬於周幾,以名字的形式表示,好比星期五
HH 小時部分(12小時制)
HH12 小時部分(12小時制)
HH24 小時部分(24小時制)
MI 分鐘部分
SS 秒部分
SSSSS 自從午夜開始的秒數
能夠簡單的將佔位符作爲參數傳遞給TO_CHAR()函數,下面的SQL語句用於計算出生日
期的年份、月份以及週數:
SELECT FBirthDay,
TO_CHAR(FBirthDay, 'YYYY') as yyyy,
TO_CHAR(FBirthDay, 'MM') as mm,
TO_CHAR(FBirthDay, 'MON') as mon,
TO_CHAR(FBirthDay, 'WW') as ww
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY YYYY MM MON WW
1981-03-22
00:00:00.0
1981 03 3月 12
1987-01-18
00:00:00.0
1987 01 1月 03
1987-11-08
00:00:00.0
1987 11 11 月 45
1982-07-12 1982 07 7月 28
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
00:00:00.0
1983-02-16
00:00:00.0
1983 02 2月 07
1984-08-07
00:00:00.0
1984 08 8月 32
1980-01-09
00:00:00.0
1980 01 1月 02
1972-07-18
00:00:00.0
1972 07 7月 29
一樣還能夠將佔位符組合起來實現更加複雜的轉換邏輯,好比下面的SQL語句用於以
「2008-08-08」這樣的形式顯示出生日期以及以「31-2007-02」這樣的形式顯示註冊日期:
SELECT FBirthDay,
TO_CHAR(FBirthDay, 'YYYY-MM-DD') as yyymmdd,
FRegDay,
TO_CHAR(FRegDay, 'DD-YYYY-MM') as ddyyyymm
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY YYYMMDD FREGDAY DDYYYYMM
1981-03-22
00:00:00.0
1981-03-22 1998-05-01
00:00:00.0
01-1998-05
1987-01-18
00:00:00.0
1987-01-18 1999-08-21
00:00:00.0
21-1999-08
1987-11-08
00:00:00.0
1987-11-08 2001-09-18
00:00:00.0
18-2001-09
1982-07-12
00:00:00.0
1982-07-12 2000-03-01
00:00:00.0
01-2000-03
1983-02-16
00:00:00.0
1983-02-16 1998-05-01
00:00:00.0
01-1998-05
1984-08-07
00:00:00.0
1984-08-07 1999-03-01
00:00:00.0
01-1999-03
1980-01-09
00:00:00.0
1980-01-09 2002-09-23
00:00:00.0
23-2002-09
1972-07-18
00:00:00.0
1972-07-18 1995-06-19
00:00:00.0
19-1995-06
咱們前面提到了,當用「DAY」作爲參數的時候就能夠將日期格式化爲名字的形式表示
的星期幾,好比下面的SQL語句用於計算出生日期以及註冊日期各屬於星期幾:
SELECT
FBirthDay,TO_CHAR(FBirthDay, 'DAY') as birthwk,
FRegDay,TO_CHAR(FRegDay, 'DAY') as regwk
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY BIRTHWK FREGDAY REGWK
1981-03-22
00:00:00.0
星期日 1998-05-01
00:00:00.0
星期五
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1987-01-18
00:00:00.0
星期日 1999-08-21
00:00:00.0
星期六
1987-11-08
00:00:00.0
星期日 2001-09-18
00:00:00.0
星期二
1982-07-12
00:00:00.0
星期一 2000-03-01
00:00:00.0
星期三
1983-02-16
00:00:00.0
星期三 1998-05-01
00:00:00.0
星期五
1984-08-07
00:00:00.0
星期二 1999-03-01
00:00:00.0
星期一
1980-01-09
00:00:00.0
星期三 2002-09-23
00:00:00.0
星期一
1972-07-18
00:00:00.0
星期二 1995-06-19
00:00:00.0
星期一
5.3.6.4 DB2
DB2中提供了DAYNAME()函數用於計算一個日期是星期幾,執行下面的SQL語句咱們能夠
獲得出生日期和註冊日期各是星期幾:
SELECT
FBirthDay,DAYNAME(FBirthDay) as birthwk,
FRegDay,DAYNAME(FRegDay) as regwk
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY BIRTHWK FREGDAY REGWK
1981-03-22 星期日 1998-05-01 星期五
1987-01-18 星期日 1999-08-21 星期六
1987-11-08 星期日 2001-09-18 星期二
1982-07-12 星期一 2000-03-01 星期三
1983-02-16 星期三 1998-05-01 星期五
1984-08-07 星期二 1999-03-01 星期一
1980-01-09 星期三 2002-09-23 星期一
1972-07-18 星期二 1995-06-19 星期一
5.3.7 取得日期的指定部分
提取日期的特定部分是很是有必要的,好比檢索本年的每月的16日的銷售量、檢索訪
問用戶集中的時間段,這些都須要對日期的特定部分進行提取,在主流數據庫中對這個功能
都提供了很好的支持,下面分別進行介紹。
5.3.7.1 MYSQL
MYSQL中提供了一個DATE_FORMAT()函數用來將日期按照特定各是進行格式化,這個函
數的參數格式以下:
DATE_FORMAT(date,format)
這個函數用來按照特定的格式化指定的日期,其中參數date爲待計算的日期值,而參數
format爲格式化字符串,格式化字符串中能夠採用以下的佔位符:
佔位符 說明
%a 縮寫的星期幾(Sun..Sat)
%b 縮寫的月份名(Jan..Dec)
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
%c 數字形式的月份(0..12)
%D 當月的第幾天,帶英文後綴(0th, 1st, 2nd, 3rd, ...)
%d 當月的第幾天,兩位數字形式,不足兩位則補零(00..31)
%e 當月的第幾天,數字形式(0..31)
%f 毫秒
%H 24小時制的小時 (00..23)
%h 12小時制的小時(01..12)
%I 12小時制的小時(01..12)
%i 數字形式的分鐘(00..59)
%j 日期在當年中的天數(001..366)
%k 24進制小時(0..23)
%l 12進制小時(1..12)
%M 月份名(January..December)
%m 兩位數字表示的月份(00..12)
%p 上午仍是下午(AM.. PM)
%r 12小時制時間,好比08:09:29 AM
%S 秒數(00..59)
%s 秒數(00..59)
%T 時間,24小時制,格式爲hh:mm:ss
%U 所屬周是當年的第幾周,週日看成第一天(00..53)
%u 所屬周是當年的第幾周,週一看成第一天(00..53)
%V 所屬周是當年的第幾周,週日看成第一天(01..53)
%v 所屬周是當年的第幾周,週一看成第一天(01..53)
%W 星期幾(Sunday..Saturday)
%w 星期幾,數字形式(0=Sunday..6=Saturday)
%X 本週所屬年,週日看成第一天
%x 本週所屬年,週一看成第一天
%Y 年份數,四位數字
%y 年份數,兩位數字
組合使用這些佔位符就能夠實現很是複雜的字符串格式化邏輯,好比下面的SQL語句實
現了將出生日期FBirthDay和註冊日期FRegDay分別按照兩種格式進行格式化:
SELECT
FBirthDay,
DATE_FORMAT(FBirthDay,'%y-%M %D %W') AS bd,
FRegDay,
DATE_FORMAT(FRegDay,'%Y年%m月%e日') AS rd
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay bd FRegDay rd
1981-03-22 00:00:00 81-March 22nd
Sunday
1998-05-01 00:00:00 1998年05 月1 日
1987-01-18 00:00:00 87-January 18th
Sunday
1999-08-21 00:00:00 1999年08 月21日
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1987-11-08 00:00:00 87-November 8th
Sunday
2001-09-18 00:00:00 2001年09 月18日
1982-07-12 00:00:00 82-July 12th Monday 2000-03-01 00:00:00 2000年03 月1 日
1983-02-16 00:00:00 83-February 16th
Wednesday
1998-05-01 00:00:00 1998年05 月1 日
1984-08-07 00:00:00 84-August 7th
Tuesday
1999-03-01 00:00:00 1999年03 月1 日
1980-01-09 00:00:00 80-January 9th
Wednesday
2002-09-23 00:00:00 2002年09 月23日
1972-07-18 00:00:00 72-July 18th Tuesday 1995-06-19 00:00:00 1995年06 月19日
很顯然,若是隻使用單獨的佔位符那麼就能夠實現提取日期特定部分的功能了,好比
DATE_FORMAT(date,'%Y')能夠用來提取日期的年份部分、DATE_FORMAT(date,'%H')能夠用來提
取日期的小時部分、DATE_FORMAT(date,'%M')能夠用來提取日期的月份名稱。下面的SQL用
於提取每一個人員的出生年份、出生時是當年的第幾天、出生時是當年的第幾周:
SELECT
FBirthDay,
DATE_FORMAT(FBirthDay,'%Y') AS y,
DATE_FORMAT(FBirthDay,'%j') AS d,
DATE_FORMAT(FBirthDay,'%U') AS u
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay y d u
1981-03-22 00:00:00 1981 081 12
1987-01-18 00:00:00 1987 018 03
1987-11-08 00:00:00 1987 312 45
1982-07-12 00:00:00 1982 193 28
1983-02-16 00:00:00 1983 047 07
1984-08-07 00:00:00 1984 220 32
1980-01-09 00:00:00 1980 009 01
1972-07-18 00:00:00 1972 200 29
5.3.7.2 MSSQLServer
在5.3.6.2一節中咱們介紹了DATENAME()函數,使用它就能夠提取日期的任意部分,好比
下面的SQL用於提取每一個人員的出生年份、出生時是當年的第幾天、出生時是當年的第幾周:
SELECT
FBirthDay,
DATENAME(year,FBirthDay) AS y,
DATENAME(dayofyear,FBirthDay) AS d,
DATENAME(week,FBirthDay) AS u
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay y d u
1981-03-22 00:00:00.0 1981 81 13
1987-01-18 00:00:00.0 1987 18 4
1987-11-08 00:00:00.0 1987 312 46
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
1982-07-12 00:00:00.0 1982 193 29
1983-02-16 00:00:00.0 1983 47 8
1984-08-07 00:00:00.0 1984 220 32
1980-01-09 00:00:00.0 1980 9 2
1972-07-18 00:00:00.0 1972 200 30
在MSSQLServer中還提供了一個DATEPART()函數,這個函數也能夠用來返回一個日期的
特定部分,其參數格式以下:
DATEPART (datepart,date)
其中參數date爲待計算日期,date 參數也能夠是日期格式的字符串;參數datepart指定
要返回的日期部分的參數,其可選值以下:
可選值 別名 說明
Year yy、yyyy 年份
Quarter qq, q 季度
Month mm, m 月份
Dayofyear dy, y 每一年的某一日
Day dd, d 日期
Week wk, ww 星期
Weekday dw 工做日
Hour hh 小時
Minute mi, n 分鐘
Second ss, s 秒
Millisecond ms 毫秒
顯然使用Dayofyear作爲datepart參數調用DATEPART ()函數就能夠獲得一個日期是當年
的第幾天;使用Year作爲datepart參數調用DATEPART ()函數就能夠獲得一個日期的年份數;
以此類推……。下面的SQL語句用於計算出生日期是當年第幾天以及註冊日期中的年份部分:
SELECT FBirthDay, DATEPART(Dayofyear,FBirthDay),
FRegDay, DATEPART(Year, FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay FRegDay
1981-03-22 00:00:00.0 81 1998-05-01 00:00:00.0 1998
1987-01-18 00:00:00.0 18 1999-08-21 00:00:00.0 1999
1987-11-08 00:00:00.0 312 2001-09-18 00:00:00.0 2001
1982-07-12 00:00:00.0 193 2000-03-01 00:00:00.0 2000
1983-02-16 00:00:00.0 47 1998-05-01 00:00:00.0 1998
1984-08-07 00:00:00.0 220 1999-03-01 00:00:00.0 1999
1980-01-09 00:00:00.0 9 2002-09-23 00:00:00.0 2002
1972-07-18 00:00:00.0 200 1995-06-19 00:00:00.0 1995
粗看起來,DATEPART()函數和DATENAME()函數徹底同樣,不過其實它們並非只是名稱
不一樣的別名函數,雖然都是用來提取日期的特定部分的,不過DATEPART()函數的返回值是數
字而DATENAME()函數則會將盡量的以名稱的方式作爲返回值。
5.3.7.3 Oracle
在5.3.6.3一節中咱們介紹了Oracle中使用TO_CHAR()函數格式化日期的方法,使用它就可
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
以提取日期的任意部分,好比下面的SQL用於提取每一個人員的出生年份、出生時是當年的第
幾天、出生時是當年的第幾周:
SELECT
FBirthDay,
TO_CHAR(FBirthDay,'YYYY') AS y,
TO_CHAR(FBirthDay,'DDD') AS d,
TO_CHAR(FBirthDay,'WW') AS u
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY Y D U
1981-03-22 00:00:00.0 1981 081 12
1987-01-18 00:00:00.0 1987 018 03
1987-11-08 00:00:00.0 1987 312 45
1982-07-12 00:00:00.0 1982 193 28
1983-02-16 00:00:00.0 1983 047 07
1984-08-07 00:00:00.0 1984 220 32
1980-01-09 00:00:00.0 1980 009 02
1972-07-18 00:00:00.0 1972 200 29
5.3.7.4 DB2
DB2中沒有提供像MYSQL、Oracle中那樣的日期格式化函數,也沒有提供像MSSQLServer
中DATENAME()那樣通用的取日期的特定部分的函數,DB2中對於提取日期的不一樣的部分須要
使用不一樣的函數,這些函數的列表以下:
函數名 功能說明
YEAR() 取參數的年份部分
MONTH() 取參數的月份部分,返回值爲整數
MONTHNAME() 對於參數的月部分的月份,返回一個大小寫混合的字符串(例如,
January)。
QUARTER() 取參數的季度數
DAYOFYEAR() 返回參數中一年中的第幾天,用範圍在 1-366 的整數值表示。
DAY() 取參數的日部分
DAYNAME() 返回一個大小寫混合的字符串,對於參數的日部分,用星期表示這一
天的名稱(例如,Friday)。
WEEK() 返回參數是一年中的第幾周
DAYOFWEEK() 返回參數中的星期幾,用範圍在 1-7 的整數值表示,其中 1 表明星期
日。
HOUR() 取參數的小時部分
MINUTE() 取參數的分鐘部分
SECOND() 取參數的秒鐘部分
MICROSECOND() 取參數的微秒部分
下面的SQL語句用於計算出生日期的年份部分而且計算註冊日期的月份名以及是一年
中的第幾周:
SELECT
FBirthDay,
YEAR(FBirthDay),
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FRegDay,
MONTHNAME(FRegDay),
WEEK(FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY 2 FREGDAY 4 5
1981-03-22 1981 1998-05-01 五月 18
1987-01-18 1987 1999-08-21 八月 34
1987-11-08 1987 2001-09-18 九月 38
1982-07-12 1982 2000-03-01 三月 10
1983-02-16 1983 1998-05-01 五月 18
1984-08-07 1984 1999-03-01 三月 10
1980-01-09 1980 2002-09-23 九月 39
1972-07-18 1972 1995-06-19 六月 25
5.4 其餘函數
除了數學函數、字符串函數、日期函數以外,數據庫中還有其餘一些函數,好比進行類
型轉換的函數、進行非空邏輯判斷的函數等,這些函數也是很是重要的,所以在本節中咱們
將對這些函數進行介紹。
5.4.1 類型轉換
在使用SQL語句的時候,咱們使用的數據的類型不必定符合函數或者運算符的須要,比
如函數須要整數類型的數據而咱們使用的則是一個字符串,在一些狀況下數據庫系統會替我
們自動將字符串類型轉換爲整數類型,這種轉換稱爲隱式轉換。可是在有的狀況下數據庫系
統不會進行隱式轉換,這時就要使用類型轉換函數了,這種轉換稱爲顯式轉換。使用類型轉
換函數不只能夠保證類型轉換的正確性,並且能夠提升數據處理的速度,所以應該使用顯式
轉換,儘可能避免使用隱式轉換。
在主流數據庫系統中都提供了類型轉換函數,下面分別進行介紹。
5.4.1.1 MYSQL
MYSQL中提供了CAST()函數和CONVERT()函數用於進行類型轉換,CAST()是符合ANSI
SQL99的函數,CONVERT() 是符合ODBC標準的函數,這兩個函數只是參數的調用方式略有差
異,其功能幾乎相同。這兩個函數的參數格式以下:
CAST(expression AS type)
CONVERT(expression,type)
參數expression爲待進行類型轉換的表達式,而type爲轉換的目標類型,type能夠是下面
的任一個:
可選值 縮寫 說明
BINARY BINARY字符串
CHAR 字符串類型
DATE 日期類型
DATETIME 時間日期類型
SIGNED INTEGER SIGNED 有符號整數
TIME 時間類型
UNSIGNED INTEGER UNSIGNED 無符號整數
下面的SQL語句分別演示以有符號整形、無符號整形、日期類型、時間類型爲目標類型
的數據轉換:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SELECT
CAST('-30' AS SIGNED) as sig,
CONVERT ('36', UNSIGNED INTEGER) as usig,
CAST('2008-08-08' AS DATE) as d,
CONVERT ('08:09:10', TIME) as t
執行完畢咱們就能在輸出結果中看到下面的執行結果:
sig usig d t
-30 36 2008-08-08 08:09:10
5.4.1.2 MSSQLServer
與MYSQL相似,MSSQLServer中一樣提供了名稱爲CAST()和CONVERT()兩個函數用於進行
類型轉換,CAST()是符合ANSI SQL99的函數,CONVERT() 是符合ODBC標準的函數。與MYSQL
中的CONVERT()函數不一樣的是MSSQLServer中的CONVERT()函數參數順序正好與MYSQL中的
CONVERT()函數參數順序相反。這兩個函數的參數格式以下:
CAST ( expression AS data_type)
CONVERT ( data_type, expression)
參數expression爲待進行類型轉換的表達式,而type爲轉換的目標類型,與MYSQL不一樣,
MYSQLServer中的目標類型幾乎能夠是數據庫系統支持的任何類型。
下面的SQL語句分別演示以整形、數值、日期時間類型爲目標類型的數據轉換:
SELECT
CAST('-30' AS INTEGER) as i,
CONVERT(DECIMAL,'3.1415726') as d,
CONVERT(DATETIME,'2008-08-08 08:09:10') as dt
執行完畢咱們就能在輸出結果中看到下面的執行結果:
i d dt
-30 3 2008-08-08 08:09:10.0
下面的SQL語句用於將每一個人的身份證後三位轉換爲整數類型而且進行相關的計算:
SELECT FIdNumber,
RIGHT(FIdNumber,3) as 後三位,
CAST(RIGHT(FIdNumber,3) AS INTEGER) as 後三位的整數形式,
CAST(RIGHT(FIdNumber,3) AS INTEGER)+1 as 後三位加1,
CONVERT(INTEGER,RIGHT(FIdNumber,3))/2 as 後三位除以2
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FIdNumber 後三位 後三位的整數形式 後三位加1 後三位除以2
123456789120 120 120 121 60
123456789121 121 121 122 60
123456789122 122 122 123 61
123456789123 123 123 124 61
123456789124 124 124 125 62
123456789125 125 125 126 62
123456789126 126 126 127 63
123456789127 127 127 128 63
5.4.1.3 Oracle
Oracle中也有一個名稱爲CONVERT()的函數,不過這個函數是用來進行字符集轉換的。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Oracle中不支持用作數據類型轉換的CAST()和CONVERT()兩個函數,它提供了針對性更強的類
型TO_CHAR()、TO_DATE()、TO_NUMBER()等函數,這些函數能夠將數據顯式的轉換爲字符串
類型、日期時間類型或者數值類型。Oracle中還提供了HEXTORAW()、RAWTOHEX()、
TO_MULTI_BYTE()、TO_SINGLE_BYTE()等函數用於存儲格式的轉換。下面咱們將對這些函數進
行分別介紹。
1) TO_CHAR()
TO_CHAR()函數用來將時間日期類型或者數值類型的數據轉換爲字符串,其參數格式如
下:
TO_CHAR(expression,format)
參數expression爲待轉換的表達式,參數format爲轉換後的字符串格式,參數format能夠
省略,若是省略參數format將會按照數據庫系統內置的轉換規則進行轉換。參數format的可
以採用的格式很是豐富,具體能夠參考Oracle的聯機文檔。
下面的SQL語句將出生日期和身高按照不一樣的格式轉換爲字符串類型:
SELECT FBirthDay,
TO_CHAR(FBirthDay,'YYYY-MM-DD') as c1,
FWeight,
TO_CHAR(FWeight,'L99D99MI') as c2,
TO_CHAR(FWeight) as c3
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY C1 FWEIGHT C2 C3
1981-03-22
00:00:00.0
1981-03-22 56.67 ¥56.67 56.67
1987-01-18
00:00:00.0
1987-01-18 36.17 ¥36.17 36.17
1987-11-08
00:00:00.0
1987-11-08 40.33 ¥40.33 40.33
1982-07-12
00:00:00.0
1982-07-12 46.23 ¥46.23 46.23
1983-02-16
00:00:00.0
1983-02-16 48.68 ¥48.68 48.68
1984-08-07
00:00:00.0
1984-08-07 66.67 ¥66.67 66.67
1980-01-09
00:00:00.0
1980-01-09 51.28 ¥51.28 51.28
1972-07-18
00:00:00.0
1972-07-18 60.32 ¥60.32 60.32
2) TO_DATE()
TO_DATE()函數用來將字符串轉換爲時間類型,其參數格式以下:
TO_DATE (expression,format)
參數expression爲待轉換的表達式,參數format爲轉換格式,參數format能夠省略,若是
省略參數format將會按照數據庫系統內置的轉換規則進行轉換。
下面的SQL語句用於將字符串形式的數據按照特定的格式解析爲日期類型:
SELECT
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
TO_DATE('2008-08-08 08:09:10', 'YYYY-MM-DD HH24:MI:SS') as dt1,
TO_DATE('20080808 080910', 'YYYYMMDD HH24MISS') as dt2
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
DT1 DT2
2008-08-08 08:09:10.0 2008-08-08 08:09:10.0
3) TO_NUMBER()
TO_NUMBER()函數用來將字符串轉換爲數值類型,其參數格式以下:
TO_NUMBER (expression,format)
參數expression爲待轉換的表達式,參數format爲轉換格式,參數format能夠省略,若是
省略參數format將會按照數據庫系統內置的轉換規則進行轉換。參數format的能夠採用的格
式很是豐富,具體能夠參考Oracle的聯機文檔。
下面的SQL語句用於將字符串形式的數據按照特定的格式解析爲數值類型:
SELECT
TO_NUMBER('33.33') as n1,
TO_NUMBER('100.00', '9G999D99') as n2
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
N1 N2
33.33 100.55
4) HEXTORAW()、RAWTOHEX()
HEXTORAW()用於將十六進制格式的數據轉換爲原始值,而RAWTOHEX()函數用來將原始
值轉換爲十六進制格式的數據。例子以下:
SELECT HEXTORAW('7D'),
RAWTOHEX ('a'),
HEXTORAW(RAWTOHEX('w'))
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
HEXTORAW(7D) RAWTOHEX(A) HEXTORAW(RAWTOHEX(W))
} 61 w
5) TO_MULTI_BYTE()、TO_SINGLE_BYTE()
TO_MULTI_BYTE()函數用於將字符串中的半角字符轉換爲全角字符,而TO_SINGLE_BYTE()
函數則用來將字符串中的全角字符轉換爲半角字符。例子以下:
SELECT
TO_MULTI_BYTE('moring'),
TO_SINGLE_BYTE('hello')
FROM DUAL
執行完畢咱們就能在輸出結果中看到下面的執行結果:
TO_MULTI_BYTE(MORING) TO_SINGLE_BYTE(HELLO)
moring hello
5.4.1.4 DB2
DB2中沒有提供專門進行顯式類型轉換的函數,取而代之的是借用了不少高級語言中的
強制類型轉換的概念,也就是使用目標類型名作爲函數名來進行類型轉換,好比要將expr
轉換爲日期類型,那麼使用DATE(expr)便可。這種實現機制很是方便,下降了學習難度。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
下面的SQL語句展現了DB2中類型轉換的方式:
SELECT CHAR(FRegDay),
INT('33'),
DOUBLE('-3.1415926')
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
1 2 3
1998-05-01 33 -3.1415926
1999-08-21 33 -3.1415926
2001-09-18 33 -3.1415926
2000-03-01 33 -3.1415926
1998-05-01 33 -3.1415926
1999-03-01 33 -3.1415926
2002-09-23 33 -3.1415926
1995-06-19 33 -3.1415926
5.4.2 空值處理
在數據庫中常常須要對空值(NULL)作處理,好比「若是名稱爲空值則返回別名」,甚
至還有更復雜的需求,好比「若是名稱爲空值則返回別名,若是別名也爲空則返回‘佚名’
兩個字」、「若是名稱爲與別名相等則返回空值,不然返回名稱」。這些需求已經帶有流程控
制的色彩了,通常來講須要在宿主語言中使用流程控制語句來進行處理,但是若是是在報表
程序等大數據量的程序中把這些任務交給宿主語言的話會大大下降運行速度,所以咱們必須
想辦法在SQL這一層進行處理。
爲了更好的演示本節中的例子,咱們須要對T_Person表中的數據進行一下修改,也就是
將Kerry的出生日期修改成空值,將Smith的出生日期和註冊日期都修改成空值,執行下面的
SQL語句:
UPDATE T_Person SET FBirthDay=null WHERE FName='Kerry';
UPDATE T_Person SET FBirthDay=null AND FRegDay=null WHERE FName='Smith';
執行完畢咱們查看T_Person表中中的數據以下:
FIDNUMBER FNAME FBIRTHDAY FREGDAY FWEIGHT
123456789120 Tom 1981-03-22 1998-05-01 56.67
123456789121 Jim 1987-01-18 1999-08-21 36.17
123456789122 Lily 1987-11-08 2001-09-18 40.33
123456789123 Kelly 1982-07-12 2000-03-01 46.23
123456789124 Sam 1983-02-16 1998-05-01 48.68
123456789125 Kerry <NULL> 1999-03-01 66.67
123456789126 Smith <NULL> <NULL> 51.28
123456789127 BillGates 1972-07-18 1995-06-19 60.32
5.4.2.1 COALESCE()函數
主流數據庫系統都支持COALESCE()函數,這個函數主要用來進行空值處理,其參數格式
以下:
COALESCE ( expression,value1,value2……,valuen)
COALESCE()函數的第一個參數expression爲待檢測的表達式,而其後的參數個數不定。
COALESCE()函數將會返回包括expression在內的全部參數中的第一個非空表達式。若是
expression不爲空值則返回expression;不然判斷value1是不是空值,若是value1不爲空值則返
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
回value1;不然判斷value2是不是空值,若是value2不爲空值則返回value3;……以此類推,
若是全部的表達式都爲空值,則返回NULL。
咱們將使用COALESCE()函數完成下面的功能,返回人員的「重要日期」:若是出生日期
不爲空則將出生日期作爲「重要日期」,若是出生日期爲空則判斷註冊日期是否爲空,若是
註冊日期不爲空則將註冊日期作爲「重要日期」,若是註冊日期也爲空則將「2008年8月8日」
作爲「重要日期」。實現此功能的SQL語句以下:
MYSQL、MSSQLServer、DB2:
SELECT FName,FBirthDay,FRegDay,
COALESCE(FBirthDay,FRegDay,'2008-08-08') AS ImportDay
FROM T_Person
Oracle:
SELECT FBirthDay,FRegDay,
COALESCE(FBirthDay,FRegDay,TO_DATE('2008-08-08', 'YYYY-MM-DD HH24:MI:SS')) AS
ImportDay
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FBirthDay FRegDay ImportDay
Tom 1981-03-22 00:00:00 1998-05-01 00:00:00 1981-03-22 00:00:00
Jim 1987-01-18 00:00:00 1999-08-21 00:00:00 1987-01-18 00:00:00
Lily 1987-11-08 00:00:00 2001-09-18 00:00:00 1987-11-08 00:00:00
Kelly 1982-07-12 00:00:00 2000-03-01 00:00:00 1982-07-12 00:00:00
Sam 1983-02-16 00:00:00 1998-05-01 00:00:00 1983-02-16 00:00:00
Kerry <NULL> 1999-03-01 00:00:00 1999-03-01 00:00:00
Smith <NULL> <NULL> 2008-08-08
BillGates 1972-07-18 00:00:00 1995-06-19 00:00:00 1972-07-18 00:00:00
這裏邊最關鍵的就是Kerry和Smith這兩行,能夠看到這裏的計算邏輯是徹底符合咱們的
需求的。
5.4.2.2 COALESCE()函數的簡化版
COALESCE()函數能夠用來完成幾乎全部的空值處理,不過在不少數據庫系統中都提供了
它的簡化版,這些簡化版中只接受兩個變量,其參數格式以下:
MYSQL:
IFNULL(expression,value)
MSSQLServer:
ISNULL(expression,value)
Oracle:
NVL(expression,value)
這幾個函數的功能和COALESCE(expression,value)是等價的。好比SQL語句用於返回人員的
「重要日期」,若是出生日期不爲空則將出生日期作爲「重要日期」,若是出生日期爲空則返
回NULL:
MYSQL:
SELECT FBirthDay,FRegDay,
IFNULL(FBirthDay,FRegDay) AS ImportDay
FROM T_Person
MSSQLServer:
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
SELECT FBirthDay,FRegDay,
ISNULL(FBirthDay,FRegDay) AS ImportDay
FROM T_Person
Oracle:
SELECT FBirthDay,FRegDay,
NVL(FBirthDay,FRegDay) AS ImportDay
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBIRTHDAY FREGDAY IMPORTDAY
1981-03-22 00:00:00.0 1998-05-01 00:00:00.0 1981-03-22 00:00:00.0
1987-01-18 00:00:00.0 1999-08-21 00:00:00.0 1987-01-18 00:00:00.0
1987-11-08 00:00:00.0 2001-09-18 00:00:00.0 1987-11-08 00:00:00.0
1982-07-12 00:00:00.0 2000-03-01 00:00:00.0 1982-07-12 00:00:00.0
1983-02-16 00:00:00.0 1998-05-01 00:00:00.0 1983-02-16 00:00:00.0
<NULL> 1999-03-01 00:00:00.0 1999-03-01 00:00:00.0
<NULL> <NULL> <NULL>
1972-07-18 00:00:00.0 1995-06-19 00:00:00.0 1972-07-18 00:00:00.0
5.4.2.3 NULLIF()函數
主流數據庫都支持NULLIF()函數,這個函數的參數格式以下:
NULLIF ( expression1 , expression2 )
若是兩個表達式不等價,則 NULLIF 返回第一個 expression1的值。若是兩個表達式等
價,則 NULLIF 返回第一個 expression1類型的空值。也就是返回類型與第一個 expression 相
同。
下面的SQL演示了NULLIF()函數的用法:
SELECT FBirthDay,FRegDay,
NULLIF(FBirthDay,FRegDay)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FBirthDay FRegDay
1981-03-22 00:00:00.0 1998-05-01 00:00:00.0 1981-03-22 00:00:00.0
1987-01-18 00:00:00.0 1999-08-21 00:00:00.0 1987-01-18 00:00:00.0
1987-11-08 00:00:00.0 2001-09-18 00:00:00.0 1987-11-08 00:00:00.0
1982-07-12 00:00:00.0 2000-03-01 00:00:00.0 1982-07-12 00:00:00.0
1983-02-16 00:00:00.0 1998-05-01 00:00:00.0 1983-02-16 00:00:00.0
<NULL> 1999-03-01 00:00:00.0 <NULL>
<NULL> <NULL> <NULL>
1972-07-18 00:00:00.0 1995-06-19 00:00:00.0 1972-07-18 00:00:00.0
5.4.3 CASE函數
COALESCE()函數只能用來進行空值的邏輯判斷處理,若是要實現「若是年齡大於25則返
回姓名,不然返回別名」這樣的邏輯判斷就比較麻煩了。在主流數據庫系統中提供了CASE
函數的支持,嚴格意義上來說CASE函數已是流程控制語句了,不是簡單意義上的函數,
不過爲了方便,不少人都將CASE稱做「流程控制函數」。
CASE函數有兩種用法,下面分別介紹。
5.4.3.1 用法一
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
CASE函數的語法以下:
CASE expression
WHEN value1 THEN returnvalue1
WHEN value2 THEN returnvalue2
WHEN value3 THEN returnvalue3
……
ELSE defaultreturnvalue
END
CASE函數對錶達式expression進行測試,若是expression等於value1則返回returnvalue1,
若是expression等於value2則返回returnvalue2,expression等於value3則返回returnvalue3,……
以此類推,若是不符合全部的WHEN條件,則返回默認值defaultreturnvalue。
可見CASE函數和普通編程語言中的SWITCH……CASE語句很是相似。使用CASE函數咱們
能夠實現很是複雜的業務邏輯。下面的SQL用於判斷誰是「好孩子」,咱們比較偏心Tom和Lily,
因此咱們將他們認爲是好孩子,而咱們比較不喜歡Sam和Kerry,因此認爲他們是壞孩子,其
他孩子則爲普通孩子:
SELECT
FName,
(CASE FName
WHEN 'Tom' THEN 'GoodBoy'
WHEN 'Lily' THEN 'GoodGirl'
WHEN 'Sam' THEN 'BadBoy'
WHEN 'Kerry' THEN 'BadGirl'
ELSE 'Normal'
END) as isgood
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME ISGOOD
Tom GoodBoy
Jim Normal
Lily GoodGirl
Kelly Normal
Sam BadBoy
Kerry BadGirl
Smith Normal
BillGates Normal
CASE函數在製做報表的時候很是有用。好比表T_Customer中的FLevel字段是整數類型,
它記錄了客戶的級別,若是爲1則是VIP客戶,若是爲2則是高級客戶,若是爲3則是普通客戶,
在製做報表的時候顯然不該該把一、二、3這樣的數字顯示到報表中,而應該顯示相應的文字,
這裏就能夠使用CASE函數進行處理,SQL語句以下:
SELECT
FName,
(CASE FLevel
WHEN 1 THEN 'VIP客戶'
WHEN 2 THEN '高級客戶'
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
WHEN 3 THEN '普通客戶'
ELSE '客戶類型錯誤'
END) as FLevelName
FROM T_Customer
5.4.3.2 用法二
上邊一節中介紹的CASE語句的用法只能用來實現簡單的「等於」邏輯的判斷,要實現
「若是年齡小於18則返回‘未成年人’,不然返回‘成年人’」是沒法完成的。值得慶幸的是,
CASE函數還提供了第二種用法,其語法以下:
CASE
WHEN condi on1 THE N ret ur nval ue1
WHEN condi on 2 THEN ret ur nval ue2
WHEN condi on 3 THEN ret ur nval ue3
……
ELSE defaultreturnvalue
END
其中的condi on1 、condi on 2 、condi on 3 ……爲條件表達式,CASE函數對各個表達式
從前向後進行測試,若是條件condi on1 爲真則返回returnvalue1,不然若是條件condi on2
爲真則返回returnvalue2,不然若是條件condi on3 爲真則返回returnvalue3,……以此類推,
若是不符合全部的WHEN條件,則返回默認值defaultreturnvalue。
這種用法中沒有限制只能對一個表達式進行判斷,所以使用起來更加靈活。好比下面的
SQL語句用來判斷一我的的體重是否正常,若是體重小於40則認爲太瘦,而若是體重大於50
則認爲太胖,介於40和50之間則認爲是正常:
SELECT
FName,
FWeight,
(CASE
WHEN FWeight<40 THEN 'thin'
WHEN FWeight>50 THEN 'fat'
ELSE 'ok'
END) as isnormal
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FWEIGHT ISNORMAL
Tom 56.67 fat
Jim 36.17 thin
Lily 40.33 ok
Kelly 46.23 ok
Sam 48.68 ok
Kerry 66.67 fat
Smith 51.28 fat
BillGates 60.32 fat
5.5 各數據庫系統獨有函數
本節內容試讀版不提供。請購買《程序員的SQL金典》。
第十章 高級話題
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
本章將討論一些數據庫開發中的高級話題,包括SQL 注入漏洞攻防、數據庫調優、範
式等。
10.4 自動增加字段
在設計數據庫的時候,有時須要表的某個字段是自動增加的,最常使用自動增加字
段的就是表的主鍵,使用自動增加字段能夠簡化主鍵的生成。不一樣的DBMS 中自動增加
字段的實現機制也有不一樣,下面分別介紹。
10.4.1 MYSQL 中的自動增加字段
MYSQL 中設定一個字段爲自動增加字段很是簡單,只要在表定義中指定字段爲
AUTO_INCREMENT 便可。好比下面的SQL 語句建立T_Person 表,其中主鍵FId 爲自動
增加字段:
CREATE TABLE T_Person
(
FId INT PRIMARY KEY AUTO_INCREMENT,
FName VARCHAR(20),
FAge INT
);
執行上面的SQL 語句後就建立成功了T_Person 表,而後執行下面的SQL 語句向
T_Person 表中插入一些數據:
INSERT INTO T_Person(FName,FAge)
VALUES('Tom',18);
INSERT INTO T_Person(FName,FAge)
VALUES('Jim',81);
INSERT INTO T_Person(FName,FAge)
VALUES('Kerry',33);
注意這裏的INSERT 語句沒有爲FId 字段設定任何值,由於DBMS會自動爲FId 字段
設定值。執行完畢後查看T_Person 表中的內容:
FId FName FAge
1 Tom 18
2 Jim 81
3 Kerry 33
能夠看到FId 中確實是自動增加的。
這個例子講解完了,請刪除T_Person 表:
DROP TABLE T_Person;
10.4.2 MSSQLServer 中的自動增加字段
MSSQLServer 中設定一個字段爲自動增加字段非只要在表定義中指定字段爲
IDENTITY便可,格式爲IDENTITY(startvalue,step),其中的startvalue參數值爲起始數字,
step 參數值爲步長,即每次自動增加時增長的值。
好比下面的SQL 語句建立T_Person 表,其中主鍵FId 爲自動增加字段,而且設定
100 爲起始數字,步長爲3:
CREATE TABLE T_Person
(
FId INT PRIMARY KEY IDENTITY(100,3),
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FName VARCHAR(20),
FAge INT
);
執行上面的SQL 語句後就建立成功了T_Person 表,而後執行下面的SQL 語句向
T_Person 表中插入一些數據:
INSERT INTO T_Person(FName,FAge)
VALUES('Tom',18);
INSERT INTO T_Person(FName,FAge)
VALUES('Jim',81);
INSERT INTO T_Person(FName,FAge)
VALUES('Kerry',33);
注意這裏的INSERT 語句沒有爲FId 字段設定任何值,由於DBMS會自動爲FId 字段
設定值。執行完畢後查看T_Person 表中的內容:
FId FName FAge
100 Tom 18
103 Jim 81
106 Kerry 33
能夠看到FId 中確實是100爲起始數字、步長爲3 自動增加的。
這個例子講解完了,請刪除T_Person 表:
DROP TABLE T_Person;
10.4.3 Oracle中的自動增加字段
Oracle 中不像MYSQL 和MSSQLServer 中那樣指定一個列爲自動增加列的方式,不
過在Oracle中能夠經過SEQUENCE序列來實現自動增加字段。
在Oracle中SEQUENCE 被稱爲序列,每次取的時候它會自動增長,通常用在須要按
序列號排序的地方。
在使用SEQUENCE前須要首先定義一個SEQUENCE,定義SEQUENCE的語法以下:
CREATE SEQUENCE sequence_name
INCREMENT BY step
START WITH startvalue;
其中sequence_name 爲序列的名字,每一個序列都必須有惟一的名字;startvalue 參
數值爲起始數字,step 參數值爲步長,即每次自動增加時增長的值。
一旦定義了SEQUENCE,你就能夠用CURRVAL來取得SEQUENCE的當前值,也能夠
經過NEXTVAL來增長SEQUENCE,而後返回 新的SEQUENCE值。好比:
sequence_name.CURRVAL
sequence_name.NEXTVAL
若是SEQUENCE不須要的話就能夠將其刪除:
DROP SEQUENCE sequence_name;
下面舉一個使用SEQUENCE序列實現自動增加的例子。
首先建立一個名稱爲seq_PersonId 的SEQUENCE:
CREATE SEQUENCE seq_PersonId
INCREMENT BY 1
START WITH 1;
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
而後建立T_Person表:
CREATE TABLE T_Person
(
FId NUMBER (10) PRIMARY KEY,
FName VARCHAR2(20),
FAge NUMBER (10)
);
執行上面的SQL 語句後就建立成功了T_Person 表,而後執行下面的SQL 語句向
T_Person 表中插入一些數據:
INSERT INTO T_Person(FId,FName,FAge)
VALUES(seq_PersonId.NEXTVAL,'Tom',18);
INSERT INTO T_Person(FId,FName,FAge)
VALUES(seq_PersonId.NEXTVAL,'Jim',81);
INSERT INTO T_Person(FId,FName,FAge)
VALUES(seq_PersonId.NEXTVAL,'Kerry',33);
注意這裏的INSERT 語句沒有爲FId 字段設定任何值,由於DBMS會自動爲FId 字段
設定值。執行完畢後查看T_Person 表中的內容:
FID FNAME FAGE
1 Tom 18
2 Jim 81
3 Kerry 33
使用SEQUENCE 實現自動增加字段的缺點是每次向表中插入記錄的時候都要顯式
的到SEQUENCE中取得新的字段值,若是忘記了就會形成錯誤。爲了解決這個問題,我
們能夠使用觸發器來解決,建立一個T_Person表上的觸發器:
CREATE OR REPLACE TRIGGER trigger_personIdAutoInc
BEFORE INSERT ON T_Person
FOR EACH ROW
DECLARE
BEGIN
SELECT seq_PersonId.NEXTVAL INTO:NEW.FID FROM DUAL;
END trigger_personIdAutoInc;
這個觸發器在T_Person 中插入新記錄以前觸發,當觸發器被觸發後則從
seq_PersonId中取道新的序列號而後設置給FID字段。
執行下面的SQL語句向T_Person表中插入一些數據:
INSERT INTO T_Person(FAge)
VALUES('Wow',22);
INSERT INTO T_Person(FName,FAge)
VALUES('Herry',28);
INSERT INTO T_Person(FName,FAge)
VALUES('Gavin',36);
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
注意在這個SQL 語句中無需再爲FId 字段賦值。執行完畢後查看T_Person 表中的
內容:
FID FNAME FAGE
1 Tom 18
2 Jim 81
3 Kerry 33
4 Wow 22
5 Herry 28
7 Gavin 36
這個例子講解完了,請刪除T_Person表以及SEQUENCE:
DROP TABLE T_Person;
DROP SEQUENCE seq_PersonId;
10.4.4 DB2中的自動增加字段
DB2 中實現自動增加字段有兩種方式:定義帶有 IDENTITY 屬性的列;使用
SEQUENCE對象。
10.4.4.1定義帶有 IDENTITY 屬性的列
首先建立T_Person表,SQL語句以下:
CREATE TABLE T_Person
(
FId INT PRIMARY KEY NOT NULL
GENERATED ALWAYS
AS IDENTITY
(START WITH 1
INCREMENT BY 1
),
FName VARCHAR(20),
FAge INT
);
執行上面的SQL 語句後就建立成功了T_Person 表,而後執行下面的SQL 語句向
T_Person 表中插入一些數據:
INSERT INTO T_Person(FName,FAge)
VALUES('Tom',18);
INSERT INTO T_Person(FName,FAge)
VALUES('Jim',81);
INSERT INTO T_Person(FName,FAge)
VALUES('Kerry',33);
注意這裏的INSERT 語句沒有爲FId 字段設定任何值,由於DBMS會自動爲FId 字段
設定值。執行完畢後查看T_Person 表中的內容:
FId FName FAge
100 Tom 18
103 Jim 81
106 Kerry 33
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
這個例子講解完了,請刪除T_Person 表:
DROP TABLE T_Person;
10.4.4.2使用SEQUENCE對象
DB2中的SEQUENCE和Oracle中的SEQUENCE相同,只是定義方式和使用方式略有
不一樣。
下面建立了一個SEQUENCE:
CREATE SEQUENCE seq_PersonId AS INT
INCREMENT BY 1
START WITH 1;
使用SEQUENCE的方式以下:
NEXT VALUE FOR sequence_name
這樣就能夠經過下面的SQL語句來使用SEQUENCE:
INSERT INTO T_Person(FId,FName,FAge)
VALUES(NEXT VALUE FOR seq_PersonId,'Kerry',33);
若是想在向表中插入記錄的時候自動設定FId 字段的值則一樣要使用觸發器,具體請
參考相關資料,這裏再也不贅述。
這個例子講解完了,請刪除seq_PersonId序列:
DROP SEQUENCE seq_PersonId;
10.5 業務主鍵與邏輯主鍵
本節內容試讀版不提供。請購買《程序員的SQL 金典》。
10.6 NULL 的學問
本節內容試讀版不提供。請購買《程序員的SQL 金典》。
10.7 開窗函數
在開窗函數出現以前存在着不少用SQL 語句很難解決的問題,不少都要經過複雜的相
關子查詢或者存儲過程來完成。爲了解決這些問題,在2003年ISO SQL標準加入了開窗
函數,開窗函數的使用使得這些經典的難題能夠被輕鬆的解決。目前在MSSQLServer、
Oracle、DB2 等主流數據庫中都提供了對開窗函數的支持,不過很是遺憾的是MYSQL 暫
時還未對開窗函數給予支持,所以本節中的例子將沒法在MYSQL 中運行經過。
爲了更加清晰的講解開窗函數咱們將建立一張表,執行下面的SQL語句:
MYSQL,MSSQLServer,DB2:
CREATE TABLE T_Person (FName VARCHAR(20),FCity VARCHAR(20),
FAge INT,FSalary INT)
Oracle:
CREATE TABLE T_Person (FName VARCHAR2(20),FCity VARCHAR2(20),
FAge INT,FSalary INT)
T_Person 表保存了人員信息,FName 字段爲人員姓名,FCity 字段爲人員所在的城
市名,FAge 字段爲人員年齡,FSalary字段爲人員工資。請在相應的DBMS 中執行相應
的SQL語句,而後執行下面的SQL語句向T_Person表中插入一些演示數據:
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Tom','BeiJing',20,3000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Tim','ChengDu',21,4000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Jim','BeiJing',22,3500);
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Lily','London',21,2000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('John','NewYork',22,1000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('YaoMing','BeiJing',20,3000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Swing','London',22,2000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Guo','NewYork',20,2800);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('YuQian','BeiJing',24,8000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Ke y' ,'London' ,25, 8500) ;
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Ki y' ,'ChengDu' ,25, 3000) ;
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Merry','BeiJing',23,3500);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Smith','ChengDu',30,3000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Bill','BeiJing',25,2000);
INSERT INTO T_Person(FName,FCity,FAge,FSalary)
VALUES('Jerry','NewYork',24,3300);
執行完畢查看T_Person 表中的內容:
FNAME FCITY FAGE FSALARY
Tom BeiJing 20 3000
Tim ChengDu 21 4000
Jim BeiJing 22 3500
Lily London 21 2000
John NewYork 22 1000
YaoMing BeiJing 20 3000
Swing London 22 2000
Guo NewYork 20 2800
YuQian BeiJing 24 8000
Ke y London 2 5 8 500
Ki y C hengDu 2 5 3 000
Merry BeiJing 23 3500
Smith ChengDu 30 3000
Bill BeiJing 25 2000
Jerry NewYork 24 3300
10.7.1 開窗函數簡介
與聚合函數同樣,開窗函數也是對行集組進行聚合計算,可是它不像普通聚合函數
那樣每組只返回一個值,開窗函數能夠爲每組返回多個值,由於開窗函數所執行聚合計
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
算的行集組是窗口。在ISO SQL規定了這樣的函數爲開窗函數,在Oracle中則被稱爲分
析函數,而在DB2 中則被稱爲OLAP函數。
要計算全部人員的總數,咱們能夠執行下面的SQL語句:
SELECT COUNT(*) FROM T_Person
除了這種較簡單的使用方式,有時須要從不在聚合函數中的行中訪問這些聚合計算
的值。好比咱們想查詢每一個工資小於5000元的員工信息(城市以及年齡),而且在每行
中都顯示全部工資小於5000元的員工個數,嘗試編寫下面的SQL語句:
SELECT FCITY , FAGE , COUNT(*)
FROM T_Person
WHERE FSALARY<5000
執行上面的SQL之後咱們會獲得下面的錯誤信息:
選擇列表中的列'T_Person.FCity' 無效,由於該列沒有包含在聚合函數或GROUP BY 子句中。
這是由於全部不包含在聚合函數中的列必須聲明在GROUP BY 子句中,能夠進行以下修改:
SELECT FCITY, FAGE, COUNT(*)
FROM T_Person
WHERE FSALARY<5000
GROUP BY FCITY , FAGE
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FCITY FAGE
BeiJing 20 2
NewYork 20 1
ChengDu 21 1
London 21 1
BeiJing 22 1
London 22 1
NewYork 22 1
BeiJing 23 1
NewYork 24 1
BeiJing 25 1
ChengDu 25 1
ChengDu 30 1
這個執行結果與咱們想像的是徹底不一樣的,這是由於GROUP BY子句對結果集進行
了分組,因此聚合函數進行計算的對象再也不是全部的結果集,而是每個分組。
能夠經過子查詢來解決這個問題,SQL以下:
SELECT FCITY , FAGE ,
(
SELECT COUNT(* ) FROM T_Person
WHERE FSALARY<5000
)
FROM T_Person
WHERE FSALARY<5000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FCITY FAGE
BeiJing 20 13
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
ChengDu 21 13
BeiJing 22 13
London 21 13
NewYork 22 13
BeiJing 20 13
London 22 13
NewYork 20 13
ChengDu 25 13
BeiJing 23 13
ChengDu 30 13
BeiJing 25 13
NewYork 24 13
雖然使用子查詢可以解決這個問題,可是子查詢的使用很是麻煩,使用開窗函數則
能夠大大簡化實現,下面的SQL語句展現了若是使用開窗函數來實現一樣的效果:
SELECT FCITY , FAGE , COUNT(*) OVER()
FROM T_Person
WHERE FSALARY<5000
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FCITY FAGE
BeiJing 20 13
ChengDu 21 13
BeiJing 22 13
London 21 13
NewYork 22 13
BeiJing 20 13
London 22 13
NewYork 20 13
ChengDu 25 13
BeiJing 23 13
ChengDu 30 13
BeiJing 25 13
能夠看到與聚合函數不一樣的是,開窗函數在聚合函數後增長了一個OVER 關鍵字。
開窗函數的調用格式爲:
函數名(列) OVER(選項)
OVER 關鍵字表示把函數當成開窗函數而不是聚合函數。SQL 標準容許將全部聚合
函數用作開窗函數,使用OVER關鍵字來區分這兩種用法。
在上邊的例子中,開窗函數COUNT(*) OVER()對於查詢結果的每一行都返回全部
符合條件的行的條數。OVER關鍵字後的括號中還常常添加選項用以改變進行聚合運算的窗
口範圍。若是OVER關鍵字後的括號中的選項爲空,則開窗函數會對結果集中的全部行進行
聚合運算。
10.7.2 PARTITION BY 子句
開窗函數的OVER關鍵字後括號中的能夠使用PARTITION BY子句來定義行的分區來
供進行聚合計算。與GROUP BY 子句不一樣,PARTITION BY 子句建立的分區是獨立於結果
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
集的,建立的分區只是供進行聚合計算的,並且不一樣的開窗函數所建立的分區也不互相
影響。下面的SQL語句用於顯示每個人員的信息以及所屬城市的人員數:
SELECT FName,FCITY , FAGE , FSalary,
COUNT(*) OVER(PARTITION BY FCITY)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FCITY FAGE FSalary
Tom BeiJing 20 3000 6
Jim BeiJing 22 3500 6
YaoMing BeiJing 20 3000 6
YuQian BeiJing 24 8000 6
Merry BeiJing 23 3500 6
Bill BeiJing 25 2000 6
Smith ChengDu 30 3000 3
Ki y C hengDu 2 5 3 000 3
Tim ChengDu 21 4000 3
Lily London 21 2000 3
Ke y L ondon 2 5 8 500 3
Swing London 22 2000 3
Guo NewYork 20 2800 3
John NewYork 22 1000 3
Jerry NewYork 24 3300 3
COUNT(*) OVER(PARTITION BY FCITY)表示對結果集按照FCITY進行分區,而且計算
當前行所屬的組的聚合計算結果。好比對於FName等於Tom的行,它所屬的城市是BeiJing,同
屬於BeiJing的人員一共有6個,因此對於這一列的顯示結果爲6。
在同一個SELECT語句中能夠同時使用多個開窗函數,並且這些開窗函數並不會相互干擾。
好比下面的SQL語句用於顯示每個人員的信息、所屬城市的人員數以及同齡人的人數:
SELECT FName,FCITY, FAGE, FSalary,
COUNT(*) OVER(PARTITION BY FCITY),
COUNT(*) OVER(PARTITION BY FAGE)
FROM T_Person
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FName FCITY FAGE FSalary
Tom BeiJing 20 3000 6 3
YaoMing BeiJing 20 3000 6 3
Guo NewYork 20 2800 3 3
Tim ChengDu 21 4000 3 2
Lily London 21 2000 3 2
Jim BeiJing 22 3500 6 3
John NewYork 22 1000 3 3
Swing London 22 2000 3 3
Merry BeiJing 23 3500 6 1
YuQian BeiJing 24 8000 6 2
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Jerry NewYork 24 3300 3 2
Ki y C hengDu 2 5 3 000 3 3
Bill BeiJing 25 2000 6 3
Ke y L ondon 2 5 8 500 3 3
Smith ChengDu 30 3000 3 1
在這個查詢結果中,能夠看到同一城市中的COUNT(*) OVER(PARTITION BY FCITY)
計算結果相同,並且同齡人中的COUNT(*) OVER(PARTITION BY FAGE) 計算結果也相同。
10.7.2 ORDER BY子句
MSSQLServer中是不支持開窗函數中的ORDER BY子句的,所以本節演示的內容只適用於
Oracle和DB2。開窗函數中能夠在OVER關鍵字後的選項中使用ORDER BY子句來指定排序規則,
並且有的開窗函數還要求必須指定排序規則。使用ORDER BY子句能夠對結果集按照指定的
排序規則進行排序,而且在一個指定的範圍內進行聚合運算。ORDER BY子句的語法爲:
ORDER BY 字段名 RANGE|ROWS BETWEEN 邊界規則1 AND 邊界規則2
RANGE表示按照值的範圍進行範圍的定義,而ROWS表示按照行的範圍進行範圍的定義;
邊界規則的可取值見下表:
可取值 說明 示例
CURRENT ROW 當前行
N PRECEDING 前N行 2 PRECEDING
UNBOUNDED PRECEDING 一直到第一條記錄
N FOLLOWING 後N行 2 FOLLOWING
UNBOUNDED FOLLOWING 一直到最後一條記錄
「RANGE|ROWS BETWEEN 邊界規則1 AND 邊界規則2」部分用來定位聚合計算範圍,
這個子句又被稱爲定位框架。下面經過例子來展現ORDER BY子句的用法。
例1
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
John 1000 1000
Lily 2000 3000
Swing 2000 5000
Bill 2000 7000
Guo 2800 9800
Tom 3000 12800
YaoMing 3000 15800
Ki y 3 000 1 8800
Smith 3000 21800
Jerry 3300 25100
Jim 3500 28600
Merry 3500 32100
Tim 4000 36100
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
YuQian 8000 44100
Ke y 8 500 5 2600
這裏的開窗函數「SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN
UNBOUNDED PRECEDING AND CURRENT ROW)」表示按照FSalary進行排序,而後計算從第
一行(UNBOUNDED PRECEDING)到當前行(CURRENT ROW)的和,這樣的計算結果就是按照
工資進行排序的工資值的累積和。
例2
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FSalary RANGE BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
John 1000 1000
Lily 2000 7000
Swing 2000 7000
Bill 2000 7000
Guo 2800 9800
Tom 3000 21800
YaoMing 3000 21800
Ki y 3 000 2 1800
Smith 3000 21800
Jerry 3300 25100
Jim 3500 32100
Merry 3500 32100
Tim 4000 36100
YuQian 8000 44100
Ke y 8 500 5 2600
這個SQL語句與例1中的SQL語句惟一不一樣的就是「ROWS」被替換成了「RANGE」。「ROWS」
是按照行數進行範圍定位的,而「RANGE」則是按照值範圍進行定位的,這兩個不一樣的定位方式
主要用來處理並列排序的狀況。好比Lily、Swing、Bill這三我的的工資都是2000元,若是按照
「ROWS」進行範圍定位,則計算從第一條到當前行的累積和,而若是若是按照「RANGE」進行
範圍定位,則仍然計算從第一條到當前行的累積和,不過因爲等於2000元的工資有三我的,所
以計算的累積和爲從第一條到2000元工資的人員結,因此對Lily、Swing、Bill這三我的進行開
窗函數聚合計算的時候獲得的都是7000(「1000+2000+2000+2000」)。
例3
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 2 PRECEDING AND 2
FOLLOWING)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
John 1000 5000
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Lily 2000 7000
Swing 2000 9800
Bill 2000 11800
Guo 2800 12800
Tom 3000 13800
YaoMing 3000 14800
Ki y 3 000 1 5300
Smith 3000 15800
Jerry 3300 16300
Jim 3500 17300
Merry 3500 22300
Tim 4000 27500
YuQian 8000 24000
Ke y 8 500 2 0500
這裏的開窗函數「SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 2
PRECEDING AND 2 FOLLOWING)」表示按照FSalary進行排序,而後計算從當前行前兩行(2
PRECEDING)到當前行後兩行(2 FOLLOWING)的工資和,注意對於第一條和第二條而言它們
的「前兩行」是不存在或者不完整的,所以計算的時候也是要按照前兩行是不存在或者不完整進
行計算,一樣對於最後兩行數據而言它們的「後兩行」也不存在或者不完整的,一樣要進行相似
的處理。
例4
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 1 FOLLOWING AND 3
FOLLOWING)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
John 1000 6000
Lily 2000 6800
Swing 2000 7800
Bill 2000 8800
Guo 2800 9000
Tom 3000 9000
YaoMing 3000 9300
Ki y 3 000 9 800
Smith 3000 10300
Jerry 3300 11000
Jim 3500 15500
Merry 3500 20500
Tim 4000 16500
YuQian 8000 8500
Ke y 8 500 < NUL L>
這裏的開窗函數「SUM(FSalary) OVER(ORDER BY FSalary ROWS BETWEEN 1
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
FOLLOWING AND 3 FOLLOWING)」表示按照FSalary進行排序,而後計算從當前行後一行(1
FOLLOWING)到後三行(3 FOLLOWING)的工資和。注意最後一行沒有後續行,其計算結果爲
空值NULL而非0。
例5
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FName RANGE BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
Bill 2000 2000
Guo 2800 4800
Jerry 3300 8100
Jim 3500 11600
John 1000 12600
Ke y 8 500 2 1100
Ki y 3 000 2 4100
Lily 2000 26100
Merry 3500 29600
Smith 3000 32600
Swing 2000 34600
Tim 4000 38600
Tom 3000 41600
YaoMing 3000 44600
YuQian 8000 52600
這裏的開窗函數「SUM(FSalary) OVER(ORDER BY FName RANGE BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW)」表示按照FName進行排序,而後計算從第一行(UNBOUNDED
PRECEDING)到當前行(CURRENT ROW)的工資和。
「RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW」是開窗函數中最常
使用的定位框架,爲了簡化使用,若是使用的是這種定位框架,則能夠省略定位框架聲明部分,
也就是說「SUM(FSalary) OVER(ORDER BY FName RANGE BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW)」等價於「SUM(FSalary) OVER(ORDER BY FName)」,
因此這個SQL語句能夠簡寫爲:
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FName)
FROM T_Person;
例6
SELECT FName, FSalary,
COUNT(*) OVER(ORDER BY FSalary ROWS BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY 3
John 1000 1
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Lily 2000 2
Swing 2000 3
Bill 2000 4
Guo 2800 5
Tom 3000 6
YaoMing 3000 7
Ki y 3 000 8
Smith 3000 9
Jerry 3300 10
Jim 3500 11
Merry 3500 12
Tim 4000 13
YuQian 8000 14
Ke y 8 500 1 5
這裏的開窗函數「COUNT(*) OVER(ORDER BY FSalary RANGE BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW)」表示按照FSalary進行排序,而後計算從第一行
(UNBOUNDED PRECEDING)到當前行(CURRENT ROW)的人員的個數,這個能夠看做是計算
人員的工資水平排名。
例7
SELECT FName, FSalary,FAge,
MAX(FSalary) OVER(ORDER BY FAge)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY FAGE 4
Tom 3000 20 3000
YaoMing 3000 20 3000
Guo 2800 20 3000
Tim 4000 21 4000
Lily 2000 21 4000
Jim 3500 22 4000
John 1000 22 4000
Swing 2000 22 4000
Merry 3500 23 4000
YuQian 8000 24 8000
Jerry 3300 24 8000
Ke y 8 500 2 5 8 500
Ki y 3 000 2 5 8 500
Bill 2000 25 8500
Smith 3000 30 8500
這裏的開窗函數「MAX(FSalary) OVER(ORDER BY FAge)」是「MAX(FSalary)
OVER(ORDER BY FAge RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)」
的簡化寫法,它表示按照FSalary進行排序,而後計算從第一行(UNBOUNDED PRECEDING)
到當前行(CURRENT ROW)的人員的最大工資值。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
例8
SELECT FName, FSalary,FAge,
MAX(FSalary) OVER(PARTITION BY FAge ORDER BY FSalary)
FROM T_Person;
執行完畢咱們就能在輸出結果中看到下面的執行結果:
FNAME FSALARY FAGE 4
Guo 2800 20 2800
Tom 3000 20 3000
YaoMing 3000 20 3000
Lily 2000 21 2000
Tim 4000 21 4000
John 1000 22 1000
Swing 2000 22 2000
Jim 3500 22 3500
Merry 3500 23 3500
Jerry 3300 24 3300
YuQian 8000 24 8000
Bill 2000 25 2000
Ki y 3 000 2 5 3 000
Ke y 8 500 2 5 8 500
Smith 3000 30 3000
從這個例子能夠看出PARTITION BY子句和ORDER BY能夠共同使用,從而能夠實現更加
複雜的功能。
10.7.3 高級開窗函數
本節內容試讀版不提供。請購買《程序員的SQL金典》。
10.8 WITH子句與子查詢
子查詢能夠簡化SQL語句的編寫,不過若是使用不當的話子查詢會
下降系統性能,爲了不子查詢帶來的性能問題,除了須要優化SQL
語句以外還須要儘可能下降使用子查詢的次數。好比下面的子查詢用來
取得系統中全部年齡或者工資與Tom相同的人員:
SELECT * FROM T_Person
WHERE FAge=(SELECT FAge FROM T_Person WHERE FName='TOM')
OR FSalary=(SELECT FSalary FROM T_Person WHERE FName='TOM')
這個SQL語句能夠完成要求的功能,不過能夠看到相似的子查詢被用到了兩
次,這會帶來下面的問題:
l 同一個子查詢被使用屢次會形成這個子查詢被執行屢次,因爲子查詢是比較
消耗系統資源的操做,因此這會下降系統的性能。
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
l 同一個子查詢在多處被使用,這違反了編程中的DRY(Don't Repeat
Yourself)原則,若是要修改子查詢就必須對這些子查詢同時修改,很容
易形成修改不一樣步。
形成這種問題的緣由就是子查詢只能在定義的時候使用,這樣若是屢次使用
就必須屢次定義,爲了解決這種問題,SQL提供了WITH子句用於爲子查詢定義
一個別名,這樣就能夠經過這個別名來引用這個子查詢了,也就是實現「一次定
義屢次使用」。
使用WITH子句來改造上面的SQL語句:
WITH person_tom AS
(
SELECT * FROM T_Person
WHERE FName='TOM'
)
SELECT * FROM T_Person
WHERE FAge=person_tom.FAge
OR FSalary=person_tom.FSalary
能夠看到WITH子句的格式爲:
WITH 別名AS
(子查詢)
定義好別名之後就能夠在SQL語句中經過這個別名來引用子查詢了,注意這
個語句是一個SQL語句,而非存儲過程,因此能夠遠程調用。
還能夠在WITH語句中爲子查詢中的列定義別名,定義的方式就是在子查詢
別名後列出參數名列表。以下:
WITH person_tom(F1,F2,F3) AS
(
SELECT FAge,FName,FSalary FROM T_Person
WHERE FName='TOM'
)
SELECT * FROM T_Person
WHERE FAge=person_tom.F1
OR FSalary=person_tom.F3
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
Chinapub在線購買地址:h p: //www. chi na- pub. com/ 301651
噹噹網在線購買地址:h p: //pr oduct.dangdang. com/ pr oduct. aspx?pr oduct_i d=20368319
第一本專門爲程序員編寫的數據庫圖書
《程序員的SQL金典》
l 將子查詢、錶鏈接、數據庫語法差別等用通俗易懂、詼諧
幽默的語言講解出來
l 配合大量真實案例,學了就能用,在短期內成爲數據庫
開發高手
l 高度提取不一樣數據庫的共同點,仔細分析不一樣點,並給出
《程序員的SQL 金典》
第一本專門爲程序員編寫的數據庫圖書
解決方案,同時學會MSSQLServer、MYSQL、Oracle、DB2
數據庫再也不是夢
l 國內第一本講解開窗函數實際應用的圖書程序員

相關文章
相關標籤/搜索