扯淡
距上次接觸 Oracle 數據庫已經是 N 年前的事了,Oracle 的工作方式以及某些點很特別,那會就感覺,這貨就是一個奇葩!最近重拾記憶,一直在折騰 Oracle,因為 Oracle 與眾不同,所以,想在這兒記錄下 Oracle 不同於其他數據庫的一些地方以及使用 Oracle 過程遇上的點點滴滴,同時,也讓對 Oracle 陌生的同學有所了解。
導航
安裝與創建數據庫
Oracle 安裝文件從官網下載就行,安裝過程基本也就是一路點擊下一步的事,在這就不多說。
安裝完后,根據軟件尿性,一般會依賴一些服務,Oracle 會不會也有呢?我們打開服務: 
嘿,還真發現多出了幾個帶“Oracle”字眼的服務,雖然不知道是干嘛用的,但肯定很重要,我們知道有這么個服務就好。如果這些服務沒啟動,我們手動把它們都給啟動,反正啟動了准沒錯。
Oracle 創建數據庫有多種方式,我學了最簡單的一種。就是利用 Oracle 自帶的 Database Configuration Assistant 圖形界面工具:
輸入數據庫名稱后,一路下一步就好。
待數據庫創建完畢,我們看下Oracle給我們生成了什么樣的數據庫文件。如果用 Database Configuration Assistant 創建數據庫時沒有指定數據庫文件保存目錄,則默認保存在 oracle 安裝目錄下名為 oradata 的文件夾中。打開 oradata 文件夾:
我們可以看到,Oracle 會為我們創建的每個數據庫都用一個文件夾保存,點進去看下有什么文件:
生成了好幾個.DBF和.LOG后綴的文件,這些估計就是 Oracle 的主數據文件和日志文件了,作為小白,咱們熟悉就好。
同時,我們再轉到服務管理那,意外發現多出了新的服務:
這兩個服務名后綴就是我們創建的數據名,也不知道干嘛用的,我們盡管知道有這么回事就行啦,嘿嘿。
建好數據庫,我們就可以正式開啟 Oracle 之旅了。我是個對命令行極度恐懼的人,所以,我找了可視化界面工具 Navicat 連接 Oracle。Oracle 的基礎語法就不一一介紹了,網上隨便一搜就是一堆,接下來我只記錄一些 Oracle 與眾不同的基礎點。
Oracle 奇葩點
限定符
SqlServer 和 MySql 的限定符分別是 [] 和 ``,Oracle 的則是雙引號""。寫 sql 語句時,它們都支持不加限定符。但在 Oracle 中,我們不得不了解一點的是,如果在 sql 中對象名(表名/列名)不加雙引號,Oracle 在解析 sql 語句時會自動將對象名轉成大寫,同時加上雙引號限定起來。比如這么個 sql:select * from table,Oracle 在運行的時候會轉成 select * from "TABLE" 后再執行。又比如在創建表時 sql 是:create table MyTable..最終生成的表名是 MYTABLE,而不是 MyTable,因為沒加限定符自動轉大寫的原因;如果加了限定符如:create table "MyTable"..,這時候創建出來的表名就是 MyTable了。因為 Oracle 嚴格區分大小寫,所以,一定要理解加雙引號和不加雙引號的區別,很重要。
區分大小寫
Oracle 與其他數據庫不同,它對表名/列名嚴格區分大小寫。比如有一個名為 MyTable 的表,查詢 sql:select * from "myTable" 會無法執行,報表不存在的錯誤。Oracle 對 sql 語句要求很嚴謹,必須大小寫一致,所以將 sql 改成 select * from "MyTable" 就能成功執行。照我們的習慣,手寫 sql 的時候,都不會加限定符,也就是寫成:select * from MyTable,這樣也是不能成功執行的,因為 Oracle 自動轉成 select * from "MYTABLE" 后執行,顯然表 MYTABLE 是不存在的。也就是因為這點,通常做 Oracle 開發都有這么個潛規則:建表時,表名和字段全是大寫!這樣方便手寫 sql,同時也為了統一規范!Oracle 這個特性,對於用慣了 SqlServer 或 MySql 的程序員來說,的確好不適應。剛開始知道這么回事的時候,我心里一萬只羊駝路過...
字符類型比較大小寫
在 SqlServer 中,字符類型字段值比較是不區分大小寫的,但...萬惡的 Oracle 卻是區分大小寫!比如執行:select case when 'A'='a' then 1 else 0 end from dual;返回的是 0 而不是 1!
好吧,表名/列名區分大小寫我忍了,但這點...我真想爆粗口!
dual 表
dual 表在 Oracle 中是一張神奇的表,它真正的表名是大寫的 DUAL,但為了看得舒服,我們還是用 dual 小寫表示。
剛接觸 Oracle,熟悉了基本的語法后我們通常會去學習它的一些函數,如 upper、lower、trim、cast 這些,在 SqlServer 中我們可以這樣寫,select upper('a'),lower('A'),cast(1 as targettype),但在 Oracle 中,抱歉,這樣寫是不能直接執行的。Oracle 規定,任何 select 查詢語句都必須從一個表對象中查,顯然,select upper('a'),lower('A'),cast(1 as targettype) 這樣的寫法,在 Oracle 看來是語法上的錯誤,因為沒有指定查詢的表對象!所以我們得改成這樣:select upper('a'),lower('A'),cast(1 as targettype) from dual。其實我在想,orcale 你就不能智能點嗎?如果我們沒有寫 from xx 子句,你就默認使用 dual 表不就行了?
就不多吐槽了,我們還是看看 dual 是什么鬼東西吧!我們直接執行 select * from dual 看下里面有什么:
如上,我們看到 dual 其實也就是一張“不同尋常”的常規表而已,只不過它只包含一個字段和一行數據,這樣,我們就不難理解,為什么 select upper('a'),lower('A'),cast(1 as targettype) from dual 可以查出來數據了。
其實,我們也可以自定義一個只有一個字段和一行數據表,完全可以做到代替 dual 表的作用,但是,oracle 自身已經幫我們建好了這么個 dual 表,我們在建就完全沒必要了。但為什么說 dual “不同尋常”呢?既然是一個表,我們是不是可以對這個表進行增刪改呢?嘻嘻。我們試着插入一條數據:insert into dual(DUMMY) values('Y'): 
權限不足,哈哈,看來 Orcale 是對這個表進行了管制的。好吧,我們知道怎么用這個表就行了,更多有關 dual 的信息我們不用太關心(其實我很想知道 oracle 建這么一個表的時候取名 dual, 這個詞翻譯起來是啥意思,一直不明白- -)。
不能執行多條sql
眾多數據都支持批量 sql 執行,但它就是不支持,說 Oracle 奇葩一點也不為過!可能有同學會問,我在 pl/sql 或 Navicat 里為什么可以一次執行多條語句?那是因為我們在寫 sql 的時候用分號隔開,pl/sql 或 Navicat 自動幫我們拆開,實際上也是一條一條的發送給 Oracle 執行。
sys_guid 函數
Oracle 類型字典中是沒有 GUID 類型,但它提供了一個生成 guid 的函數:sys_guid。但我想說的是,這函數並沒什么X用!因為它返回的是二進制類型!我們設計表的時候總不能用二進制做主鍵吧!同時,我用的 Oracle 訪問驅動是 Oracle.ManagedDataAccess,這個驅動的 DataReader 根本也不支持 GetGuid。
number 類型
在 Oracle 中 number 類型有點“廣”,我們可以理解所有數值類型都可以用 number 表示。但有些東西還是適當了解比較好。如果一個 number 類型字段未指定精度,Oracle.ManagedDataAccess 驅動 DataReader 獲得的數據類型將都會是 decimal 類型。雖然也可以調用 GetInt32、GetInt64 等方法獲取值,但會有數據轉換的損耗。因此,我們設計表的時候,對於數值類型,我們最好還是適當的指定精度為好。因為,指定了精度,Oracle.ManagedDataAccess 驅動會根據精度使用相應的C#類型存儲,例如,如果一個字段類型是 NUMBER(9,0),Oracle 驅動會使用 int 類型存儲值,如果一個字段類型是 NUMBER(4,0),Oracle 驅動會使用 Int16 類型存儲值。從下面輸出我們可以看出:
同時,指定了精度,我想估計也會讓數據庫節省一定的空間,總之,如果做 Oracle 開發,我建議要養成指定精度的習慣。
自增實現
SqlServer 和 MySql 都有自增標識列,很方便,Oracle 不支持(聽說新版Oracle開始支持了)。如果想要實現自增,得利用序列實現。我給 Chloe.ORM 擴展的 Oracle Provider 支持序列,但有個不好的點,就是每次插入數據的時候,得先從數據庫將序列值查出來,然后再插入真實數據表,這相對 SqlServer 就多了一次數據庫交互,多少有點不爽。
時間類型
Oracle 時間類型有 date 和 timestamp 兩種類型。
Oracle 的 date 類型與 SqlServer 的 date 類型不同。SqlServer 的 date 類型精度精確到日,屬真正意義的日期,而 Oracle 的 date 類型是精確到秒,可以說是時間類型了,不算日期了。不過,Oracle 這個奇葩,它有一個精確到毫秒級別的 timestamp 類型!但它與 date 類型有區別的:
1.如果我們想用to_char函數獲取一個時間的毫秒部分,通常我們寫成這樣to_char(date,'ff3'),如果一個字段是 date 類型就不支持,因為 date 類型只精確到秒。然而 timestamp 類型可以 to_char(timestamp,'ff3') 這樣提取毫秒數。
2.兩個 date 類型的數據相減返回的是一個number類型的天數(這個非常好,SqlServer 和 MySql 都不支持),而兩個 timestamp 類型相減,則返回的是 Oracle 一個特別的時間戳類型INTERVAL DAY TO SECOND,我真的醉了- -。返回INTERVAL DAY TO SECOND類型,我們真心沒法用啊...因為我在給 Chloe.ORM 開發 Oracle Provider,需要支持求兩個日期相差的天數/小時數/分鍾數/秒數/毫秒數,我真不知道怎么從INTERVAL DAY TO SECOND里提取出來!最后只能用一個變通的方式給支持了,但多少感覺 Oracle 這設計好無語。順便吐槽下 Navicat,在 Navicat 查詢器里不支持 TO_TIMESTAMP 函數(Oracle 是支持的),坑了我等小白好幾天!
ROWNUM 理解
Oracle 分頁可以用類似 SqlServer 的 ROW_NUMBER() 函數,也可以用 Oracle 特有的 ROWNUM 分頁。通常,我們都是用 ROWNUM 方式,畢竟這是 Oracle 推薦語法。
ROWNUM 並不是真實存在的一個列,它是執行查詢時 Oracle 動態給查詢的每一行加上的一個偽列。我們必須深入理解才能用好它。既然 ROWNUM 列並不是真實存在表中,那它什么時候被定義呢?一條完整的查詢 sql 語句由5部分組成(select、from 、where、group、order),執行時,這5部分是有先后順序的,即從 from 數據集中掃描每行數據-->where條件過濾-->group-->order-->select,Oracle 在掃描數據集的時候每掃描到一行就給其編號(從1開始),即設置 ROWNUM,給行編號動作是在 where 條件過濾之前,編號后才進行 where 過濾,所以,我們寫 sql 的時候,可以在 where 條件里可以用 ROWNUM 進行過濾,如 where ROWNUM<10 等。
這相信不難理解,但有一點我們必須要知道的是,Oracle 每掃描到一行就給那行編號,假設是10,然后進行 where 條件過濾,如果不符合 where 條件的話,這條數據就會被拋棄,然后繼續掃描下一行數據,但給下一行數據編的號還是上行不符合條件數據的編號,即還是10,以此類推,所以就保證了僅經過 where 條件過濾的結果集中的編號都是從1開始且有順序的。只要我們理解了 ROWNUM 的生成機制,就好寫 sql 了。
為了加深理解我對 ROWNUM 機制闡述,我們先來分析一個簡單的 sql:select * from users where rownum>1,這個 sql 在 Oracle 里運行是永遠的得不到結果的,因為 Oracle 從表 users 里掃描的第一行數據時,給其編號1,然后經 rownum>1 判斷過濾不符合條件,所以第一行數據被拋棄,然后掃面第二行數據,此時第二行數據被編的號還是1,再經 rownum>1 判斷過濾還是不符合,以此類推,所以就永遠也查不出任何數據了。因此,在 where 條件中如果需要進行 rownum 過濾,只能是用 <、<= 條件。
鑒於 ROWNUM 的生成機制,所以,我們用 ROWNUM 分頁的時候,必須用嵌套的方式實現,即類似這樣的寫法:
select * from ( select T.*, ROWNUM RN from (select * from MYTABLE order by id desc) T where ROWNUM <= 40 ) where RN >= 21
位運算
Oracle 支持與運算(BITAND函數),但不支持或運算,我...@#¥%^&*!...(此處省略一萬只羊駝)
結語
Oracle 在數據庫行列中算是奇葩的一個,給我的感覺就是用戶體驗差!對它真沒啥好感- -。最后的最后,給大家留個小禮物,分享一個求兩個時間差的函數給大家:
create or replace function DATETIMEDIFF ( inDateTime1 in TIMESTAMP, inDateTime2 in TIMESTAMP, inInterval in VARCHAR -- d/h/m/s/ms ) return NUMBER as ret NUMBER; diffMillisecond NUMBER; intervalDivisor NUMBER; begin diffMillisecond := (cast(inDateTime1 as date)-cast(inDateTime2 as date)) * 24 * 60 * 60 * 1000 + cast(to_char(inDateTime1,'ff3') as number) - cast(to_char(inDateTime2,'ff3') as number); intervalDivisor := (case inInterval when 'd' then (24 * 60 * 60 * 1000) when 'h' then (60 * 60 * 1000) when 'm' then (60 * 1000) when 's' then 1000 when 'ms
' then 1 else 0 end); -- 以 0 作除數,引發異常 ret :=diffMillisecond/intervalDivisor; return ret; end;
我對 Oracle 了解不是很深,這個求時間差方式估計不是最好的,但勉強能求出兩個時間差。如哪位同學有更好的想法,望指點分享,thx!







