SQL語句執行與結果集的獲取
title: SQL語句執行與結果集的獲取
tags: [OLEDB, 數據庫編程, VC++, 數據庫]
date: 2018-01-28 09:22:10
categories: windows 數據庫編程
keywords: OLEDB, 數據庫編程, VC++, 數據庫,執行SQL, 獲取結果集
---
上次說到命令對象是用來執行SQL語句的。數據源在執行完SQL語句後會返回一個結果集對象,將SQL執行的結果返回到結果集對象中,應用程序在執行完SQL語句後,解析結果集對象中的結果,得到具體的結果,這次的主要內容是如何解析結果集對象並獲取其中的值。
如何執行SQL語句
執行SQL語句一般的步驟如下:
- 創建ICommandText接口.
- 使用ICommandText接口的SetCommandText方法設置SQL命令
- 使用ICommandText接口的Excute方法執行SQL語句並接受返回的結果集對象,這個結果集對象一般是IRowset.
其實OLEDB並不一定非要傳入SQL語句,他可以傳入簡單的命令,只要數據源能夠識別,也就是說我們可以根據數據源的不同傳入那些只有特定數據源才會支持的命令,已達到簡化操作或者實現某些特定功能的目的.
針對有的SQL語句,我們並不是那麽關心它返回了那些數據,比如說Delete語句,insert語句,針對這種情況我們可以將對應返回結果集的參數設置為NULL,比如像下面這樣
pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL)
明確告訴數據源程序不需要返回結果集,這樣數據源不會準備結果集,減少了數據源的相關操作,從某種程度上減輕了數據源的負擔。
設置command對象的屬性
與之前數據源對象和會話對象的屬性不同,command對象的屬性是作用在返回的數據源對象上的,比如我們沒有設置對應的更新屬性,那麽數據源就不允許我們使用結果集進行更新數據的操作。這些屬性必須在執行SQL語句得到結果集的操作之前定義好。因為在獲得數據源返回的結果集的時候數據源已經設置了對應的屬性。
command對象的屬性集ID是PROPSET_ROWSET.該屬性集中有很多能夠影響結果集對象的屬性。
下面是一個執行SQL語句的例子:
LPOLESTR lpSql = OLESTR("select * from aa26");
CreateDBSession(pIOpenRowset);
HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand);
COM_SUCCESS(hRes, _T("查詢接口IDBCreateCommand失敗,錯誤碼:%08x\n"), hRes);
hRes = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&pICommandText);
COM_SUCCESS(hRes, _T("創建接口IDBCreateCommand失敗,錯誤碼:%08x\n"), hRes);
hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql);
COM_SUCCESS(hRes, _T("設置sql語句失敗,錯誤碼:%08x\n"), hRes);
DBPROP dbProp[16] = {0};
DBPROPSET dbPropset[1] = {0};
//設置結果集可以進行增刪改操作
dbProp[0].colid = DB_NULLID;
dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
dbProp[0].dwPropertyID = DBPROP_UPDATABILITY;
dbProp[0].vValue.vt = VT_I4;
dbProp[0].vValue.lVal = DBPROPVAL_UP_DELETE | DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT;
//申請打開書簽功能
dbProp[1].colid = DB_NULLID;
dbProp[1].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[1].dwPropertyID = DBPROP_BOOKMARKS;
dbProp[1].vValue.vt = VT_BOOL;
dbProp[1].vValue.boolVal = VARIANT_TRUE;
//申請打開行查找功能
dbProp[2].colid = DB_NULLID;
dbProp[2].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[2].dwPropertyID = DBPROP_IRowsetFind;
dbProp[2].vValue.vt = VT_BOOL;
dbProp[2].vValue.boolVal = VARIANT_TRUE;
//申請打開行索引
dbProp[3].colid = DB_NULLID;
dbProp[3].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[3].dwPropertyID = DBPROP_IRowsetIndex;
dbProp[3].vValue.vt = VT_BOOL;
dbProp[3].vValue.boolVal = VARIANT_TRUE;
//申請打開行定位功能
dbProp[4].colid = DB_NULLID;
dbProp[4].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[4].dwPropertyID = DBPROP_IRowsetLocate;
dbProp[4].vValue.vt = VT_BOOL;
dbProp[4].vValue.boolVal = VARIANT_TRUE;
//申請打開行滾動功能
dbProp[5].colid = DB_NULLID;
dbProp[5].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[5].dwPropertyID = DBPROP_IRowsetScroll;
dbProp[5].vValue.vt = VT_BOOL;
dbProp[5].vValue.boolVal = VARIANT_TRUE;
//申請打開行集視圖功能
dbProp[6].colid = DB_NULLID;
dbProp[6].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[6].dwPropertyID = DBPROP_IRowsetView;
dbProp[6].vValue.vt = VT_BOOL;
dbProp[6].vValue.boolVal = VARIANT_TRUE;
//申請打開行集刷新功能
dbProp[7].colid = DB_NULLID;
dbProp[7].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[7].dwPropertyID = DBPROP_IRowsetRefresh;
dbProp[7].vValue.vt = VT_BOOL;
dbProp[7].vValue.boolVal = VARIANT_TRUE;
//申請打開列信息擴展接口
dbProp[8].colid = DB_NULLID;
dbProp[8].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[8].dwPropertyID = DBPROP_IColumnsInfo2;
dbProp[8].vValue.vt = VT_BOOL;
dbProp[8].vValue.boolVal = VARIANT_TRUE;
//申請打開數據庫同步狀態接口
dbProp[9].colid = DB_NULLID;
dbProp[9].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[9].dwPropertyID = DBPROP_IDBAsynchStatus;
dbProp[9].vValue.vt = VT_BOOL;
dbProp[9].vValue.boolVal = VARIANT_TRUE;
//申請打開行集分章功能
dbProp[10].colid = DB_NULLID;
dbProp[10].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[10].dwPropertyID = DBPROP_IChapteredRowset;
dbProp[10].vValue.vt = VT_BOOL;
dbProp[10].vValue.boolVal = VARIANT_TRUE;
dbProp[11].colid = DB_NULLID;
dbProp[11].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[11].dwPropertyID = DBPROP_IRowsetCurrentIndex;
dbProp[11].vValue.vt = VT_BOOL;
dbProp[11].vValue.boolVal = VARIANT_TRUE;
dbProp[12].colid = DB_NULLID;
dbProp[12].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[12].dwPropertyID = DBPROP_IGetRow;
dbProp[12].vValue.vt = VT_BOOL;
dbProp[12].vValue.boolVal = VARIANT_TRUE;
//申請打開行集更新功能
dbProp[13].colid = DB_NULLID;
dbProp[13].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[13].dwPropertyID = DBPROP_IRowsetUpdate;
dbProp[13].vValue.vt = VT_BOOL;
dbProp[13].vValue.boolVal = VARIANT_TRUE;
dbProp[14].colid = DB_NULLID;
dbProp[14].dwOptions = DBPROPOPTIONS_OPTIONAL;
dbProp[14].dwPropertyID = DBPROP_IConnectionPointContainer;
dbProp[14].vValue.vt = VT_BOOL;
dbProp[14].vValue.boolVal = VARIANT_TRUE;
dbPropset[0].cProperties = 15;
dbPropset[0].guidPropertySet = DBPROPSET_ROWSET;
dbPropset[0].rgProperties = dbProp;
hRes = pICommandText->QueryInterface(IID_ICommandProperties, (void**)&pICommandProperties);
COM_SUCCESS(hRes, _T("查詢接口ICommandProperties失敗,錯誤碼:%08x\n"), hRes);
hRes = pICommandProperties->SetProperties(1, dbPropset);
COM_SUCCESS(hRes, _T("設置屬性失敗,錯誤碼:%08x\n"), hRes);
hRes = pICommandText->Execute(NULL, IID_IRowset, NULL, NULL, (IUnknown**)&pIRowset);
COM_SUCCESS(hRes, _T("執行sql語句失敗,錯誤碼:%08x\n"), hRes);
這段代碼詳細的展示了如何執行SQL語句獲取結果集並設置COMMANDUI對象的屬性。
結果集對象
結果集一般是執行完SQL語句後返回的一個代表二維結構化數組的對象。這個結構化對象可以理解為一個與數據表定義相同的一個結構體。而結果集中保存了這個結構體的指針
下面是結果集對象的詳細定義
CoType TRowset {
[mandatory] interface IAccessor;
[mandatory] interface IColumnsInfo;
[mandatory] interface IConvertType;
[mandatory] interface IRowset;
[mandatory] interface IRowsetInfo;
[optional] interface IChapteredRowset;
[optional] interface IColumnsInfo2;
[optional] interface IColumnsRowset;
[optional] interface IConnectionPointContainer;
[optional] interface IDBAsynchStatus;
[optional] interface IGetRow;
[optional] interface IRowsetChange;
[optional] interface IRowsetChapterMember;
[optional] interface IRowsetCurrentIndex;
[optional] interface IRowsetFind;
[optional] interface IRowsetIdentity;
[optional] interface IRowsetIndex;
[optional] interface IRowsetLocate;
[optional] interface IRowsetRefresh;
[optional] interface IRowsetScroll;
[optional] interface IRowsetUpdate;
[optional] interface IRowsetView;
[optional] interface ISupportErrorInfo;
[optional] interface IRowsetBookmark;
}
結果集對象的一般用法
得到結果集後,它的使用步驟一般如下:
- 首先Query出IColumnsInfo接口
- 通過調用IColumnsInfo::GetColumnInfo方法得到關於結果集的列的詳細信息DBCOLUMNINFO結構的數組,包括:列序號,列名,類型,字節長度,精度,比例等
3.通過該結構數組,準備一個對應的DBBINDING結構數組,並計算每行數據實際需要的緩沖大小,並填充結構DBBINDING。這個過程一般叫做綁定
4.利用DBBINDING數組和IAccessor::CreateAccessor方法創建一個數據訪問器並得到句柄HACCESSOR - 調用IRowset::GetNextRow遍歷行指針到下一行,第一次調用就是指向第一行,並得到行句柄HROW,這個行句柄表示我們訪問的當前是結果中的第幾行,一般它的值是一個依次遞增的整數
- 調用IRowset::GetData傳入準備好的行緩沖內存指針,以及之前創建的訪問器HACCESSOR句柄和HROW句柄。最終行數據就被放置到了指定的緩沖中。循環調用GetNextRow和GetData即可遍歷整個二維結果集。
列信息的獲取
取得結果集對象後,緊接著的操作一般就是獲取結果集的結構信息,也就是獲取結果集的列信息(有些材料中稱為字段信息)要獲取列信息,就需要QueryInterface出結果集對象的IColumnsInfo接口,並調用IColumnsInfo::GetColumnInfo方法獲得一個稱為DBCOLUMNINFO結構體的數組該結構體中反映了列的邏輯結構信息(抽象數據類型)和物理結構信息(內存需求大小等信息)
函數GetColumnInfo定義如下:
HRESULT GetColumnInfo (
DBORDINAL *pcColumns,
DBCOLUMNINFO **prgInfo,
OLECHAR **ppStringsBuffer);
第一個參數表示總共有多少列,第二個參數是一個DBCOLUMNINFO,返回一個列信息的數組指針,第三個參數返回一個字符串指針,這個字符串中保存的是個列的名稱,每個名稱間以\0\0分割。但是我們一般不使用它來獲取列名,我們一般使用DBCOLUMNINFO結構的pwszName成員。
DBCOLUMNINFO定義如下:
typedef struct tagDBCOLUMNINFO {
LPOLESTR pwszName; //列名
ITypeInfo *pTypeInfo; //列的類型信息
DBORDINAL iOrdinal; //列序號
DBCOLUMNFLAGS dwFlags; //列的相關標識
DBLENGTH ulColumnSize; //列最大可能的大小,對於字符串來說,它表示的是字符個數
DBTYPE wType; //列類型
BYTE bPrecision; //精度(它表示小數點後面的位數)
BYTE bScale; //表示該列的比例,目前沒有用處,一般給的是0
DBID columnid; //列信息在數據字典表中存儲的ID
} DBCOLUMNINFO;
對於columnid成員,DBMS系統一般會有多個系統表來表示眾多的信息,比如用戶信息,數據庫信息,數據表信息等等,其中針對每個表中的列的相關信息DBMS系統使用特定的系統表來存儲,而查詢這個系統表來獲取列信息時使用的就是這個columnid值。
數據綁定
一般綁定需要兩步,1是獲取列信息的DBCOLUMNINFO結構,接著就是根據列信息來填充DBBINDING數據結構。
有的時候可能會覺得綁定好麻煩啊,還不如直接返回一個緩沖,將所有結果放入裏面,應用程序根據需求自己去解析它,這樣豈不是更方便。之所以需要綁定,有下面一個理由:
- 並不是所有的數據類型都能被應用程序支持,比如說數據庫中的NUMBER類型在VC++中找不到對應的數據結構來支持。
- 有時一行數據並不能完全讀取到內存中,比如說我們給的緩沖不夠或者是數據庫中的數據本身比較大,比如存儲了一個視頻文件等等。
- 在程序中並不是所有的訪問器都是為了讀取數據,而且使用返回所有結果的方式太簡單粗暴了,比如我只想要一列的數據那個數據可能占用內存不足1K,但是數據庫表中某一列數據特別大,可能占用內存會超過一個G,如果全都返回的話太浪費內存了。所以在綁定時候可以靈活的指定返回那些數據,返回數據長度是多少,針對特別大的數據,我們可以指定它只返回部分,比如只返回前面的1K
- 使用綁定可以靈活的安排返回數據在內存中的擺放形式。
綁定結構的定義如下:
typedef struct tagDBBINDING
{
DBORDINAL iOrdinal; //列號
DBBYTEOFFSET obValue;
DBBYTEOFFSET obLength;
DBBYTEOFFSET obStatus;
ITypeInfo *pTypeInfo;
DBOBJECT *pObject;
DBBINDEXT *pBindExt;
DBPART dwPart;
DBMEMOWNER dwMemOwner;
DBPARAMIO eParamIO;
DBLENGTH cbMaxLen; //數據的最大長度,一般給我們為這個列的數據準備的緩沖的長度
DWORD dwFlags;
DBTYPE wType; //將該列轉化為何種數據類型展示
BYTE bPrecision;
BYTE bScale;
}DBBINDING;
參數的詳細說明:
- obValue、obLength、obStatus:數據源在返回結果的時候一般會返回該列信息的三中數據,數據長度、數據狀態、數據值。數據狀態表示數據源在提供數據的一個狀態信息,比如該列信息為空時它會返回一個DBSTATUS_S_ISNULL,列數據比較長,而提供的數據可能不夠,這個時候會返回一個狀態表示發生了截斷。而數據長度表示返回結果的長度。這個值是返回的數據對應的字節長度,註意這裏需要與前面ulColumnSize區分開來。三個數據在內存中擺放的順序如下:
其中每列數據都是按照status length value結構排布,而不同的列的數據按照順序依次向後排放,這個內存的布局有點像結構體數組在內存中的的布局方式。而綁定結構中的obValue、obLength、obStatus規定了它們三者在一塊內存緩沖中的偏移,要註意後面一列的開始位置是在前面一列的結束位置而不是所有數據都是從0開始。 - dwPart:前面說數據源返回結果中有3個部分,但是我們可以指定數據源返回這3個部分的哪些部分,它的值是一些標誌位,根據這些標誌來決定需要返回哪些數據,不需要返回哪些數據.它的值主要有:DBPART_LENGTH、 DBPART_STATUS、DBPART_VALUE;
- dwMemOwner,這個值表示將使用何種內存緩沖來保存數據源返回的結果,我們一般使用DBMEMOWNER_CLIENTOWNED,表示使用用戶自定義內存的方式,即這個緩沖需要自行準備。
- eParamIO:我們將返回的值做何種用途,DBPARAMIO_NOTPARAM表示不做特殊用途,DBPARAMIO_INPUT,作為輸入值,一般在需要更新列數據的時候使用這個標誌,DBPARAMIO_OUTPUT,作為輸出值,這個輸出時相對於數據源來說的,表示輸出到應用程序程序緩沖,作為展示用。
wType:將數據源中的原始數據做何種類型的轉化,比如原來數據庫中存儲的是整數的123456,而這個值是DBTYPE_WSTR的話,數據源中的結果會被轉化為字符串的"123456",放入到緩沖中。
DBBINDING 與DBCOLUMNSINFO結構的比較
它們二者中有許多數據成員是相同的,表示的含義也基本相同,但是二者也有顯著的區別:- DBCOLUMNINFO是數據提供者給使用者的信息,它是固定的,對相同的查詢來說,列總是相同的,因此數據提供者返回的 DBCOLUMNINFO數組也是固定的.而DBBINDING是作為數據消費者創建之後給數據提供者的一個結構數組,它的內容則由調用者來完全控制,通過這個結構可以指定數據提供者最終將數據擺放成調用者指定的格式,並進行指定的數據類型轉換.針對相同的查詢我們可以指定不同的DBBINDINGS結構。
DBCOLUMNINFO反映的是二維結果集的原始列結構信息而DBBINDING則反映的是二維結果集數據最終按要求擺放在內存中的樣式
下面是一個針對綁定的列子:
```cpp
ExecSql(pIOpenRowset, pIRowset);//創建IColumnsInfo接口
HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo);
COM_SUCCESS(hRes, _T("查詢接口IComclumnsInfo,錯誤碼:%08x\n"), hRes);//獲取結果集的詳細信息
hRes = pIColumnsInfo->GetColumnInfo(&cClumns, &rgColumnInfo, &lpClumnsName);
COM_SUCCESS(hRes, _T("獲取列信息失敗,錯誤碼:%08x\n"), hRes);//綁定
pDBBindings = (DBBINDING)MALLOC(sizeof(DBBINDING) cClumns);
for (int iRow = 0; iRow < cClumns; iRow++)
{
pDBBindings[iRow].bPrecision = rgColumnInfo[iRow].bPrecision;
pDBBindings[iRow].bScale = rgColumnInfo[iRow].bScale;
pDBBindings[iRow].cbMaxLen = rgColumnInfo[iRow].ulColumnSize * sizeof(WCHAR);
if (rgColumnInfo[iRow].wType == DBTYPE_I4)
{
//數據庫中行政單位的長度最大為6位
pDBBindings[iRow].cbMaxLen = 7 * sizeof(WCHAR);
}
pDBBindings[iRow].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
pDBBindings[iRow].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE;
pDBBindings[iRow].eParamIO = DBPARAMIO_NOTPARAM;
pDBBindings[iRow].iOrdinal = rgColumnInfo[iRow].iOrdinal;
pDBBindings[iRow].obStatus = dwOffset;
pDBBindings[iRow].obLength = dwOffset + sizeof(DBSTATUS);
pDBBindings[iRow].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG);
pDBBindings[iRow].wType = DBTYPE_WSTR;dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindings[iRow].cbMaxLen * sizeof(WCHAR); dwOffset = COM_ROUNDUP(dwOffset); //進行內存對齊
}
//創建訪問器
hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
COM_SUCCESS(hRes, _T("查詢IAccessor接口失敗錯誤碼:%08x\n"), hRes);
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cClumns, pDBBindings, 0, &hAccessor, NULL);
COM_SUCCESS(hRes, _T("創建訪問器失敗錯誤碼:%08x\n"), hRes);//輸出列名信息
DisplayColumnName(rgColumnInfo, cClumns);
//分配對應的內存
pData = MALLOC(dwOffset * cRows);
while (TRUE)
{
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &uRowsObtained, &phRows);
if (hRes != S_OK && uRowsObtained != 0)
{
break;
}
ZeroMemory(pData, dwOffset * cRows);
//顯示數據
for (int i = 0; i < uRowsObtained; i++)
{
pCurrData = (BYTE)pData + dwOffset i;
pIRowset->GetData(phRows[i], hAccessor, pCurrData);
DisplayData(pDBBindings, cClumns, pCurrData);
}//清理hRows pIRowset->ReleaseRows(uRowsObtained, phRows, NULL, NULL, NULL); CoTaskMemFree(phRows); phRows = NULL;
}
//顯示列名稱
void DisplayColumnName(DBCOLUMNINFO *pdbColumnInfo, DBCOUNTITEM iDbCount)
{
COM_DECLARE_BUFFER();
for (int iColumn = 0; iColumn < iDbCount; iColumn++)
{
COM_CLEAR_BUFFER();
TCHAR wszColumnName[MAX_DISPLAY_SIZE + 1] =_T("");
size_t dwSize = 0;
StringCchLength(pdbColumnInfo[iColumn].pwszName, MAX_DISPLAY_SIZE, &dwSize);
dwSize = min(dwSize, MAX_DISPLAY_SIZE);
StringCchCopy(wszColumnName, MAX_DISPLAY_SIZE, pdbColumnInfo[iColumn].pwszName);
COM_PRINTF(wszColumnName);
COM_PRINTF(_T("\t"))
}
COM_PRINTF(_T("\n"));
}
//顯示數據
void DisplayData(DBBINDING pdbBindings, DBCOUNTITEM iDbColCnt, void pData)
{
COM_DECLARE_BUFFER();
for (int i = 0; i < iDbColCnt; i++)
{
COM_CLEAR_BUFFER();
DBSTATUS status = (DBSTATUS)((PBYTE)pData + pdbBindings[i].obStatus);
ULONG uSize = ((ULONG)((PBYTE)pData + pdbBindings[i].obLength)) / sizeof(WCHAR);
PWSTR pCurr = (PWSTR)((PBYTE)pData + pdbBindings[i].obValue);
switch (status)
{
case DBSTATUS_S_OK:
case DBSTATUS_S_TRUNCATED:
COM_PRINTF(_T("%s\t"), pCurr);
break;
case DBSTATUS_S_ISNULL:
COM_PRINTF(_T("%s\t"), _T("(null)"));
break;
default:
break;
}
}
COM_PRINTF(_T("\n"));
}
```
在使用前一個列子中的方法設置對應的屬性並執行SQL語句後,得到一個結果集,然後調用對應的Query方法,得到一個pIColumnsInfo接口,接著調用接口的GetColumnsInfo方法,獲取結構的具體信息。
最需要註意的是綁定部分的代碼,根據返回的具體列數,我們定義了一個對應的綁定結構的數組,將每個賦值,賦值的時候定義了一個dwOffset結構來記錄當前使用內存的情況,這樣每次在循環執行一次後,它的位置永遠在上一個列信息緩沖的尾部,這樣我們可以很方便的進行偏移的計算。
綁定完成後這個dwOffset的值就是所有列使用的內存的總大小,因此在後面利用這個值分配一個對應長度的內存。然後循環調用GetNextRows、GetData方法依次獲取每行、每列的數據。最後調用相應的函數來進行顯示,至此就完成了數據的讀取操作。
最後,我發現碼雲上的代碼片段簡直就是為保存平時例子代碼而生的,所以後面的代碼將不再在GitHub上更新了,而換到碼雲上面。
源代碼查看
SQL語句執行與結果集的獲取