邊做邊學 SQL Server 2005 Driver for PHP 存取 Microsoft SQL Server 資料庫

作者:賴榮樞 (http://www.goodman-lai.idv.tw)

邊做邊學 SQL Server 2005 Driver for PHP 存取 Microsoft SQL Server 資料庫


SQL Server 2005 Driver for PHP 提供了 24 個可以用在 PHP 程式的函式,這些函式名稱都是 sqlsrv_ 開頭。而本文即將說明這些函式如何協助 PHP 程式存取 Microsoft SQL Server 資料庫。此外,本文附上一段5分鐘的影片,說明以 SQL Server 2005 Driver for PHP 的函式所編寫的 AdventureWorks Product Reviews 應用範例。

  • 資料庫存取的基本步驟
  • 連接資料庫
  • 檢視用戶端資訊
  • 取得資料
  • 更新資料
  • 轉換資料型別
  • 錯誤處理
  • 結語

本文無法盡數說明所有 24 個函式,而是依照資料庫存取的動作類型 -- 包括連接資料庫、取得資料、更新資料、轉換資料型別、錯誤處理等,分別說明對應到的 SQL Server 2005 Driver for PHP 函式。


資料庫存取的基本步驟

連接資料庫是存取資料庫優先且必要的動作;要取得資料庫裡的資料、或更新資料庫,都必須先成功連上資料庫。SQL Server 2005 Driver for PHP 提供了 sqlsrv_connect 函式讓我們連接 SQL Server。

連接資料庫之後,接著要以 T-SQL 陳述式來「查詢」已連上的資料庫,查詢的內容包羅萬象,甚至有可能超出單純的查詢。因此這個部分必須根據目的、需求,編寫出適當的 T-SQL 陳述式,並加以執行。我們可以利用 SQL Server 2005 Driver for PHP 的 sqlsrv_query 函式進行一次查詢,而 sqlsrv_prepare / sqlsrv_execute 函式能處理多次查詢。

然後我們要取得查詢結果;查詢結果能以陣列、物件、資料流傳回程式,或者也能直接擷取單一欄位。取得查詢結果大都還需要一一「挖出」陣列或物件裡的資料,並呈現在網頁。SQL Server 2005 Driver for PHP 提供的查詢結果取得函式包括 sqlsrv_fetch_array、sqlsrv_fetch_object、sqlsrv_fetch、sqlsrv_get_field、sqlsrv_next_result 等。

此外也可能要將資料回存資料庫 -- 不論是以新資料更新舊資料,或是新增資料列。我們可以利用查詢函式(sqlsrv_prepare / sqlsrv_execute)來更新資料。

連接及關閉資料庫

SQL Server 2005 Driver for PHP 提供了 sqlsrv_connect 函式來連接資料庫。但為了安全,連接資料庫應該有所驗證。因此 SQL Server 2005 Driver for PHP 也提供了兩種資料庫連接的驗證方式:Windows 驗證及 SQL Server 驗證;預設是採用 Windows 驗證。

不論是 Windows 驗證或 SQL Server 驗證,都必須有權限適當的 Windows 帳號或 SQL Server 帳號;帳號的權限必須足以進行 PHP 程式後續的動作,但為了安全考量,也不應該使用權限太大的帳號。

sqlsrv_connect 函式的使用看似簡單,只需兩個參數(第 1 個是連接的伺服器名稱 $serverName 字串,第 2 個是連接資訊 $connectionInfo 陣列);但事實上,第 2 個可選擇的連接資訊陣列可包含了諸多連接相關參數。

sqlsrv_connect( string $serverName [, array $connectionInfo])

$serverName 字串指定了欲連接的伺服器名稱、IP 位址(需確定 SQL Server 組態管理員已啟用 TCP/IP 或具名管道通訊協定),這個字串也可以包含 SQL Server 執行個體名稱(例如 myServer\instanceName)或 TCP 通訊埠編號(例如 myServer,1943,但這必須先將 SQL Server 設定成能接聽特定的通訊埠)。

可選擇的 $connectionInfo 是關聯陣列,內含諸多連接相關參數,通常我們會利用這個陣列來指定連接 SQL Server 之後欲使用的資料庫。如果使用 SQL Server 驗證,也要以此陣列指定 SQL Server 帳號的使用者 ID(UID)和通行碼(password,PWD)。

sqlsrv_connect 函式若執行成功,會傳回 PHP 連線資源,後續的存取動作都會用到。但若執行失敗,會傳回 false。

Windows 驗證

如果沒有以 $connectionInfo 陣列指定 SQL Server 帳號的 UID 和 PWD,sqlsrv_connect 函式就會以 Windows 驗證登入 SQL Server;Windows 驗證是 SQL Server 2005 Driver for PHP 預設的驗證方式。如果 Web 伺服器使用模擬(impersonation),這種驗證則是以 Web 伺服器的行程或緒程作為連接 SQL Server 的身份。因此執行中的 Web 伺服器行程或緒程的憑證,必須能登入 SQL Server。此外,如果 SQL Server 和 Web 伺服器分屬不同電腦,SQL Server 必須設定成能夠遠端連接。

以下是 Windows 驗證的例子:

// 指定伺服器名稱(本機電腦)
$serverName = "(local)";
// 指定連接字串的資料庫名稱
$connectionInfo = array( "Database"=>"AdventureWorks");
/* sqlsrv_connect 函式連接失敗會傳回 false,
    成功會傳回PHP連線資源,
    因此PHP連線資源會被指定到變數 $conn */
$conn = sqlsrv_connect( $serverName, $connectionInfo);

SQL Server 驗證

sqlsrv_connect 函式也能以 SQL Server 驗證的方式登入 SQL Server,但首先 SQL Server 必須設定成 SQL Server 混合模式驗證,然後只要以 $connectionInfo 陣列指定能夠登入 SQL Server 的 UID 和 PWD 即可。但為了安全起見,不應該直接將 UID 和 PWD 寫在 PHP 程式裡,可以改成將 UID 和 PWD 存放在特定且具備適當存取權限的檔案,然後再以 PHP 程式讀取 UID 和 PWD,是較為安全的作法,例如:

// 指定伺服器名稱(本機電腦)
$serverName = "(local)";
// 將 UID 和 PWD 分別存在 C 碟裡的檔案,再以 file_get_contents 函式讀取
$uid = file_get_contents("C:\AppData\uid.txt");
$pwd = file_get_contents("C:\AppData\pwd.txt");
$connectionInfo = array( "UID"=>$uid,
                    "PWD"=>$pwd,
                    "Database"=>"AdventureWorks");
/* sqlsrv_connect 函式連接失敗會傳回 false,
    成功會傳回PHP連線資源,
    因此PHP連線資源會被指定到變數 $conn */
$conn = sqlsrv_connect( $serverName, $connectionInfo);

連接共用

連接共用(connection pooling)是指成功連線之後,將連線集中置於集區,讓程式能重複使用集區裡的連線,而不需重新連接。SQL Server 2005 Driver for PHP 利用 ODBC 的連接共用來提供這項功能,而且預設便啟用連接共用。當 PHP 程式欲連接 SQL Server 時,SQL Server 2005 Driver for PHP 在建立新的連線之前,會檢查集區裡是不是已經有屬性相同的連線:如果有,就會使用集區裡現有的連線(並且會重置其連接狀態);如果沒有,SQL Server 2005 Driver for PHP 才會建立新的連線,並將成功建立的連線加入集區。

但無論如何,我們亦可將連接資訊的 ConnectionPooling 屬性設為 false(0),以此強制 SQL Server 2005 Driver for PHP 建立新的 SQL Server 連線(ConnectionPooling 屬性的預設值為 True):

// 指定伺服器名稱(本機電腦)
$serverName = "(local)";
$connectionInfo = array("Database"=>"AdventureWorks",
                    "ConnectionPooling"=>false);
$conn = sqlsrv_connect($serverName, $connectionInfo);

結束連接

若欲結束已建立的連接,要使用 sqlsrv_close 函式,並以欲結束的 PHP 連線資源作為參數;如果是強制建立的 SQL Server 連線,用完之後務必要以 sqlsrv_close 函式來結束連線。

// 以 sqlsrv_close 函式並搭配 $conn 變數裡的 PHP 連線資源來結束連線
sqlsrv_close( $conn);

檢視用戶端及伺服端資訊

為了便於檢視用戶端及伺服端資訊,SQL Server 2005 Driver for PHP 分別提供了 sqlsrv_client_info 及 sqlsrv_server_info 函式。這兩個函式在使用之前,都必須先建立 SQL Server 連線,並以 PHP 連線資源作為執行函式的參數。而且這兩個函式都是將取得的相關資訊存放在關聯陣列。

sqlsrv_client_info:檢視用戶端資訊

如果執行失敗,sqlsrv_client_info 函式會傳回 false,若執行成功,則會將以下資訊放置在關聯陣列。

索引鍵

說明

DriverDllName

SQL Server Native Client DLL檔名(SQLNCLI.DLL)

DriverODBCVer

ODBC 版本序號

DriverVer

SQL Server Native Client DLL 版本序號

ExtensionVer

php_sqlsrv.dll(SQL Server 2005 Driver for PHP)版本序號

以下是 sqlsrv_client_info 函式的使用範例及執行結果。

// $conn 內容是 PHP 連接資源
// 叫用 sqlsrv_client_info 函式取得用戶端資訊
if( $client_info = sqlsrv_client_info( $conn))
{
    // 以foreach 迴圈顯示 $client_info 陣列內容
    foreach( $client_info as $key => $value)
    {
        echo $key.":".$value."</br>";
    }
}
// 若叫用結果為 false,表示叫用失敗
else
{
    echo "sqlsrv_client_info函式執行有誤</br>";
}


圖1 sqlsrv_client_info 函式的執行結果

sqlsrv_server_info:檢視用戶端及伺服端資訊

與 sqlsrv_client_info 函式相當類似的是,sqlsrv_server_info 函式如果執行失敗也會傳回 false,若執行成功,則會將以下資訊放置在關聯陣列。

索引鍵

說明

CurrentDatabase

目前列為目標的資料庫

SQLServerVersion

SQL Server 版本序號

SQLServerName

伺服器名稱

以下是 sqlsrv_server_info 函式的使用範例及執行結果。

// $conn 內容是 PHP 連接資源
// 叫用 sqlsrv_server_info 函式取得用戶端資訊
if( $server_info = sqlsrv_server_info( $conn))
{
    // 以foreach 迴圈顯示 $server_info 陣列內容
    foreach( $server_info as $key => $value)
    {
        echo $key.":".$value."</br>";
    }
}
// 若叫用結果為 false,表示叫用失敗
else
{
    echo "sqlsrv_server_info 函式執行有誤 </br>";
}


圖2 sqlsrv_server_info 函式的執行結果

取得資料

資料庫查詢結果會傳回資料列,只要利用不同的 sqlsrv_fetch_array、sqlsrv_fetch_object、sqlsrv_fetch 等函式,我們即可以陣列、物件、資料流等不同形式來取得資料,而且也能取得單一欄位或多筆結果的資料。

這些函式是以查詢的執行結果作為參數,而且若執行成功,會傳回所取得的資料,但若執行失敗,會傳回 false;如果執行成功但未能取得資料,則會傳回 null(可以利用 PHP 函式 is_null 來檢查)。

sqlsrv_fetch_array:以陣列傳回下一筆資料

若執行成功,sqlsrv_fetch_array 函式會傳回存放資料的陣列,而且可以是數值索引的陣列,也可以是關聯陣列。使用 sqlsrv_fetch_array 函式時,必須將查詢的結果指定為第 1 個參數,第 2 個參數可選擇,用來指定傳回數值索引陣列或關聯陣列(預設兩者皆傳回)。

sqlsrv_fetch_array( resource $stmt[ , int $fetchType])

第 2 個參數能以如下的常數指定:SQLSRV_FETCH_NUMERIC(數值索引陣列)、SQLSRV_FETCH_ASSOC(關聯陣列)、SQLSRV_FETCH_BOTH(兩者,此為預設值)。

以下是 sqlsrv_fetch_array 函式的使用範例及執行結果,其中是使用數值索引陣列。

// 定義查詢
$tsql = "SELECT ProductID,
            UnitPrice,
            StockedQty 
        FROM Purchasing.PurchaseOrderDetail
        WHERE StockedQty < 3 
            AND DueDate='2002-01-29'";
// 執行查詢
// $conn 內容是 PHP 連接資源
$stmt = sqlsrv_query( $conn, $tsql);
if ( $stmt )
{
    echo "已執行 SQL 查詢";
} 
else 
{
    echo "查詢失敗,錯誤訊息如下:";
    die( print_r( sqlsrv_errors(), true));
}
// 以迴圈顯示陣列裡的查詢結果
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC))
{
    echo "ProdID: ".$row[0]."</br>";
    echo "UnitPrice: ".$row[1]."</br>";
    echo "StockedQty: ".$row[2]."</br>";
    echo "-----------------</br>";
}
// 釋放查詢及連接資源
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);
?>


