2009年3月12日 星期四

[C#] 在 Oracle 上一次執行多個 SQL 命令

一般在 ADO.NET 的批次處理部份,都是在 DataSet 進行離線的處理完後,透過 SqlDataAdapter(或是 OracleDataAdapter) 搭配 SqlCommandBuilder(或是 OracleCommandBuilder) 來做!

若是對同一個 table 進行大量的 insert,也可以改用 bind parameter array 的方式進行批次新增

但如果要處理的多個指令都不太一樣呢? 有 update、insert、delete 這時要怎麼辦?

每個 SQL 都要重新產生 connection 物件或是 command 物件真是成本太大了。因此,若是使用的資料庫為 Oracle (MS SQL Server 不確定能不能這樣做),可以將所有的 SQL 語法組合成以下的樣子:
BEGIN

第一個 SQL 命令;

第二個 SQL 命令;

第三個 SQL 命令;

.......

.......

END;

最後在 END 之後也是要補一個分號。

當所有 SQL 語法組合而成上面的格式後,就可以透過一個 command 與一個 connection 一次將 SQL 命令丟給資料庫去作囉!


參考資料

MSDN Library >> Performing Batch Operations Using DataAdapters (ADO.NET) (中文版)

ADO.Net實現Oracle大批量數據的更新優化 - 中國自學編程網

oracle 一次執行多個sql的問題 - 白虎的地盤 - CSDNBlog

2009年2月25日 星期三

資料庫連線字串大全

在網路上偶然看到以下這個網站:

ConnectionStrings.com - Forgot that connection string? Get it here!

大概想的到的資料庫都在裡面了......(當然,想不到的也有一大堆)

以後忘記連線字串怎麼寫就可以連進去參考囉!

當然,也可以透過 ConnectionStringBuilder 類別來產生......

2009年2月20日 星期五

[JavaScript] expando 屬性的定義與使用

最近在讀 Learning Jquery 時,看見 JavaScript expando,是個蠻有意思的東西,可以自行擴充 JavaScript object 的屬性。

而由於這些屬性是放置於 memory 中,因此使用起來速度較快,不過也要記得釋放,免得出現 memory leak 的問題。

在網路上查詢到以下的範例(內容有稍作修改),說明 JavaScript expando 的使用方式:
<html>
<head>
<script type="text/javascript">
<!--
var myObj = new Object();
alert(myObj.toString()); //出現 "[object Object]"

//設定 expando 屬性
myObj.myExpando = "Hello Expando";
myObj.toString = function(){ //override toString() method
return this.myExpando;
}
alert(myObj.toString()); //出現 "Hello Expando"

//設定 expando 屬性
myObj.reportTime = function(){
alert(new Date());
}
myObj.reportTime(); //出現目前的日期與時間

//刪除 expando 屬性
delete myObj.reportTime;

//再一次嘗試呼叫已經刪除的 expando 屬性
try {
myObj.reportTime();
} catch(e) {
alert(e.message); //出現 "myObj.reportTime is not a function"
}
-->
</script>
</head>
<body>
</body>
</html>



參考資料

2009年2月16日 星期一

[SQL Server] 減少 Transaction Log 所佔的硬碟容量

今天同事在問,之前也沒做過,所以上網找了一下資料,發現以下兩個不錯的連結說明:

Brad's MIS Note: 交易紀錄檔 Transaction Log 管理

數據庫日常維護(參考)

2009年2月12日 星期四

[jQuery] jQuery + AJAX

AJAX

AJAX,這個技術是透過 JavaScript 中的 XMLHttpRequest 物件去呼叫後端 server code 並動態改變前端的網頁,讓網頁不需要 refresh。

AJAX 的技術並不複雜,大概掌握幾個重點即可:
  1. XMLHttpRequest 物件將資料傳遞到後端 server 的方式,可分為 POST 與 GET。

  2. 從 server 回傳的資料可以是 plain text 或是 XML。

  3. 前端網頁 DOM 的處理........(這其實跟 AJAX 已經沒有太大關係了)



jQuery 與 AJAX 的搭配


直接寫 pure XMLHttpRequest 的程式碼真的太累了,光是 cross browser 的問題就可以搞死人了......不過還好 jQuery 都幫大家處理好啦!

以下用的範例程式來說明:(demo)

ajax01.html
<html>
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
//取得 button 物件並註冊 click 事件
$("#btnAJAX").click(function(){
//取得使用者輸入的值
var intValue = $("#txtValue").val();

/* 以 POST 的方式送出 request
傳入參數 : value
參數值 = intValue
callback function 為 function(result){...} */

$.post("server01.php", {value : intValue}, function(result){
$("#spnMain").html("server 端回傳結果 = " + result);
});
});
});
-->
</script>
</head>
<body>
請輸入整數值,server 會計算其平方值並回傳:<p />
<input type="text" id="txtValue" /> <input type="button" id="btnAJAX" value="從 server 端取得結果" /><p />
<span id="spnMain"></span>
</body>
</html>

server01.php
<?php
if(isset($_POST["value"]))
echo $_POST["value"] * $_POST["value"];
else
echo "0";
?>

從上面的範例可以看出,透過 jQuery 物件的 post() 方法就可以直接享受到 AJAX 的好處囉! 當然,也可以用 get() 的方式來達到 AJAX 的效果。

此外,callback function 的部份比較需要注意的就是參數的傳遞了,在 jQuery 中可以允許使用一般的方式或是 context 的方式傳入參數,以下用範例說明:(demo)

ajax02.html
<html>
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
//測試函式
function testFunc(intVal) {
alert("final value is " + (intVal * 2 + 10));
} //end function


$(document).ready(function(){
//取得 button 物件並註冊 click 事件
$("#btnAJAX").click(function(){
//取得使用者輸入的值
var intValue = $("#txtValue").val();

/* 以 POST 的方式送出 request
傳入參數 : value
參數值 = intValue
callback function 為 function(result){...} */

$.post("server.php", {value : intValue}, function(result){
//直接呼叫 function
testFunc(result);

//使用 jQuery context 的方式呼叫 function
testFunc.apply(this, [result]);
});
});
});
-->
</script>
</head>
<body>
請輸入整數值,server 會計算其平方值並回傳:<p />
<input type="text" id="txtValue" /> <input type="button" id="btnAJAX" value="從 server 端取得結果" /><p />
<span id="spnMain"></span>
</body>
</html>


參考資料

[jQuery] jQuery 初體驗

第一支 jQuery 程式

很久之前就聽過 jQuery 的大名啦!

所以來試試看這個 JavaScript Framwork 到底厲害之處在哪裡..........

之前在查詢 JavaScript framework 的時候就有大概瞭解到,jQuery 的強項在於 DOM 的處理,據用過的人說,jQuery 處理 DOM 可是又快又簡單,是否真的有這麼神奇呢?

官網 tutorial 中的第一隻程式就有一些 DOM 的簡單應用:
<html>
<head>

<script type="text/javascript" src="js/jquery-1.3.1.js"></script>

