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));
}