圖3 sqlsrv_fetch_array 函式的執行結果

上述範例是使用數值索引陣列,如果要使用關聯陣列,需調整如下:

while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC))
{
    echo "ProdID: ".$row['ProductID']."<\br>";
    echo "UnitPrice: ".$row['UnitPrice']."<\br>";
    echo "StockedQty: ".$row['StockedQty']."<\br>";
    echo "-----------------<\br>";
}

sqlsrv_fetch_object:以物件傳回下一筆資料

sqlsrv_fetch_object 函式能以物件的形式取得資料列,而且我們可以自訂類別來存放所取得的物件,也能使用預設的 stdClass 類別。以下的例子是使用預設的類別。

// 已建立 PHP 連接資源,也已執行查詢 ($stmt)
// $obj 的內容是PHP物件形式的資料列
while( $obj = sqlsrv_fetch_object( $stmt))
{
    // 一一顯示物件裡的 LastName 及 FirstName
    echo $obj->LastName.", ".$obj->FirstName."</br>";
}

以下是自訂類別的例子,與前者最大差別,是這種作法要先定義類別、建構函式,然後在執行 sqlsrv_fetch_object 函式時,必須以第2個參數指定自訂的類別。

// 定義 Product 類別
class Product
{
    // 建構函式
    public function Product($ID)
    {
        $this->objID = $ID;
    }
    public $objID;
    public $name;
    public $StockedQty;
    public $SafetyStockLevel;
    private $UnitPrice;
    // method
    function getPrice()
    {
        return $this->UnitPrice;
    }
}
// 省略連接 SQL Server、執行查詢等程式碼
$i=0;
// 以下叫用 sqlsrv_fetch_object,是以第 2 個參數指定自訂的類別
while( $product = sqlsrv_fetch_object( $stmt, "Product", array($i)))
{
    echo "Object ID: ".$product->objID."</br>";
    echo "Product Name: ".$product->Name."</br>";
    echo "Stocked Qty: ".$product->StockedQty."</br>";
    echo "Safety Stock Level: ".$product->SafetyStockLevel."</br>";
    echo "Product Color: ".$product->Color."</br>";
    echo "Unit Price: ".$product->getPrice()."</br>";
    echo "-----------------</br>";
    $i++;
}