<script type="text/javascript">
<!--
$(document).ready(function(){
/* 預設按下超連結都會執行以下的效果 */
$("a").click(function(event){
alert("Thanks for visiting!");

//透過 event 的 preventDefault() 方法,停止原本要做的動作(網頁導向指定頁面)
event.preventDefault();

//將此 object 隱藏(有慢慢隱藏的效果)
$(this).hide("slow");
});

//幫每一個 hyperlink 都加上 css 效果(對應到上方程式中定義的粗體)
$("a").addClass("test");

/* 由於 jQuery 的 function 都會回傳原本的 query object,因此可以將程式串在一起執行
因此以下程式執行的工作為:
1、按下超連結時,搜尋 class 名稱���否為 "clickme",若���,執行以下工作:
1.1 顯示 "Thanks for visiting!" 訊息 (上面已經定義過的動作會照樣進行)
1.2 開始慢慢消失
1.3 顯示 "you are leaving the site." 訊息
1.4 由於顯示訊息的關係(花了點時間),會讓超連結一瞬間消失(其實它���慢慢消失的)
2、使用 end() 方法,回到開始進行 query 的 document object(原本��� hyperlink object)
3、按下超連結時,搜尋 class 名稱���否為 "hideme",若���,執行以下工作:
3.1 顯示 "Thanks for visiting!" 訊息 (上面已經定義過的動作會照樣進行)
3.2 開始慢慢消失(這裡不寫 hide 也行,因為上面已經用過)
3.3 回傳 false
4、最後又使用 end() 方法,回到開始進行 query 的 document object
*/

$("a").filter(".clickme").click(function(){
alert("you are leaving the site.");
}).end().filter(".hideme").click(function(){
$(this).hide();
return false;
}).end();
});
-->
</script>

<style type="text/css">
a.test { font-weight: bold; }
</style>

</head>
<body>
<a href="http://jquery.com/">jQuery</a>
<p />
<a href="http://google.com/" class="clickme">I give a message when you leave</a>
<p />
<a href="http://yahoo.com/" class="hideme">Click me to hide!</a>
<p />
<a href="http://microsoft.com">I'm a normal link</a>
</body>
</html>



Event Regist
<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
alert("document is ready!");

/* $ 符號代表的意義為 jQuery class
$("a") 屬於 jQuery selector
並註冊了 click 的事件(事件的註冊會累計執行) */

$("a").click(function(){
alert("Hello World!");
});
/* 透過此種方式進行 event regist
就不用在每個 element 中撰寫類似「<a href="" onclick="alert('Hello world')">Link</a>」的語法了
這樣最大的優點是:「HTML、JavaScript、CSS 都可以徹底的分離了!」 */

-->
</script>
</head>
<body>
<a href="">Link</a>
</body>
</html>
使用 selector $("元素名稱") 選擇特定的 element,並進行 event regist。 (範例中註冊的是 click 事件)

透過 jQuery 的 event regist 方式,就可以作到將 HTML、JavaScript、CSS 三個徹底分離,讓後續管理更簡單方便。



Selector 的應用
<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
//尋找 ID 為 orderedlist 的 element,並增加 css 效果
$("#orderedlist").addClass("red");

//尋找 orderedlist 的 li element,並增加 css 效果
$("#orderedlist > li").addClass("blue");

//尋找 orderlist 中最後一個 li element,並註冊 hover 事件,指定發生的 css 效果
$("#orderedlist li:last").hover(function(){
$(this).addClass("green");
}, function(){
$(this).removeClass("green");
});

/* 尋找 ID 為 orderedlist 的 element
並在其所有的 li element 加上字串
而「$("#orderedlist").find("li")」的效果等同「$("#orderedlist > li")」*/

$("#orderedlist").find("li").each(function(i){
$(this).append(" BAM! " + i);
});
});
-->
</script>

</head>
<body>
<ol id="orderedlist">
<li>First element</li>
<li>Second element</li>
<li>Third element</li>
</ol>
</body>
</html>
  1. 使用 css + xpath 結合的方式尋找 element,例如範例中的「$("#orderedlist > li")」以及「$("#orderedlist li:last")」

  2. 使用 each 在每個 element 後方在另外加上字串

<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
//尋找不包含 ul 的 li element,並指定 css 效果
$("li").not(":has(ul)").css("border", "1px solid black");
});
-->
</script>

</head>
<body>
<ol id="orderedlist">
<li>First element</li>
<li>Second element</li>
<li>Third element</li>
</ol>

<ol id="orderedlist2">
<li>First element, second list</li>
<li>Second element, second list</li>
<li>Third element, second list</li>
<li>Li with child ul
<ul>
<li>Child One</li>
<li>child two</li>
</ul>
</li>
</ol>
</body>
</html>
  1. 透過 not(expr) 與 :has(selector) 的搭配,取得不包含 ul 的 li element

  2. 使用 css(name, value) 指定 css 效果

<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
$("a[name]").css("background-color", "#bbb");
});
-->
</script>

</head>
<body>
<a href="">no name attribute</a>
<p />
<a name="test" href="">has name attribute</a>
</body>
</html>
透過 element[attribute_name] 的方式取得擁有特定 attribute 的元素

<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
/* 一連串的動作,說明如下:
1、尋找 ID 為 FAQ 的 element
2、尋找該 element 下的 dd element,並將其隱藏
3、使用 end() 回到 faq element 狀態
4、繼續尋找 dt element,並進行 event regist
5、設定 click 後,會讓 dt element 的下一個 element 進行 slideToggle() 的動作 */

$("#faq").find('dd').hide().end().find('dt').click(function(){
$(this).next().slideToggle();
});
//使用 end() 的優點在於 $("#faq") 僅會發生一次 DOM select
});
-->
</script>

</head>
<body>
<h3>Bird FAQ - click the questions to show the answers</h3>
<dl id="faq">
<dt>What shouldn't I do to the bird?</dt>
<dd>Never use oils or lotions which contain oils on your bird. They gunk up the feathers,
and ruin their insulating properties. This means a chilled bird. Never wait out a cat bite--those
require immediate veterinary attention--a bird can die within two days because a cat'
s mouth is so
filthy and full of bacteria. Don't bother with over-the-counter medication. It really doesn't work,
and in some cases, may upset the delicate bacterial balance in the bird's body, or even worsen the
situation. Never try to treat a fracture at home.</dd>

<dt>My bird is healthy. I don'
t need to go to a vet, do I?</dt>
<dd>Schedule a "well-bird" checkup. Prevention is the best medicine. Even though the bird might appear outwardly healthy, it may have a low-grade infection or something not so readily apparent. Your bird's health and your peace of mind will be worth it.</dd>

<dt>My bird'
s leg is being rubbed raw by the leg band. Can I take it off?</dt>
<dd>No. Don't attempt this, especially if the leg is broken or swollen. The vet will be able
to remove the band, and deal with whatever injury maybe lurking under the banded area.</dd>

<dt>How do I pull a broken blood feather?</dt>
<dd>This is probably the most common mishap. The remedy is simple--yank! It'
s most easily done
with two people. One to restrain the bird and the other to pull the feather. Use pliers, or a
hemostat. Tweezers won't work on primaries. Make certain that the wing bones are firmly supported
or you can break the wing. Clamp onto the feather and give a sharp tug in the direction of the
feather. The feather will come out. Next, apply gentle, direct pressure to the follicle where the
feather was to stop the bleeding. Dab some styptic powder on it, as it will help stop the bleeding
as well. Let the bird rest. Ask your vet or breeder to demonstrate exactly how to pull a blood
feather if you'
re apprehensive about doing it.</dd>
</dl>
</body>
</html>
  1. 透過 next() 可以取得 next sibling node

  2. 使用 end() 的優點在於 $("#faq") 的 DOM select 動作只會作一次,因此可以增加程式執行效能

  3. slideToggle() 就只是滑動的效果而已,不過官方文件中還可以另外指定 speed 跟 callback function

