[置頂] MySQL C API預處理函數call存儲過程


drop procedure if exists SelectAll;
delimiter $
create procedure SelectAll(in TableName char(64))
begin
set @sql = 'select * from ?';
prepare stmt from @sql;
set @p = TableName;
execute stmt using @p;
deallocate prepare stmt;
end$
delimiter ;

首先拿出錯誤的做法,動態SQL參數是表名。然后下面給出正確的做法。

drop procedure if exists SelectAll;
delimiter $
create procedure SelectAll(in TableName char(64))
begin
set @sql = concat('select * from ', TableName);
prepare stmt from @sql;
execute stmt;
deallocate prepare stmt;
end$
delimiter ;

用變量做表名,簡單的用set或者declare語句定義變量,然后直接作為sql的表名是不行的,mysql會把“變量名”當作表名,也就是說這個時候傳進去的不是變量的值,而是變量的名字。解決方法是將整條sql語句作為變量,其中穿插變量作為表名,用concat連接組成SQL語句,然后用execute stmt調用該語句。當然這里不僅僅是表名要注意,表名和列名都是如此。set @p = value這樣定義的變量直接寫在字符串中就會被當作變量轉換,declare的變量和參數傳入的變量則必須用concat來連接。execute stmt using @p,這樣的語句using后面的變量也只能用set @p = value這種,declare和參數傳入的變量不行。 同理使用MySQL C API預處理語句函數的時候”?”代表的位置也不能是列名和表名。

if (!m_stmt)
{
return 1;
}
if (mysql_stmt_bind_param(m_stmt, m_param_bind))
{
return 1;
}
m_result = mysql_stmt_result_metadata(m_stmt);
if (NULL == m_result)
{
return 1;
}
else
{
m_result_count = mysql_num_fields(m_result);
m_result_bind = new MYSQL_BIND[m_result_count];
if (!m_result_bind)
{
return 1;
}
memset(m_result_bind, 0, sizeof(MYSQL_BIND)*m_result_count);
}
if (mysql_stmt_execute(m_stmt))
{
return 1;
}

我們使用MySQL C API取結果集的時候,通常都是這樣的一套做法,其中使用mysql_stmt_result_metadata是為了獲取我們結果集的列數,這樣才能初始化結果集bind數組的個數,當然還有一個方法使用mysql_stmt_field_count

if (!m_stmt)
{
return 1;
}
if (mysql_stmt_bind_param(m_stmt, m_param_bind))
{
return 1;
}
m_result_count = mysql_stmt_field_count(m_stmt);
if (m_result_count > 0)
{
m_result_bind = new MYSQL_BIND[m_result_count];
if (!m_result_bind)
{
return 1;
}
memset(m_result_bind, 0, sizeof(MYSQL_BIND)*m_result_count);
}
if (mysql_stmt_execute(m_stmt))
{
return 1;
}

但是這里有一個坑,我做了測試,使用MySQL C API預處理語句函數call存儲過程時,在mysql_stmt_result_metadata這里會返回NULL,但是你注釋掉這段,取結果,其實是有結果的,在mysql_stmt_field_count這里是返回的是0,我當時一直以為是自己的存儲過程,或者變量沒有傳正確造成的。MySQL手冊中說mysql_stmt_result_metadata可以檢測SQL是否有查詢結果,可是這里返回了NULL,卻明明有結果集。

后記:2016-12-28
一次偶然的機會,我瀏覽到MySQL手冊的官方英文文檔,原諒我之前一直都是讀的MySQL5.1手冊中文版。在我之前的一篇文章《C++封裝MySQL預處理語句》都是參考的5.1版本手冊使用C API預處理語句的實例,當然即使到了5.7.11版本還是沒問題,我那一套API調用過程都是對的,這里說的是調用基本的SQL語句是沒有任何問題的。但是對於調用存儲過程,發生了一些變化。在手冊中關於mysql_stmt_result_metadata和mysql_stmt_field_count的使用,都是說在mysql_stmt_prepare后調用就沒問題,但是對於存儲過程就會造成上述NULL和0的問題。然后我仔細啃了一下英文文檔,我比較了5.5-5.7版本這個鏈接的章節內容都是一樣的,重要的部分是這個例子。
原文鏈接:https://dev.mysql.com/doc/refman/5.5/en/c-api-prepared-call-statements.html,這個可以說是調用存儲過程的正確API調用順序,https://dev.mysql.com/doc/refman/5.5/en/mysql-stmt-fetch.html這個是適用於調用普通SQL語句的API調用順序。它們的區別就在於,如果你調用的是普通SQL語句,那么只要滿足手冊中說的,mysql_stmt_result_metadata和mysql_stmt_field_count都是在mysql_stmt_prepare后就沒問題,但是如果你調用的是存儲過程,那么mysql_stmt_result_metadata和mysql_stmt_field_count必須在mysql_stmt_execute后面才能得到列數。這個結論,我分別在MySQL5.6.30和MySQL5.7.11版本測試過,成功!以下是我優化后可以通用普通SQL語句和存儲過程的調用過程:

if (!m_stmt)
{
return 1;
}
if (mysql_stmt_bind_param(m_stmt, m_param_bind))
{
printf("%s", mysql_stmt_error(m_stmt));
return 1;
}
if (mysql_stmt_execute(m_stmt))
{
printf("%s", mysql_stmt_error(m_stmt));
return 1;
}
/*
m_result = mysql_stmt_result_metadata(m_stmt);
if (NULL == m_result)
{
printf("%s", mysql_stmt_error(m_stmt));
return 1;
}
else
{
m_result_count = mysql_num_fields(m_result);
if (m_result_count > 0)
{
m_result_bind = new MYSQL_BIND[m_result_count];
if (!m_result_bind)
{
return 1;
}
memset(m_result_bind, 0, sizeof(MYSQL_BIND)*m_result_count);
}
else
{
printf("%s", mysql_stmt_error(m_stmt));
return 1;
}
}
*/
m_result_count = mysql_stmt_field_count(m_stmt);
if (m_result_count > 0)
{
m_result_bind = new MYSQL_BIND[m_result_count];
if (!m_result_bind)
{
return 1;
}
memset(m_result_bind, 0, sizeof(MYSQL_BIND)*m_result_count);
}
else
{
printf("%s", mysql_stmt_error(m_stmt));
return 1;
}

最后還有一點,官方文檔提到了call存儲過程可能會產生多個結果集的問題,需要調用mysql_next_result來逐個釋放:

if (m_stmt)
{
mysql_stmt_free_result(m_stmt);
mysql_stmt_close(m_stmt);
m_stmt = NULL;
do
{
MYSQL_RES *result = mysql_store_result(m_mysql);
if (NULL != result)
{
mysql_free_result(result);
result = NULL;
}
}
while (0 == mysql_next_result(m_mysql));
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com