sqlsrv_fetch / sqlsrv_get_field:取得單一欄位資料

如果只是想取得某一筆資料列的單一欄位,可以搭配使用 sqlsrv_fetch和sqlsrv_get_field,前者能讓下一筆資料列變得可讀,後者則可讀取目前資料列的特定欄位(如果必須指定回傳資料的 PHP 資料型別,或必須以資料流的方式取得資料,就應該使用 sqlsrv_get_field)。

sqlsrv_fetch( resource $stmt)
sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType])

以下的範例會先取得 Purchasing 資料表裡特定日期和特定庫存量的產品資訊,再利用 sqlsrv_fetch 函式重複檢視查詢結果的每一筆資料列,然後再以 sqlsrv_get_field 函式取得特定欄位的資料。

// 定義查詢
$tsql = "SELECT ProductID,
                UnitPrice,
                StockedQty 
        FROM Purchasing.PurchaseOrderDetail
        WHERE StockedQty < 3 
        AND DueDate='2002-01-29'";
// 省略連接 SQL Server、執行查詢等程式碼,$stmt 是查詢結果
// 以 while 迴圈重複檢視查詢結果的每一筆資料列
while( sqlsrv_fetch( $stmt))
{
    // 以 sqlsrv_get_field 函式取得特定欄位的資料
    echo "ProdID: ".sqlsrv_get_field($stmt, 0)."<\br>";
    echo "UnitPrice: ".sqlsrv_get_field($stmt, 1)." <\br>";
    echo "StockedQty: ".sqlsrv_get_field($stmt, 2)." <\br>";
    echo "-----------------<\br>";
}