<html>                                                               
<head>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<script type="text/javascript">
<!--
$(document).ready(function(){
/* 一連串的動作,說明如下:
1、尋找 ID 為 FAQ 的 element
2、尋找該 element 下的 dd element,並將其隱藏
3、使用 end() 回到 faq element 狀態
4、繼續尋找 dt element,並進行 event regist
5、設定 click 後,會讓 dt element 的下一個 element 進行 slideToggle() 的動作 */

$("#faq").find('dd').hide().end().find('dt').click(function(){
$(this).next().slideToggle();
});
//使用 end() 的優點在於 $("#faq") 僅會發生一次 DOM select
});
-->
</script>

</head>
<body>
<h3>Bird FAQ - click the questions to show the answers</h3>
<dl id="faq">
<dt>What shouldn't I do to the bird?</dt>
<dd>Never use oils or lotions which contain oils on your bird. They gunk up the feathers,
and ruin their insulating properties. This means a chilled bird. Never wait out a cat bite--those
require immediate veterinary attention--a bird can die within two days because a cat'
s mouth is so
filthy and full of bacteria. Don't bother with over-the-counter medication. It really doesn't work,
and in some cases, may upset the delicate bacterial balance in the bird's body, or even worsen the
situation. Never try to treat a fracture at home.</dd>

<dt>My bird is healthy. I don'
t need to go to a vet, do I?</dt>
<dd>Schedule a "well-bird" checkup. Prevention is the best medicine. Even though the bird might appear outwardly healthy, it may have a low-grade infection or something not so readily apparent. Your bird's health and your peace of mind will be worth it.</dd>

<dt>My bird'
s leg is being rubbed raw by the leg band. Can I take it off?</dt>
<dd>No. Don't attempt this, especially if the leg is broken or swollen. The vet will be able
to remove the band, and deal with whatever injury maybe lurking under the banded area.</dd>

<dt>How do I pull a broken blood feather?</dt>
<dd>This is probably the most common mishap. The remedy is simple--yank! It'
s most easily done
with two people. One to restrain the bird and the other to pull the feather. Use pliers, or a
hemostat. Tweezers won't work on primaries. Make certain that the wing bones are firmly supported
or you can break the wing. Clamp onto the feather and give a sharp tug in the direction of the
feather. The feather will come out. Next, apply gentle, direct pressure to the follicle where the
feather was to stop the bleeding. Dab some styptic powder on it, as it will help stop the bleeding
as well. Let the bird rest. Ask your vet or breeder to demonstrate exactly how to pull a blood
feather if you'
re apprehensive about doing it.</dd>
</dl>
</body>
</html>
  1. 透過 parents(expr) 取得父節點,並指定 css 效果。



參考資料

2009年2月10日 星期二

[TCP/IP Illustrated] Broadcasting & Multicasting

前言

IP address 有三種,分別是:
  1. unicast
    此即為一般的 IP address,傳送者與接收者是一對一的關係。

  2. broadcast
    當要將訊息發布給同網段中所有的電腦時,就將訊息送給 broadcast IP address,這樣一來所有的電腦都會將訊息接收過去。使用此特性的協定有 ARP、RARP....等等。

  3. multicast
    此種也是類似 broadcast,但接收端僅為特定的群組,並非全部電腦都包含在內。

其中 broadcase 與 multicase 都必須仰賴 UDP 協定,原因是因為使用 TCP 必須先進行 3-way Handshake 以進行兩端點間的連線,並不適合用於此情境中。



Broadcasting

broadcast 是將資料傳送到網路中所有的電腦的一種方式,一共可以分為四種:(資料來源)
  1. 有限廣播 (Limited Broadcast)
    只有目前所在的區域網路 (LAN) 上作廣播,其IP的地址格式為255.255.255.255。

  2. 網路取向廣播 (Net-directed Broadcast)
    在某特定 (指定) 的網路作廣播,例如向某一類別為A (Class A) 的網路廣播,其IP地址格式為netid.255.255.255。

  3. 子網路取向廣播 (Subnet-directed Broadcast)
    針對某等級(class) 中的某子網路 (Subnetwork) 作廣播。以類別B的網路來看,若其子網路遮罩 (subnet mask) 為255.255.255.0,而IP地址為140.116.46.255,則是表示將資料廣播到類別B的網路140.116.中的子網路46上,而最後一個255則是代表廣播的意思。

  4. 全子網路取向廣播 (All-subnets-directed Broadcast)
    針對某一等級中所有的子網路皆作廣播。此種廣播方式必須與路由器 (router) 的子網路遮罩配合。例如有一類別B的子網路遮罩為255.255.255.0,則若有一IP地址為140.116.255.255時,則表示將資料廣播到類別B的網路140.116中的所有子網路上。在實際的網路架構中,歸屬於某一類別網路的所有子網路,可能位在不同路由器 (router) 的不同輸出入埠上(port),因此藉由子網路遮罩,路由器可以將資料傳送到所屬的所有子網路上進行廣播



Multicasting

IP Multicasting 是將資料傳送給某一特定群體 (Group) 的所有成員 (members),而屬於同一群體的各個成員可能是散佈在各個不同的網路上。

接著用一張圖來說明 multicasting 的 IP address 格式:

從上圖可以知道,multicasting IP address 屬於 class D,其範圍介於「224.0.0.0 ~ 239.255.255.255」之間

在 RFC 1112 定義了 IP multicast 的標準,它的特性之一是可送出一份 IP datagram 到 LAN 或 WAN 上,只要對這份 datagram 有興趣的使用者加入此「group address」(如何加入呢? 只要設定要擷取特定 multicasting IP address 的資料,就表示加入此 group 了;若是有其他認證機制則另當別論),就可收到這份 datagram。Multicasting 是一種 push 的技術,server 送出資料而 client 不用發出 request。

而 router 也要判斷所連結的網段中是否有加入特定 multicasting group 的電腦,來決定是否繼續傳送 multicasting 的訊息,這裡面運作的機制,稱為 IGMP(Internet Group Management Protocol)。

在目前還是有許多地方會應用到,最常用的大概就屬於視訊會議這一類的應用了。

然而,IANA 預先定義了幾個 well-known 的 multicasting IP address(就像 well-known port number 一樣),在使用上就必須要避開這些 IP,例如:
  • 224.0.0.1 (子網路中所有的系統)

  • 224.0.0.2 (子網路中所有的 router)

  • 224.0.1.1 (NTP;Netowork Time Protocol)

  • 224.0.0.9 (RIP-2)

當然不只以上這些,而若要查詢最新的清單,可以查詢 RFC 文件。


參考資料

[Oracle] 網路學習資源

ODP.NET (Oracle Data Provider for .NET)



TableSpace





Import & Export





Performance Tunning





SQL 語法應用





未分類










2009年2月9日 星期一

[TCP/IP Illustrated] UDP (User Datagram Protocol)

前言

不同於 TCP(stream-oriented,每個 IP datagram 中的資料都有其關聯) 協定,UDP 屬於不可靠的協定,因此並無法保證封包一定可以到達目的地(這表示即使封包沒到達目的地,發送端也不會收到任何通知)。

