我一直在寻找一种通过C / C ++中的ODBC API将整数数组传递给存储过程的方法。虽然VARCHAR(XX)
为数组类型的字符,但整数没有数组类型,例如VARINT(XX)
或类似的类型。
许多答案(例如How to send a big array to a stored procedure)建议使用表格值参数。但是,它们都使用 C#/ ADO.NET,Java或PHP 代码,其类型在C ODBC API中根本不可用。
我的存储过程:
-- "Array" Parameter type, used in the stored procedure later on
CREATE TYPE dbo.CharList
AS TABLE (Id INT)
GO
CREATE PROCEDURE [dbo].[Update_Character_Nation]
@OldNation_Id INT,
@NewNation_Id INT,
@Excluded dbo.CharList READONLY, -- array / table parameter
AS
BEGIN
SET NOCOUNT ON;
UPDATE Characters
SET Nation_Id = @NewNation_Id
WHERE Nation_Id = @OldNation_Id AND Id NOT IN (SELECT Id FROM @ExcludedChars) -- parameter used to exclude result sets here
END
所以我尝试了几件事:
1。直接传递数组
void UpdateCharacterNationTestArray()
{
unsigned int old_nation_id = 2;
unsigned int new_nation_id = 4;
unsigned int excluded_character_ids[] = {24, 36};
SQLLEN arr_size = ARRAYSIZE(excluded_character_ids);
SQLINTEGER cb = SQL_NTS;
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &old_nation_id, 0, NULL);
SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &new_nation_id, 0, NULL);
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, arr_size, 0, excluded_character_ids, 0, &cb); // Binding array directly, like one would do with a VARCHAR type
SQLRETURN res = SQLExecDirect(hstmt, (SQLCHAR*)"{call dbo.Update_Character_Nation(?,?,?)}", SQL_NTS);
if (res != SQL_SUCCESS && res != SQL_SUCCESS_WITH_INFO)
{
printf("Error during query execution: %hd\n", res);
ProcessLogs(SQL_HANDLE_STMT, hstmt);
}
}
这与诊断记录失败:
ERROR;土生土长的:206;州:22018; msg:[Microsoft] [ODBC SQL Server 驱动程序] [SQL Server]操作数类型冲突:int与之不兼容 CharList
2。将数组作为表值参数传递(或至少尝试这样做)
void UpdateCharacterNationTestTVP()
{
unsigned int old_nation_id = 2;
unsigned int new_nation_id = 4;
unsigned int excluded_character_ids[] = {24, 36};
SQLLEN arr_size = ARRAYSIZE(excluded_character_ids);
SQLINTEGER cb = SQL_NTS;
SQLCHAR* tvp_name = (SQLCHAR*)"dbo.CharList";
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &old_nation_id, 0, NULL);
SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &new_nation_id, 0, NULL);
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, arr_size, 0, tvp_name, 0, &cb); // Binding the type here. All parameters after SQL_PARAM_INPUT are taken from MSDN documentation about TVP
// Super scary binding stuff tvp requires you to do
SQLINTEGER cb_rows[] = {SQL_NTS, SQL_NTS};
SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER)3, SQL_IS_INTEGER); // focusing the third parmeter (the TVP one) for the call(s) below
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, arr_size /*or 0*/, 0, excluded_character_ids, sizeof(UINT), cb_rows); // binding of the actual array in a column styled fashion here
SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER)0, SQL_IS_INTEGER); // resetting the focus
SQLRETURN res = SQLExecDirect(hstmt, (SQLCHAR*)"{call dbo.Update_Character_Nation(?,?,?)}", SQL_NTS);
if (res != SQL_SUCCESS && res != SQL_SUCCESS_WITH_INFO)
{
printf("Error during query execution: %hd\n", res);
ProcessLogs(SQL_HANDLE_STMT, hstmt);
}
}
此代码(或多或少地复制了这篇MSDN文章:Use Table-Valued Parameters (ODBC))出错:
ERROR;原生:0;州:07002; msg:[Microsoft] [ODBC SQL Server 驱动程序] COUNT字段不正确或语法错误
第3。吗
我最终正在寻找一种使用无符号整数数组调用存储过程的方法。如果有另一种方法,请告知。如果可能的话,我想远离字符串(VARCHAR)解析。
环境
为了可见性,我没有包含ODBC初始化代码,但这些是环境规范:
SQL_OV_ODBC3
hstmt
=已分配的SQL语句句柄有关上述方法的任何想法或关于如何将数组绑定为存储过程参数的全新建议都表示赞赏。
答案 0 :(得分:0)
您最好的选择可能是将数组解析为分隔列表并将其传递给过程。
对于如何做到这一点有一个很好的答案,在下面的问题上引用其他网站等:
答案 1 :(得分:0)
深入挖掘,我发现第一个ODBC API调用失败实际上是对TVP列/参数的绑定调用:
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, arr_size, 0, tvp_name, 0, &cb);
此调用返回SQL_ERROR
并检查由此发出的诊断记录给出了以下错误:
HY004 [Microsoft] [ODBC SQL Server驱动程序]无效的SQL数据类型
这个具体问题已经在这里提出,但遗憾的是仍未解决。最终使用过时的ODBC驱动程序是导致整个问题的原因。有关如何解决此问题的详细信息,请参阅我在其他问题上的回答:https://stackoverflow.com/a/47113255/2334932
然后由@ A.K提出两点。在他的评论中终于解决了这个问题:
<强> 1。参数长度
传递给SQLBindParameter
的最后一个值,参数长度或cb
,需要实际可用行数而不是SQL_NTS
,因为它在这里用作输入参数。
<强> 2。传递参数名称不是必需的,因为它们受位置
的约束我认为它可以使用或不使用,但是在这里指定TVP的名称实际上并不是必需的,可以省略。
因此,将第三个SQLBindParameter
调用更改为此修复了问题的其余部分:
cb = arr_size;
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, arr_size, 0, NULL, 0, &cb);
完整的工作代码
void UpdateCharacterNationTestTVP()
{
unsigned int old_nation_id = 2;
unsigned int new_nation_id = 4;
unsigned int excluded_character_ids[] = {24, 36};
SQLLEN arr_size = ARRAYSIZE(excluded_character_ids);
SQLINTEGER cb = arr_size; // Needs to have the actual amount of available rows
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &old_nation_id, 0, NULL);
SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &new_nation_id, 0, NULL);
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_SS_TABLE, arr_size, 0, NULL, 0, &cb); // Does not need the name of the TVP
// Super scary binding stuff tvp requires you to do
SQLINTEGER cb_rows[] = {SQL_NTS, SQL_NTS};
SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER)3, SQL_IS_INTEGER); // focusing the third parmeter (the TVP one) for the call(s) below
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, arr_size /*or 0*/, 0, excluded_character_ids, sizeof(UINT), cb_rows); // binding of the actual array in a column styled fashion here
SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER)0, SQL_IS_INTEGER); // resetting the focus
SQLRETURN res = SQLExecDirect(hstmt, (SQLCHAR*)"{call dbo.Update_Character_Nation(?,?,?)}", SQL_NTS);
if (res != SQL_SUCCESS && res != SQL_SUCCESS_WITH_INFO)
{
printf("Error during query execution: %hd\n", res);
ProcessLogs(SQL_HANDLE_STMT, hstmt);
}
}