sqlsrv_next_result:處理多筆資料

SQL Server 2005 Driver for PHP 的 sqlsrv_next_result 函式能讓 PHP 程式繼續處理下一筆結果。

sqlsrv_next_result( resource $stmt )

以下的例子會執行批次查詢來取得特定產品編號的評論資訊、插入產品評論,然後再次取得特定產品編號的評論資訊,因此就能看到新加入的產品評論。這個例子是以 sqlsrv_next_result 函式將批次查詢的結果移往下一筆。

// 省略連接SQL Server的程式碼
// 定義批次查詢
$tsql = "--Query 1
        SELECT ProductID, ReviewerName, Rating 
        FROM Production.ProductReview 
        WHERE ProductID=?;

// 省略連接 SQL Server 的程式碼
// 定義批次查詢
        --Query 2
        INSERT INTO Production.ProductReview (ProductID, 
                                                ReviewerName, 
                                                ReviewDate, 
                                                EmailAddress, 
                                                Rating)
        VALUES (?, ?, ?, ?, ?);

        --Query 3
        SELECT ProductID, ReviewerName, Rating 
        FROM Production.ProductReview 
        WHERE ProductID=?;";

// 指定參數值並執行查詢
$params = array(798, 
                798, 
                'CustomerName', 
                '2008-4-15', 
                'test@customer.com', 
                3, 
                798 );