以下為 UDP datagram 是如何封裝在 IP datagram 中:

由於 UDP 協定必須依靠 IP 協定將資料傳送出去,因此在 UDP datagram 外面還要另外加上 IP datagram。



UDP Header

雖然 UDP 是種不可靠的協定,但相對於 TCP 單純的多,以下是 UDP Header 的內容:

其中比較需要說明的為 UDP length 的部份,而此 length 指的是整個 UDP header 的長度,最少為 8,而從圖中就可以看出,當 length = 8,表示 data 的部份為 0 byte。

而 checksum 的欄位,可以作為驗證資料正確與否來用,但接收端驗證後,若即使有資料錯誤的情況發生,也只會直接丟棄封包而不會通知傳送端,因此有些網路設備並沒有實作或是開啟驗證 checksum 的功能。



IP Fragmentation

要瞭解 IP 是如何將封包拆開再結合之前,必須先看看 IP header 的內容:
當資料量過大時,IP 會自動將資料拆開,分成多個 datagram 進行傳送。

其中將資料拆開成多個 datagram 的工作在傳送段電腦完成;而將多個 datagram 組合成原本的資料,則是接收段電腦的工作。

而在 IP header 中用來控制資料拆開與組合的資訊,即為 identification、flag、fragmentation offset 三個欄位。

此外,若是封包在傳遞的過程中遺失了應該要怎辦? 答案就是:「全部重送一次」。

但 IP 沒有可以判斷傳送是否發生錯誤的機制,因此必須仰賴上一層的 protocol(例如:TCP) 來處理,而 UDP 沒有此種驗證機制,因此若是有需求,就必須在 UDP application 中有提供才行。

那到底將資料拆成多個封包後會變得如何呢? 以下有圖可以說明:



UDP datagram 可以到多大?

理論上,每一個 UDP datagram 最大可以到 65535 bytes(20 bytes IP header + 8 bytes UDP header + 63307 bytes data),但實際上,由於 socket API 或是 OS kernel 的限制,有時候並無法使用這麼大的 UDP datagram 進行傳送。

而實際應用上,其實也沒用到如此大的 UDP datagram,舉例來說:

NFS:使用大小為 8192 bytes 的 UDP datagram

DNS、TFTP、BOOTP、SNMP:都使用小於 512 bytes 的 UDP datagram



與 UDP 協定相關的錯誤

ICMP Unreachable Error (Fragmentation Required)

