.NET程序員細數Oracle不同凡響的那些奇葩點

扯淡

距上次接觸 Oracle 數據庫已是 N 年前的事了,Oracle 的工做方式以及某些點很特別,那會就感受,這貨就是一個奇葩!最近重拾記憶,一直在折騰 Oracle,由於 Oracle 不同凡響,因此,想在這兒記錄下 Oracle 不一樣於其餘數據庫的一些地方以及使用 Oracle 過程趕上的點點滴滴,同時,也讓對 Oracle 陌生的同窗有所瞭解。程序員

導航

 

安裝與建立數據庫

Oracle 安裝文件從官網下載就行,安裝過程基本也就是一路點擊下一步的事,在這就很少說。
安裝完後,根據軟件尿性,通常會依賴一些服務,Oracle 會不會也有呢?咱們打開服務:
oracleservicesql

嘿,還真發現多出了幾個帶「Oracle」字眼的服務,雖然不知道是幹嗎用的,但確定很重要,咱們知道有這麼個服務就好。若是這些服務沒啓動,咱們手動把它們都給啓動,反正啓動了準沒錯。
Oracle 建立數據庫有多種方式,我學了最簡單的一種。就是利用 Oracle 自帶的 Database Configuration Assistant 圖形界面工具:
數據庫

dbCA

GU}AROA$XEM`T9IH_$PX`CB

輸入數據庫名稱後,一路下一步就好。
待數據庫建立完畢,咱們看下Oracle給咱們生成了什麼樣的數據庫文件。若是用 Database Configuration Assistant 建立數據庫時沒有指定數據庫文件保存目錄,則默認保存在 oracle 安裝目錄下名爲 oradata 的文件夾中。打開 oradata 文件夾:
oracle

chloeDBdir

咱們能夠看到,Oracle 會爲咱們建立的每一個數據庫都用一個文件夾保存,點進去看下有什麼文件:ide

dbf

生成了好幾個.DBF和.LOG後綴的文件,這些估計就是 Oracle 的主數據文件和日誌文件了,做爲小白,我們熟悉就好。
同時,咱們再轉到服務管理那,意外發現多出了新的服務:
函數

chloeservice

這兩個服務名後綴就是咱們建立的數據名,也不知道幹嗎用的,咱們儘管知道有這麼回事就行啦,嘿嘿。
建好數據庫,咱們就能夠正式開啓 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 的程序員來講,的確好不適應。剛開始知道這麼回事的時候,我內心一萬隻羊駝路過...ui

字符類型比較大小寫

在 SqlServer 中,字符類型字段值比較是不區分大小寫的,但...萬惡的 Oracle 倒是區分大小寫!好比執行:select case when 'A'='a' then 1 else 0 end from dual;返回的是 0 而不是 1!
好吧,表名/列名區分大小寫我忍了,但這點...我真想爆粗口!
spa

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 看下里面有什麼:

image

如上,咱們看到 dual 其實也就是一張「不一樣尋常」的常規表而已,只不過它只包含一個字段和一行數據,這樣,咱們就不難理解,爲何 select upper('a'),lower('A'),cast(1 as targettype) from dual 能夠查出來數據了。
其實,咱們也能夠自定義一個只有一個字段和一行數據表,徹底能夠作到代替 dual 表的做用,可是,oracle 自身已經幫咱們建好了這麼個 dual 表,咱們在建就徹底不必了。但爲何說 dual 「不一樣尋常」呢?既然是一個表,咱們是否是能夠對這個表進行增刪改呢?嘻嘻。咱們試着插入一條數據:insert into dual(DUMMY) values('Y'):

image

權限不足,哈哈,看來 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 類型。雖然也能夠調用 GetInt3二、GetInt64 等方法獲取值,但會有數據轉換的損耗。所以,咱們設計表的時候,對於數值類型,咱們最好仍是適當的指定精度爲好。由於,指定了精度,Oracle.ManagedDataAccess 驅動會根據精度使用相應的C#類型存儲,例如,若是一個字段類型是 NUMBER(9,0),Oracle 驅動會使用 int 類型存儲值,若是一個字段類型是 NUMBER(4,0),Oracle 驅動會使用 Int16 類型存儲值。從下面輸出咱們能夠看出:

image

同時,指定了精度,我想估計也會讓數據庫節省必定的空間,總之,若是作 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!

相關文章
相關標籤/搜索