$stmt = sqlsrv_query($conn, $tsql, $params);

// 取得並顯示第 1 次查詢的結果
echo "查詢 1 的結果:<\br>";
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ))
{
    print_r($row);
}
// 以sqlsrv_next_result 函式將批次查詢的結果移往下一筆
sqlsrv_next_result($stmt);
// 取得並顯示第 2 次查詢的結果
echo "查詢 2 的結果:<\br>";
echo "資料已更新:".sqlsrv_rows_affected($stmt)."</br>";
// 以 sqlsrv_next_result 函式將批次查詢的結果移往下一筆
sqlsrv_next_result($stmt);
// 取得並顯示第 3 次查詢的結果
echo "查詢 3 的結果:<\br>";
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ))
{
    print_r($row);
}

更新資料

SQL Server 2005 Driver for PHP 除了能以參數化查詢的方式更新資料,也能以資料流的方式傳送資料(包括送進及送出資料庫),並且支援資料交易。以下將說明兩種參數化查詢的用法。

參數化查詢

SQL Server 2005 Driver for PHP 提供了 sqlsrv_query 或 sqlsrv_prepare / sqlsrv_execute 函式用作查詢的執行。sqlsrv_query 可以用作一次查詢,而且適用多數情形。sqlsrv_prepare / sqlsrv_execute 是一對互相搭配的函式,將陳述式分成「準備」和「執行」兩部份,適用於多次查詢。

以下是以更新 Production 資料表某些產品編號的數量為例,說明如何以 sqlsrv_query 函式來執行參數化查詢。

// 定義查詢字串,這裡要更新 Production 資料表的某些產品編號的數量(數量亦未知)
$tsql1 = "UPDATE Production
        SET Quantity = ?
        WHERE ProductID = ?";
// 初始或更新對應到 T-SQL 查詢替代符號的 PHP 變數
// 本例欲將編號為 709 的產品數量更新成 10
$qty = 10;
$productId = 709;
// 以 sqlsrv_query 函式來執行查詢
$stmt1 = sqlsrv_query( $conn, $tsql1, array($qty, $productId));

sqlsrv_prepare / sqlsrv_execute 函式

以下的例子示範了 sqlsrv_prepare / sqlsrv_execute 函式的用法,此例會將數筆訂單插入 Sales資 料表。其中在叫用 sqlsrv_prepare 函式時,會將 $params 陣列繫結到陳述式 ($stmt),而每次插入新訂單之前,會以對應到訂單細節的新值來更新 $params 陣列。執行後面的查詢時,則會用到新的參數值。

// 定義查詢字串
$tsql = "INSERT INTO Sales (SalesOrderID, 
                        OrderQty, 
                        ProductID, 
                        SpecialOfferID, 
                        UnitPrice)
        VALUES (?, ?, ?, ?, ?)";

/*  初始或更新對應到 T-SQL 查詢替代符號的PHP變數
    以下的每個子陣列會是查詢的參數陣列
    每個子陣列裡的順序是
    SalesOrderID、OrderQty、ProductID、SpecialOfferID、UnitPrice */
$parameters = array( array(43659, 8, 711, 1, 20.19),
                    array(43660, 6, 762, 1, 419.46),
                    array(43661, 4, 741, 1, 818.70));