當封包大小超過 MTU 時,就必須將封包拆開了,但此時如果 IP header 中的 DF(don't fragment) flag 若是被啟用,就會發生 ICMP Unreachable Error。


ICMP Source Quench Error

此種錯誤發生在接收端的 buffer 又已經滿了,且來不及處理快速傳入的 UDP datagram 時。



基本上,不一定每個接收端發生此種錯誤時,都會處理 ICMP Source Quench Error 訊息給傳送端,這必須要端看接收端的實作情況。(有些會因為協定是 UDP 而忽略,TCP 則會處理)

2009年1月31日 星期六

在 Windows 中編輯 Linux Shell Script 的問題

最近開始練習 Linux shell script,想說用 Windows 上的編輯器來編輯

卻發現程式一直都無法正確執行

後來網路上找到資料,原來是 Windows 與 Linux 的換行字元不同所導致.......

這時候就必須要修改一下編輯器的設定了!

以 MadEdit 為例:


將換行字元改為 Unix,程式就可以正常執行啦! 


參考資料

2009年1月23日 星期五

[TCP/IP Illustrated] Dynamic Routing Protocols

Dynamic Routing

相鄰的 router 之間互相交換網段資訊、連線資訊 ... 等資訊,稱為 dynamic routing,而在 router 上處理這一類工作的 process,就稱為 routing daemon。

routing protocol  還可以根據是否在同一個 AS(Autonomous System) 中來分類,分別是:
  • IGP (Interior Gateway Protocol)
    包含 RIP(Routing Information Protocol)、EIGRP、OSPF(Open Shortest Path First Protocol)、IS-IS ... 等等。

  • EGP (Exterior Gateway Protocol)
    包含 EGP、BGP(Border Gateway Protocol) ... 等等。




Routing Information Protocol

RIP message 是包在 UDP datagram 中傳遞的,以下是傳遞 RIP 訊息的 UDP datagram 格式:


接著是 RIP message 本身的格式:


其中 RIP message 有幾個部份需要注意的:
  • command
    1 = request
    2 = reply

  • version
    是版本不同而不同,RIP = 1,RIPv2 = 2。

  • address family
    此欄位的值永遠為 2,而其他框起來的部份(共 20 bytes)則共同組成一個 IP address 與 metric 資訊。

一個 RIP 訊息最多就僅能帶 25 個 IP address 與 metric 資訊。

運作方式

RIP 所使用的是 UDP port 520,運作的流程如下:
  1. Initialization
    首先會偵測 router 上的介面是否啟用,接著會從啟用的介面送出 request 封包,請求其他 router 將其完整的 routing table 資訊送過來。
    而 request 封包中的 command = 1,address family = 0,metric = 16。

  2. Request received
    router 接收到 request 的封包後,回傳 routing table 資訊給請求者;或是根據封包中所請求的位址給予相對應的 routing 資訊。

  3. Response received
    請求者收到回應,更新 routing table。

  4. Regular routing updates
    接著 router 每 30 秒會將自己的 routing table 資訊送給相鄰的 router。

  5. Triggered updates
    當設定有改變時,就可以發送訊息給鄰近的 router 進行修改。

每個 router 都有其 timeout 的預設值,以三分鐘為例,就表示有 6 次 router advertisement message 沒有收到,因此此時會將 metric 設定為 16,表示此 router 已經不存在,將其刪除。

但沒辦法避免的,使用 RIP 的確會發生 routing table 更新延遲而發生網路不夠穩定的情況。

Metric 的意義

metric 在 RIP 是用來紀錄封包傳遞時,會經過的 router 次數(hop count),而直接連接網路介面的 hop count 為 1,以下用圖來說明:
若是 R1 要到 N2,metric = 1
若是 R1 要到 N3,metric = 2
若是 R2 要到 N2,metric = 1
若是 R2 要到 N1,metric = 2

RIP 的問題所在

從上面運作的原理,其實就可以大概推測出一些問題的端倪了,大概有以下幾點:
  1. hop count 最大是 15,這到 internet 上就不是那麼適用了。

  2. RIP 無法處理 subnet 的定址資訊。

  3. 不論是 router 故障或是從故障中復原,要讓網路上的其他 router 知道以更新 routing table,需要花過多的時間。




RIP version 2

基本上 RIPv2 並沒有改變原先 RIP 運作的模式,而是將原本在 RIP message 中沒有用到的欄位拿來使用而已,以下是 RIPv2 message 的格式:
不同於 RIP 的欄位說明如下:
  • routing domain
    辨識此封包屬於哪個 routing daemon(儲存 process number) 之用,因此表示可以同時有多個 routing daemon 同時處理 RIPv2 的封包。

  • route tag
    用來支援 EGP 之用,儲存 AS(Autonomous System) number。

  • subnet mask
    對應上面的 IP address,以支援 subnet 的處理。

另外要提的是,RIPv2 還支援了簡單的認證功能,只要 address family 設定為 0xFFFFF,且 route tag 的值為 2,往下的 16 個 bits 就是儲存明文密碼的地方。

不僅如此,RIPv2 還支援 multicast,如此一來就不用使用 broadcast 來減少網路流量了!



OSPF (Open Shortest Path First)

相較於 RIP,OSPF 是較新的 routing protocol,克服了 RIP 的許多限制,其中 OSPF Ver.2 定義在 RFC 1247 中。

RIP 屬於距離向量(distance-vector)協定,而 OSPF 屬於鏈路狀態(link-state)協定;差別在於 link-state protocol 中,router 之間不互相交換距離(這裡指的是 hop count)資訊,而是主動偵測周遭 router 的連線狀態(包含實際的距離、頻寬 ... 等資訊),接著將蒐集到的資訊傳送給同一個 AS 中的所有 router,每個 router 蒐集來自各方的資訊後,再將這些資訊組合而成 routing table。

其中最重要的是,當網路上有 router 發生問題而無法提供正常服務時,比起 RIP,使用 OSPF 可以大幅降低收斂(亦即重新發送訊息以組合成可以正常提供服務的 routing table)的時間。

之前提到 RIP 是使用 UDP port 520,而 OSPF 則是直接使用 IP 協定。

由於 OSPF 屬於 link-state protocol,因此也較 RIP 多了以下的優點:
  1. 可以針對不同的路徑計算出 IP type-of-service,因此在 routing table 中可以針對同一個目的地有多個選擇。

  2. 每個網路介面都會給予一個值,其計算的基準在於 throughput、round-trip time、可靠度 .... 等等。每個 IP type-of-service 都可以賦予一個值。

  3. 當到目的地的封包有多條路徑可以選擇,且每條路徑成本相同時,OSPF 會自動將流量分散從不同的路徑傳遞,以求達到負載平衡的效果。

  4. 點對點之間的連線網路介面不需要 IP address。

  5. router 之間訊息的傳輸使用 multicast 而非 broadcast,減少整體的網路流量。

而現在 routing protocol 幾乎都改用 OSPF 囉!



BGP (Border Gateway Protocol)

BGP 是 EGP 的一種,用來在 internet 中讓 AS 之間交換 routing 資訊之用,目前 BGP Ver. 4 的詳細規則可以查詢 RFC 1771。

不同 AS 之間會透過 BGP 傳遞包含 AS 的完整 routing 路徑表資訊,藉由組合這些 routing 資訊來形成一個完整的 internet routing 資訊,並避免掉可能發生的 routing loop。

不同於 RIP(UDP port 520) 與 OSPF(IP),BGP 系統間的溝通是透過 TCP 協定來交換 routing table 的資訊;此外,BGP 還會定期送出 keepalive 的訊息,用來確定周遭的 AS 是否可以正常提供 routing 的服務,以便當 AS 有問題時,可以縮短收斂的時間。

而 BGP 的設計上,還可以讓 AS 的管理員自訂設定規則,亦即 BGP 可以允許轉換為 policy-based routing,因此也讓管理者有更大的空間可以發揮與利用。



CIDR (Classless Interdomain Routing)

CIDR 應該是不能算是 protocol,而是一種技術(或是功能)。

怎麼說呢? 這是由於 B class 的網段過於龐大,256 * 256 台主機,如果不切割網段會造成可怕的 broadcast storm,因此解決方式就是切成 C class。

但是切成 C class 卻衍生出了另一個問題:每個 network 之間都必須要有 router。

這可是相當麻煩且耗費成本的事情阿,而且 routing table 的數量也會一下子暴增很多;因此 CIDR 就是用來解決這個問題所衍生出來的技術。

而若是僅按照 Class A B C 去切割網路,其實問題是相當多的,例如:
  1. 浪費 IP address
    使用 A class、B class 的組織不見得用的到這麼多 IP;使用 C class 的組織有可能 IP 數量不夠用;多切割一個網段就必須要多浪費兩個 IP(Network ID & Broadcast)。

  2. 無法符合實際需求
    大部分組織可能會遇到 C class 不夠用,B class 卻太多的情況,僅切成 class A B C 並不符合實際的需求。

CIDR 使用 7~32 可變換長度的 Network ID 來取代原本 Class A B C 長度為的 8 16 24 的 Network ID,因此 host 範圍可以從 32 ~ 約 50000 個!

從上面的敘述可以瞭解到,其實 CIDR 的功能就是「將多個小網段組成一個較大的網段」,如此一來 router 所維護的 routing table entry 就可以大大地減少,也因此可以提昇 router 運作的效能。



參考資料

2009年1月22日 星期四

[Oracle] Trigger - 「:NEW」的使用

今天試寫一支 trigger,目的是要讓每一列的 action 欄位更新為 1 時,自動塞入日期與時間的資料,第一次用到「:new」,因此記錄一下。

以下是程式碼:

create or replace trigger tri_fill_datetime_to_message
--只有在更新資料後才需要加入日期時間資訊
before update
on mytable
for each row
begin
--當 action 更新的值為 1 時,在執行以下動作
if :new.action = 1 then
--設定新資料的日期時間資訊
:new.log_date := to_char(sysdate, 'YYYYMMDD');
:new.log_time := to_char(sysdate, 'HH24MISS');
:new.log_datetime := sysdate;
end if;
end tri_fill_datetime_to_message;


參考資料

2009年1月19日 星期一

[TCP/IP Illustrated] IP Routing

簡介

IP 在 TCP/IP protocol suit 中是非常重要的一個協定,有多重要呢? 以下用一張圖來簡單說明 IP 所負責的事情:

在圖中的 routing table 是最常被 IP 存取的部份,可能每秒數百次;而 routing daemon 則是在背景執行的程序,大約半分鐘會更新 routing table 的資訊一次,而更新的資訊可能來自鄰近的 router,或甚至是 ICMP redirect 的封包都可以用來作為更新 routing table 的依據。


Routing 原則

基本上,routing 的規則如下:
  1. 搜尋有無符合的 host

  2. 搜尋有無符合的 network

  3. 搜尋 default gateway (預設閘道)

以上的 routing 工作是由 IP 負責處理;而上面的規則都是去搜尋 routing table 中的資訊,而這些資訊(也稱為 routing policy)都是由 routing daemon 所負責提供。

routing table 長什麼樣呢? 以下截圖說明:

其中兩條被框起來的規則(Flags 的部份有大寫「G」)為 gateway,而 Flags 說明如下:
  • U:表示此規則路徑是正常運作的

  • G:將封包導向 gateway

  • H:將封包導向特定的主機

  • D:表示此規則是由 ICMP Redirect 訊息所新增的

  • M:表示此規則是由 ICMP Redirect 訊息所修改的



關於 routing 時會發生的錯誤種類

ICMP Host & Network Unreachable Errors

當 router 收到 IP datagram 卻無法將其繼續傳送時,就會發生此錯誤,通常此種錯誤出現的原因為網路設定上的錯誤,或是硬體線路沒接好。

ICMP Redirect Errors

此錯誤訊息是由 router 所發,目的是用來告知發送 IP datagram 的主機,應該要將封包送到另外一個 router 上。

以下用圖片來說明一下發生 ICMP Redirect Error 的情境:

  1. 其中 host 將 IP datagram 送給 R1,因為 R1 是 host 的 default gateway。

  2. R1 收到封包,檢查 routing table 規則後,發現其實 R2 才是正確的路徑,因此將封包導向 R2。

  3. 除了將封包導向外,R1 也額外發送了 ICMP Redirect Error 的訊息給 host,通知它要修改自己本身的 routing table。

由這個功能可以瞭解到,若是在複雜一點的 topology 中,要每個 host 都清楚知道封包 routing 的路徑是很難的,但有了這功能後,就可以僅設定 default gateway,接著送封包後就會由 router 通知來改變自身的 routing table 了。

【備註】
  1. 此種錯誤訊息僅有 router 能發送,host 是不能發送的。

  2. router 僅能送出 host redirect 而不是 network redirect,理由是因為 router 要掌握 topology 所有的網段資訊不是很容易,而且也有可能覆蓋原本 host 的 routing table 中不需要修改的設定。



ICMP Router Discovery Messages

此種訊息是用來讓 host 動態取得 routing 資訊用,透過發送此訊息(solicitation),周遭收到此訊息的 router 便會進行回應(advertisement);此外,router 還會定期的廣播 router advertisement,讓 host 可以即時更新 routing table 中的資訊。

從上面的運作方式可以瞭解到:
ICMP Router Discovery Message = ICMP Router Solicitation Message +
ICMP Router Advertisement Message
接著是兩種 message 的格式:

ICMP Router Solicitation Message
ICMP Router Advertisement Message

Router 在做哪些事情?

在 routing 資訊的傳送中,router 做了哪些事情呢?

router 會固定每一段時間(約 450~600 秒不等)都發送 advertisement 訊息,若是 router 中有網路介面準備要關閉停用時,會發送一個 lifetime = 0 的 advertisement 訊息通知周圍的 host 與 router 此網路介面將要失效,必須要更新 routing table 中的資訊。

若是同時有多台 router 在同一個網段中,preference level 的部份就是要由網路管理者來決定了,才有辦法讓 routing policy 有先後順序之分。

Host 在做哪些事情?

host 就跟 router 相反了,開機時送出 router solicitation 訊息,以便接收 router 的回應來更新 routing table;當然,也會隨時監聽來自 router 的 advertisement 訊息,以便取得 routing 的最新資訊。

一般來說,router advertisement 訊息每 10 分鐘就會發送一次,而 lifetime 為 30 分鐘,只要在 30 分鐘之內有收到 router advertisement,就可以一直保持著有 routing 資訊了!

2009年1月11日 星期日

[TCP/IP Illustrated] Traceroute

運作原理簡介

基本上 traceroute 是使用 ICMP 協定,並透過 IP header 中的 TTL 的值每經過一個 router 會遞減的特性,來取得封包傳遞的路徑。

詳細的運作原理說明如下:(以下 client 為執行 traceroute 的主機,server 則為目的地)
  1. client 發送 TTL 為 1 的封包,destination 指定為 server。

  2. 當第一個 router 收到後會將 TTL 減一,此時 TTL 為 0,因此 router 回送 ICMP time exceeded 訊息,如此一來,client 就可以知道封包傳遞路徑中的第一個 router 是哪一個。

  3. 接著發送 TTL 為 2 的封包,同樣的可以接收到來自第二個 router 所回傳的 ICMP time exceeded 訊息。

  4. 一直到封包真正到達 server 時,此時 server 會回應 ICMP port unreachable 訊息,為什麼呢? 原因是因為 client 所發送的封包為 UDP datagram,並指定一個很大的數字(> 30000),通常不會有 server 會在這麼大的 port number 上提供服務,因此會回傳 ICMP port unreachable 的訊息。

  5. client 收到來自 server 的 ICMP port unreachable 訊息,就完整拼湊出封包傳遞路徑了!


tracertroute 實測

在 Windows 中的程式名稱為 tracert,在 Linux 則為 tracepath (也有 tracertroute,不過要另外裝套件),以下是在 Windows 使用的範例圖:

若用 tcpdump 偵測封包往來情況,結果如下:

從上面的實測可以得知,在封包傳遞路徑上的節點,都是由 ICMP time exceeded 與 udp port unreachable 來決定的,以下為 ICMP time exceeded 封包的格式:
從圖中可以看出,ICMP time exceed 封包的 type 值為 11,而 time exceeded error 又會在兩種情況下發生,分別是在傳送(trasmit,code = 0)以及封包重組(reassembly,code = 1)時。


IP Source Routing Option

這個功能剛好跟 ping 的 record route option 相反,是將 router 的 IP 清單填到封包中,讓封包可以按照清單上的路徑傳遞,有分為 strict/loose source routing,封包格式如下:

不過這功能似乎不常用,可能高階的網管有可能用到吧......以後學到高階的時候再來研究這個好了。


參考資料

[Linux Command] T 開頭

tcpdump

2009年1月10日 星期六

[TCP/IP Illustrated] Ping

簡介

ping 通常是我們用來驗證主機之間的通訊是否有連通的第一個手段。然而隨著網路技術越趨進步與複雜,光用 ping 已經不足以判斷兩台主機之間是否可以互相進行通訊了,但由於 ping 已經是一個歷史悠久的網路工具程式,因此還是很值得拿來探討一下。


ping 是如何運作的 ?

ping 的實作是使用 ICMP echo/reply 來達成的,其中發送 echo 的主機稱為 client,回應 reply 的主機稱為 server,而由於 ping 都已經實作在各 OS 的 kernel 中,因此也不必為了啟用這個功能而安裝特定的軟體或是服務。

以下是 ICMP echo/reply 的封包格式圖:
封包的欄位說明:
  • type
    echo 為 8;reply 為 0。

  • code
    echo/reply 皆為 0。

  • identifier
    此欄位儲存 ping client 的 process number,如此一來當收到 reply 後,就可以知道由哪一個 process 來處理。

  • sequence number
    紀錄 ping client 所送出的第幾個封包,從 0 開始依次遞增。


以下是從 Linux server ping 到 google 的範例:


IP Record Route Option

使用 ping 時可以透過「-R」參數將 record route 選項開啟,如此一來就可以知道封包所經過的路徑與節點,但由於目前大部分主機都關閉對此功能的支援,因此使用這個參數都不會有相關的回應。

不過雖然 ping 支援紀錄路徑的功能,但還是有最多僅能紀錄 9 組 ip 的限制(即使來回經過的相同節點都要包含),在目前網路環境下,實用性似乎不足。

不過以下還是放一下 IP record route 封包存放資料的格式:


IP Timestamp Option

這個就跟 record route option 蠻類似的,只是紀錄的是封包到達每個 hop 的 timstamp 而非 ip address,封包格式如下圖:

2009年1月9日 星期五

[Linux] 透過指令直接設定 IP、Netmask、Gateway

今天在 VirtualBox 上安裝 Ubuntu Server,但由於網路環境中沒有 DHCP,因此要進到 /etc/network/interfaces 去修改成 static;但麻煩的是,沒有安裝 vim 套件前,實在是沒辦法編輯(不曉得預設是哪種編輯器....用起來很彆扭),當然也就沒辦法修改 IP。

那........怎麼辦呢? 我要改 IP 跟 Gateway 阿!

找了一下資料,發現可以透過指令方式直接指定:(設定範例)
root> ifconfig eth0 192.168.1.1 netmask 255.255.255.0
root> route add default gw 192.168.1.254

這樣就成功啦!


參考資料

2008年12月25日 星期四

[Oracle] 多國語系的處理

前言

Oracle 對於多國語系的支援是相當強大的,可以隨時根據需求改變使用不同的語系、時間格式、貨幣格式、文字編碼、設數字表示法 … 等等。

在 Oracle 中是透過一群參數來控制多國語系的設定,這一群參數稱為「NLS(Native Language Support) parameter」。

檢視並運用 NSL paramter

NSL parameter 可以透過 Oracle 所提供的 SQL Developer 工具進行檢視,選擇「Report –> Data Dictionary Reports –> About Your Database –> National Language Support Parameters」,畫面如下:

另外,可以透過以下語法,暫時修改目前連線 session 的語系環境:

ALTER SESSION SET parameter-name = ”value”;

以下以範例作說明:

--修改語言
ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN';

但需要注意的是,透過 ALTER SESSION 所修改的參數,在重新連線後都會重新變回原本的預設值。


建立支援多國語系的環境

以下分段說明如何自訂多國語系的環境:

設定語言(Language)與地區(Territory)參數

在此部份的參數與影響的部份為:

1、NLS_LANGUAGE

  • server 訊息顯示時所用的語言

  • 日期與其縮寫的表示方式

  • 資料排序時的依據(不同的語系會根據不同的字元標準進行排序)

以下為 NLS_LANGUAGE 的使用範例:

--修改語言
ALTER SESSION SET NLS_LANGUAGE = 'TRADITIONAL CHINESE';
--顯示 : 23-12月-08
SELECT SYSDATE FROM DUAL;

--修改語言
ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN';
--顯示 : 23-DEC-08
SELECT SYSDATE FROM DUAL;


2、NLS_TERRITORY

  • 日期格式

  • 十進位字元與分隔符號

  • 本地貨幣符號

  • ISO 貨幣符號

以下為 NLS_TERRITORY 的使用範例:

--結果如下:
-- NT$24,000.00
-- NT$17,000.00
-- NT$17,000.00
SELECT TO_CHAR(salary, 'L99G999D99') salary
FROM employees
WHERE employee_id IN (100, 101, 102);

--改變 territory 為德國
ALTER SESSION SET NLS_TERRITORY = 'GERMANY'

--結果如下:
-- €24.000,00
-- €17.000,00
-- €17.000,00
SELECT TO_CHAR(salary, 'L99G999D99') salary
FROM employees
WHERE employee_id IN (100, 101, 102);


設定日期(Date)與時間(Time)參數

不同的地區都會有自己地區表示時間的格式,但如果不適合用,當然要修改預設值也是沒有問題的,關於日期與時間的 NLS parameter,有以下幾個:

1、NLS_DATE_FORMAT

除了 NLS_TERRITORY 參數可以改變預設的日期格式外,若要進行客製化的話,就必須透過 NLS_DATE_FORMAT 這個參數了,以下用範例說明: (官方文件說明)

--執行結果 : 24.12.08
SELECT SYSDATE FROM DUAL;

--改變日期格式
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY';

--執行結果 : 12/24/2008
SELECT SYSDATE FROM DUAL;

--改變日期格式
ALTER SESSION SET NLS_DATE_FORMAT = '"DATE: "MM/DD/YYYY';

--執行結果 : DATE: 12/24/2008
SELECT SYSDATE FROM DUAL;


2、NLS_DATE_LANGUAGE

透過此參數可以改變顯示日期的語言,不過僅會在使用 TO_CHAR 與 TO_DATE 兩個 function 時有效,以下是範例:(官方文件說明)

Created with colorer-take5 library. Type 'sql'

--執行結果: ���期三:24 12月 2008
SELECT TO_CHAR(SYSDATE, 'Day:Dd Month yyyy') FROM DUAL;

--改變表示 DATE 語言
ALTER SESSION SET NLS_DATE_LANGUAGE = 'FRENCH';

--執行結果: Mercredi:24 Decembre 2008
SELECT TO_CHAR(SYSDATE, 'Day:Dd Month yyyy') FROM DUAL;

--改變表示 DATE 語言
ALTER SESSION SET NLS_DATE_LANGUAGE = 'AMERICAN';

--執行結果: Wednesday:24 December 2008
SELECT TO_CHAR(SYSDATE, 'Day:Dd Month yyyy') FROM DUAL;


3、NLS_TIMESTAMP_FORMAT & NLS_TIMESTAMP_TZ_FORMAT

這兩個其實是一起的,只是一個會改變 select 出來的 timestamp 顯示(NLS_TIMESTAMP_FORMAT),一個會改變由 TO_CHAR() function 所呈現的 timestamp 顯示(NLS_TIMESTAMP_TZ_FORMAT)。

以下為範例:(官方文件說明)

--執行結果: 24-12月-08 02.22.38.284000000 下午 ASIA/TAIPEI
SELECT CURRENT_TIMESTAMP FROM DUAL;

--改變 timestamp 顯示格式
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH:MI:SS:FF TZH:TZM';

--執行結果: 2008-12-24 02:22:38:334000000 +08:00
SELECT CURRENT_TIMESTAMP FROM DUAL;


數字表示法的設定

數字的部份,可以透過「NLS_NUMERIC_CHARACTERS」參數設定千、百萬的分隔字元,以及表示小數點的分隔字元;以下用範例說明:

--執行結果: 4,000.00
SELECT TO_CHAR(4000, '9G999D99') FROM DUAL;

ALTER SESSION SET NLS_NUMERIC_CHARACTERS = ',.';

--執行結果: 4.000,00
SELECT TO_CHAR(4000, '9G999D99') FROM DUAL;


貨幣格式的設定

貨幣格式的部份可由以下三個參數來設定:

  1. NLS_CURRENCY

  2. NLS_ISO_CURRENCY

  3. NLS_DUAL_CURRENCY

以下用範例說明使用方式:

--執行結果:
-- TWD009.000,00
-- TWD006.000,00
-- TWD004.800,00
-- TWD004.800,00
-- TWD004.200,00
SELECT TO_CHAR(salary, 'C099G999D99') salary FROM employees where department_id = 60;

--將貨幣表示更改為法國
ALTER SESSION SET NLS_ISO_CURRENCY = 'FRENCH';

--執行結果:
-- EUR009.000,00
-- EUR006.000,00
-- EUR004.800,00
-- EUR004.800,00
-- EUR004.200,00
SELECT TO_CHAR(salary, 'C099G999D99') salary FROM employees where department_id = 60;


排序與搜尋方式的設定

不同的語言在資料排序上不盡然相同,有些以字母順序為依據,有些以發音為依據、有些以單字的字母數量為依據;而 Oracle 當然也支援根據不同的情況進行改變。

而影響排序搜尋的 NLS parameter 有兩個,分別是「NLS_SORT」與「NLS_COMP」 以下有兩個參數會影響到系統的運作:

以下用範例來說明:

--修改排序方式
ALTER SESSION SET NLS_SORT = 'BINARY';

--執行結果:
-- Cabrio
-- Cambrault
-- Cambrault
-- Chen
-- Chung
-- Colmenares
SELECT last_name FROM employees WHERE last_name LIKE 'C%'
ORDER BY last_name;

--修改排序方式
ALTER SESSION SET NLS_SORT = 'spanish_m';

--執行結果:
-- Cabrio
-- Cambrault
-- Cambrault
-- Colmenares
-- Chen
-- Chung
SELECT last_name FROM employees WHERE last_name LIKE 'C%'
ORDER BY last_name;

此外,若是搜尋時大小寫有別,可以透過類似「NLS_SORT = ‘BINARY_CI’」(Case-Insensitive) 的方式進行設定。


設定資料長度的判斷基準

由於 Oracle 支援多國語言,其中英文是屬於 single-byte 的字元集,其他許多國家的語言則屬於 multi-byte 的字元集;而在 Oracle 中預設是以 byte 為基準,但實際上有時候還是必須以 char 為基準才是比較正確的。

因此在 Oracle 中可以透過以下語法進行設定:

--將計算基準改為 char
ALTER SESSION SET NLS_LENGTH_SEMANTICS = 'CHAR';

--將計算基準改為 byte
ALTER SESSION SET NLS_LENGTH_SEMANTICS = 'BYTE';


設計支援多語系的應用程式

官方網站中有文件說明,指導開發者如何開發 unicode 的應用程式

要儲存 unicode 的資料到 Oracle 有兩種方式:

  1. 建立一個 unicode 資料庫。(指定資料庫使用的編碼)

  2. 指定特定欄位支援 multi-bytes 以儲存 unicode 的資料,在 Oracle 為 NCHAR 資料型態。 (基本上,設定 NCHAR 型態的話,不論資料庫使用的編碼為何,都可以儲存 unicode 的資料)


使用 Unicode 資料型態

在 Oracle 有兩種儲存 unicode 資料的型態,分別為:

  1. NCHAR

  2. NVARCHAR2

首先說明 NCHAR 使用方式,宣告方式如下:

CREATE TABLE table1 (column1 NCHAR(30));


而 NCHAR 的使用長度上有兩個限制:

  1. 當字元編碼為 UTF-8 時,NCHAR 最多可以儲存 2000 的字元;若是改為 AL16UTF16,就僅能儲存 1000 個字元。

  2. 實際儲存長度最大為 32768 bytes。

根據使用者的宣告,資料長度若不足宣告的欄位長度,則會補足空白字元。


另外一種資料型態則為 NVARCHAR2,宣告方式如下:

CREATE TABLE tables (column2 NVARCHAR2(2000));

而 NVARCHAR2 在使用長度上也是有兩個限制:

  1. 當字元編碼為 UTF-8 時,NCHAR 最多可以儲存 4000 的字元;若是改為 AL16UTF16,就僅能儲存 2000 個字元。

  2. 實際儲存長度最大為 32768 bytes。


使用與處理 Unicode 字串

在 SQL 處理中,若要指定使用 unicode 字串,有以下幾個方式:

  1. 在單引號旁邊加上大寫「N」。

  2. 使用 NCHR(n) 函式,可以將字元轉為 unicode 編碼,提高程式的可攜帶性。

  3. 使用 UNISTR(‘string’) 函式,將字串轉換為 unicode 編碼。


NCHAR 的轉換

在開發多國語系的應用程式時,要考慮的部份除了 server 的編碼外,還包含 client 的編碼,當兩者編碼不同時,資料在傳輸時可能就會因為轉換而造成資料不正確,即使使用 N(‘string’) 的方式也是相同。

此時可以在 DB client 的地方增加一個名稱為「ORA_NCHAR_LITERAL_REPLACE」的環境變數,並將值設定為 TRUE,如此一來使用 N(‘string’) 時,系統就會自動轉換為 unicode 來傳輸並儲存囉!


在 Function 中使用 NLS parameter

在 Oracle 中有些 function 會因為語系、地區的不同而造成使用效果也相對不同(例如:TO_CHAR'、TO_DATE、TO_NUMBER、NLS_UPPER、NLS_LOWER、NLS_INITCAP、NLSSORT … 等等),因此若是有特殊需求的話,可以直接將 NLS parameter 指定在 function 中,讓操作環境暫時改變。

為了讓 function 開發時可以容易些,有時候必須暫時轉變語系進行處理,否則程式光是要考慮語系的部份可能就太過於複雜了,以下用個範例說明:

Created with colorer-take5 library. Type 'sql'

--這樣搜尋是不被允許的,因為日期格式並非如此表示
SELECT last_name FROM employees WHERE hire_date > '01-Jan-1999';

--所以改成美國的表示法
ALTER SESSION SET NLS_DATE_LANGUAGE = 'American';

--如此一來,不論原本的語系為何,都可以正確顯示搜尋結果囉!
SELECT last_name FROM employees WHERE hire_date > '01-Jan-1999';


透過在 function 中指定有個好處,就是不會改變該連線 session 時的 NLS 設定,而只有在該 DML 操作時有效而已,以下用個範例說明:

--直接在 TO_CHAR 函式中設定 NLS parameter
SELECT last_name
FROM employees
WHERE hire_date > TO_DATE('01-Jan-1999', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE = American');

因此若是要避免修改到 NLS 設定而影響其他操作,就將 NLS parameter 放進 function 中。

而有哪些 function 可以接受 NLS parameter ? 而這些 function 可以接受的 NLS parameter 又是哪些呢?

以下為列表:
  • TO_DATE
    NLS_DATE_LANGUAGE, NLS_CALENDAR

  • TO_NUMBER
    NLS_NUMERIC_CHARACTERS, NLS_CURRENCY, NLS_ISO_CURRENCY, NLS_DUAL_CURRENCY

  • TO_CHAR
    NLS_DATE_LANGUAGE, NLS_NUMERIC_CHARACTERS, NLS_CURRENCY,NLS_ISO_CURRENCY, NLS_DUAL_CURRENCY, NLS_CALENDAR

  • TO_NCHAR
    NLS_SORT

  • NLS_UPPER
    NLS_SORT

  • NLS_LOWER
    NLS_SORT

  • NLS_INITCAP
    NLS_SORT

  • NLSSORT
    NLS_SORT

接著為使用範例:

-- 21/JUIN /1999      
-- 13/JANV./2000
-- 17/SEPT./1987
SELECT TO_CHAR(hire_date, 'DD/MON/YYYY', 'NLS_DATE_LANGUAGE = French') "Hire Date" FROM employees;

-- 25/12月/2008
SELECT TO_CHAR(SYSDATE, 'DD/MON/YYYY', 'NLS_DATE_LANGUAGE=''Traditional Chinese'' ') "System Date" FROM DUAL;

-- 13.000,00
SELECT TO_CHAR(13000, '99G999D99', 'NLS_NUMERIC_CHARACTERS = '',.''') "13K" FROM DUAL;

-- 2.600,00
-- 2.600,00
-- 4.400,00
SELECT TO_CHAR(salary, '99G999D99', 'NLS_NUMERIC_CHARACTERS='',.'' NLS_CURRENCY=''EUR''') salary FROM employees;
-- ABEL
-- ANDE
-- ATKINSON
SELECT NLS_UPPER(last_name, 'NLS_SORT = Swiss') "Last Name" FROM employees;

-- Abel
-- Ande
-- Atkinson
-- Austin
SELECT last_name FROM employees ORDER BY NLSSORT(last_name, 'NLS_SORT = German');

為了符合多國語系,在開發應用程式時,許多細節就必須注意到,未來才不需要因為語系或是編碼的問題傷透腦筋囉!