// 初始參數值
$orderId = 0;
$qty = 0;
$prodId = 0;
$specialOfferId = 0;
$price = 0.0;
// 準備
$stmt = sqlsrv_prepare( $conn, $tsql, array( $orderId,
                                    $qty,
                                    $prodId,
                                    $specialOfferId,
                                    $price));
// sqlsrv_ prepare 函式執行失敗會傳回false
if( $stmt === false )
{
    // 向使用者顯示執行失敗的訊息,下一行程式可顯示系統的錯誤訊息
    die( print_r( sqlsrv_errors(), true));
}
// 執行 $parameters 裡每組參數的陳述式
foreach( $parameters as $params)
{
    list($orderId, $qty, $prodId, $specialOfferId, $price) = $params;
    if( sqlsrv_execute($stmt) === false )
    {
        // 向使用者顯示執行失敗的訊息,下一行程式可顯示系統的錯誤訊息
        die( print_r( sqlsrv_errors(), true));
    }
    else
    {
        // 顯示新的資料列,以確認資料列已成功插入
        echo "資料列已改變: ".sqlsrv_rows_affected( $stmt )."<\br>";
    }
}
// 釋放查詢的 PHP 資源
sqlsrv_free_stmt( $stmt);

轉換資料型別

PHP 程式從資料庫取出資料、或將資料送回資料庫時,可能需要轉換資料型別,這涉及 PHP 本身的資料型別和 SQL Server 資料型別的差異,而轉換分成兩種:一是系統以預設資料型別自動轉換,另一種是我們以指定的型別自行轉換。

資料送回資料庫的型別轉換

當資料送回資料庫時,如果我們沒有自行轉換,SQL Server 2005 Driver for PHP 會根據下表 SQL Server 預設的資料型別,將 PHP 的資料型別轉換成 SQL Server 資料型別。

PHP 資料類別

SQL Server 預設的資料型別

NULL

varchar(1)

Boolean

bit

Integer

int

Float

float(24)

String (長度小於 8000 位元組)

varchar(<字串長度>)

String (長度超過 8000 位元組)

varchar(max)

Resource

Not supported.

Stream (非二進位編碼)

varchar(max)

Stream (二進位編碼)

varbinary

Integer

int

Array

不支援

Object

不支援

DateTime

datetime

從資料庫取出資料的型別轉換

反之,從資料庫取出資料時,如果我們沒有自行轉換資料型別,SQL Server 2005 Driver for PHP 會根據下表,將 SQL Server 資料型別轉換成 PHP 資料型別。

SQL Server 資料型別

預設的 PHP 資料型別

預設的編碼方式

bigint

String

8位元字元¹

binary

Stream²

Binary³

bit

Integer

8位元字元¹

char

String

8位元字元¹

datetime

Datetime

不適用

decimal

String

8位元字元¹

float

Float

8位元字元¹

image4

Stream²

Binary³

int

Integer

8位元字元¹

money

String

8位元字元¹

nchar

String

8位元字元¹

numeric

String

8位元字元¹

nvarchar

String

8位元字元¹

nvarchar(MAX)

Stream²

8位元字元¹

ntext5

Stream²

8位元字元¹

real

Float

8位元字元¹

smalldatetime

Datetime

8位元字元¹

smallint

Integer

8位元字元¹

smallmoney

String

8位元字元¹

sql_variant

String

8位元字元¹

text6

Stream²

8位元字元¹

timestamp

Stream²

8位元字元¹

tinyint

Integer

8位元字元¹

UDT

Stream²

Binary³

uniqueidentifier

String7

8位元字元¹

varbinary

Stream²

Binary³

varbinary(MAX)

Stream²

Binary³

varchar

String

8位元字元¹

varchar(MAX)

Stream²

8位元字元¹

variant

不支援

不支援

xml

Stream²

8位元字元¹

以下請注意:

  1. 資料會根據 Windows 系統地區設定的字碼頁將資料轉換成 8 位元字元,無法對映到此字碼頁的字元,會被換成單位元組的問號(?)。
  2. 若以 sqlsrv_fetch_array 或 sqlsrv_fetch_object 取得 PHP Stream 型別資料,資料會以字串回傳(但編碼與 Stream 相同)。例如,若以 sqlsrv_fetcharray 取回 SQL Server 二進位型別,回傳的預設型別會是二進位字串。
  3. 回傳時,將資料視為來自伺服端且未經編碼或轉換的未處理的 byte stream。
  4. 這是對映到 varbinary(max) 型別的傳統型別。
  5. 這是對映到 nvarchar(max) 型別的傳統型別。
  6. 這是對映到 varchar(max) 型別的傳統型別。
  7. UNIQUEIDENTIFIER 是由以下規則運算式所代表的 GUID:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-f]{4}-[0-9a-fA-f]{4}-[0-9a-fA-F]{12}。

轉換成 SQL Server 型別

如果要指定存回 SQL Server 資料庫的資料型別,可以在準備或執行插入或更新資料等查詢時,利用 $params 陣列。以下將資料存回資料庫的例子,將 $changeDate、$rate、$payFrequency 的型別分別指定成 SQLSRV_SQLTYPE_DATETIME、SQLSRV_SQLTYPE_MONEY、SQLSRV_SQLTYPE_TINYINT,沒有指定型別的 $employeeId,則回使用預設值。

// 定義查詢
$tsql1 = "INSERT INTO HumanResources.EmployeePayHistory (EmployeeID,
                                                    RateChangeDate,
                                                    Rate,
                                                    PayFrequency)
        VALUES (?, ?, ?, ?)";
// 建構參數陣列
$employeeId = 5;
$changeDate = "2005-06-07";
$rate = 30;
$payFrequency = 2;
$params1 = array(
                // EmployeeID 沒有指定 SQL Server 資料型別,會使用預設型別
                array($employeeId, null),
                // datetime:SQLSRV_SQLTYPE_DATETIME
                array($changeDate, null, null, SQLSRV_SQLTYPE_DATETIME),
                // money:SQLSRV_SQLTYPE_MONEY
                array($rate, null, null, SQLSRV_SQLTYPE_MONEY),
                // tinyint:SQLSRV_SQLTYPE_TINYINT
                array($payFrequency, null, null, SQLSRV_SQLTYPE_TINYINT));
// 執行插入查詢
$stmt1 = sqlsrv_query($conn, $tsql1, $params1);

轉換成 PHP 資料型別

與前述相反的是,從 SQL Server 資料庫取出的資料,可能需要自行指定成 PHP 的資料型別。此時我們可以使用 sqlsrv_get_field 函式,並且將欲指定的 PHP 資料型別當成函式的第 3 個參數。當然,欲使用 sqlsrv_get_field 函式,必須先搭配使用 sqlsrv_fetch 函式。

// 定義 T-SQL 查詢
$tsql = "SELECT ReviewerName, 
            ReviewDate,
            Rating, 
            Comments 
        FROM Production.ProductReview 
        WHERE ProductID = ? 
        ORDER BY ReviewDate DESC";
// 設定參數值
$productID = 709;
$params = array( $productID);
// 執行查詢
$stmt = sqlsrv_query($conn, $tsql, $params);
// 取回並顯示資料;取回資料的同時,亦指定成 PHP 資料型別
while ( sqlsrv_fetch( $stmt))
{
    // 不指定;使用預設型別
    echo "Name: ".sqlsrv_get_field( $stmt, 0 )."</br>";
    // 指定成 8 位元字元編碼的字串
    echo "Date: ".sqlsrv_get_field( $stmt, 1, SQLSRV_PHPTYPE_STRING( SQLSRV_ENC_CHAR))."</br>";
    // 不指定;使用預設型別
    echo "Rating: ".sqlsrv_get_field( $stmt, 2 )."</br>";
    echo "Comments: ";
    // 指定成 8 位元字元編碼的資料流
    $comments = sqlsrv_get_field( $stmt, 3, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR));
    fpassthru( $comments);
    echo "</br>"; 
}

將 UTF-8 編碼資料存回伺服器

首先,資料庫目的資料行的型別必須是 nchar 或 nvarchar。然後要以 PHP 的 iconv 函式將資料轉成 UTF-16LE 編碼,例如 $data 是 UTF-8 編碼的變數,以下將可轉換成 UTF-16LE:

$data = iconv("utf-8", "utf-16le", $data);
接著要在參數陣列將 PHP 型別指定成 SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY):
$params = array( array($data, 
                    null, 
                    SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)));

從資料庫取得資料後轉成 UTF-8 編碼

首先,資料庫裡的資料是以 UTF-16LE 編碼。接著要以 sqlsrv_get_field 函式取得資料,然後將 PHP 資料型別指定成 SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY):

$data = sqlsrv_get_field($stmt, 0, 
                    SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY));
接著利用 PHP 的 iconv 函式將取得的資料轉換成 UTF-8(原本是 UTF-16LE 編碼):
$data = iconv("utf-16le", "utf-8", $data);

錯誤處理

SQL Server 2005 Driver for PHP 預設會將警告視為錯誤,因此如果叫用 SQL Server 2005 Driver for PHP 所屬的函式產生了警告或錯誤,都會傳回 false。但我們也可以利用 sqlsrv_configure 函式來關閉這項功能。例如 PHP 程式若包含以下程式,就會讓 SQL Server 2005 Driver for PHP 所屬的函式產生錯誤時才會傳回 false(也就是若產生警告,並不會傳回 false):

sqlsrv_configure("WarningsReturnAsErrors", 0);

如果將上述程式裡的 0 改成 1,就會恢復成預設狀態。不論改成 0 或 1,只會影響執行的 PHP 程式。

此外,利用 php.ini 的 [sqlsrv] 也可以設定上述的 WarningsReturnAsErrors(這會影響整個 PHP 執行環境):

sqlsrv.WarningsReturnAsErrors = 0

取得細節

SQL Server 2005 Driver for PHP 提供的 sqlsrv_errors 函式,能傳回最近一次 SQL Server 2005 Driver for PHP 運作的錯誤或警告詳細資訊:

sqlsrv_errors( [int $errorsAndOrWarnings] )

其中的可選擇參數 $errorsAndOrWarnings,是用來指定 sqlsrv_errors 傳回何種類型的錯誤,包括:

  • SQLSRV_ERR_ALL:傳回錯誤和警告,此為預設值。
  • SQLSRV_ERR_ERRORS:傳回錯誤。
  • SQLSRV_ERR_WARNINGS:傳回警告。

sqlsrv_errors 函式會傳回陣列或 null 型別的資料,null 表示沒有最近沒有產生錯誤或警告,而陣列則包含了 3 組鍵/值(以下鍵值亦可是 0、1、2 等數值):

  • SQLSTATE:
    • 若是源自 ODBC 驅動程式的錯誤,SQLSTATE 是由 ODBC 傳回,細節可參考 ODBS 錯誤碼
    • 若是源自 SQL Server 2005 Driver for PHP 的錯誤,SQLSTATE 為 IMSSP。
    • 若是源自 SQL Server 2005 Driver for PHP 的警告,SQLSTATE 為 01SSP。
  • code:
    • 若是源自 SQL Server 的錯誤,是 SQL Server 本生的錯誤碼。
    • 若是源自 ODBC 驅動程式的錯誤,錯誤碼會由 ODBC 傳回。
    • 若是源自 SQL Server 2005 Driver for PHP 的錯誤,錯誤碼會由 SQL Server 2005 Driver for PHP 傳回。
  • message:描述錯誤的訊息。

顯示錯誤或警告訊息

我們可以用 print_r 來顯示整個物件:

print_r(sqlsrv_errors());

也可以一一顯示物件的內容:

$errors = sqlsrv_errors();
foreach( $errors as $error)
{
    echo "SQLSTATE: ".$error[ 'SQLSTATE']."</br>";
    echo "code: ".$error[ 'code']."</br>";
    echo "message: ".$error[ 'message']."</br>";
}
不過,處理之前,最好先檢查是不是真的有錯誤或警告:
if( ($errors = sqlsrv_errors() ) != null)
{
    // 有錯誤或警告
}

結語

SQL Server 2005 Driver for PHP 提高了 PHP 程式存取 Microsoft SQL Server 的透通性,尤其是較新的 SQL Server 2005、或最新的 SQL Server 2008。目前的 SQL Server 2005 Driver for PHP 1.0 提供了 24 個函式,供作 PHP 程式存取 SQL Server 之用。雖然本文花了相當的篇幅及例子在說明這些函式,但依然無法盡數說完所有的函式;就算已說明的函式,也無法完全解說所有細節。

因此完整的說明,需請您參閱微軟提供的文件(SQL Server Driver for PHP Documentation);除了可於微軟網站查閱,下載回來的 SQL Server 2005 Driver for PHP 檔案(SQLServerDriverForPHP.EXE),也包含內容相同的線上說明檔(SQLServerDriverForPHP_1.0.8204.chm)。


參考資料