/**********************************************************************
* FILENAME : TVPInsertRecs.c
*
* DESCRIPTION :
* Demonstrates a simple example of using a Table-Valued Parameter
* (TVP) to populate a table using a stored procedure with just one
* parameter. TVPs were introduced in SQL SERVER 2008. A TVP is a
* data type that can be used to hold records in a 'table' like
* manner but are held within a variable inside a program. The
* whole of the 'table' like parameter can then be passed as a
* single parameter to a stored procedure for processing. For
* example, TVPs can be usefully applied when performing bulk data
* operations where a whole set of rows can be passed as a single
* parameter to a stored procedure for subsequent inserting into
* a database table.
*
* ODBC USAGE :
*
* The TVP used here represents a table type with 4 columns:
* FirstName, LastName, Address and City.
*
* Prompts user for number of records to create
*
* Sets up the test by dropping and re-creating:-
* 1. Database Table (TestTVPTable1) - into which rows
* are written from rows in a TVP.
* 2. Stored Procedure (InsertFromTVP) - to select records
* from the TVP and insert them into TestTVPTable1.
* 3. TVP Table Variable (PersonTVP) - Table variable to
* hold records and passed as a single parameter to
* InsertFromTVP().
*
* Bind Paramaters:
* Binds parameter 1 as a TVP
*
* Bind TVP Parameters:
* Sets focus on parameter 1
* Bind arrays to each of the 4 TVP columns and lengths. i.e.
* for FirstName, LastName, Address and City
* Clears focus by setting focus to zero
*
* Load data into the TVP bound arrays:
* For the number of records requested
* prompts user for each of the four columns per row
* and save each set in the next row of the TVP bound buffers
* Execute the stores procedure to insert the records stored in the
* TVP bound array buffers. Note the stored procedure only takes
* one parameter, the TVP, but upto as many as MAX_ARRAY_SIZE records
* are written to the table.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <sqlncli.h>
#include <string.h>
#include "util.c"
#define LASTNAME_LEN 255
#define FIRSTNAME_LEN 255
#define ADDRESS_LEN 255
#define CITY_LEN 255
#define MAX_ARRAY_SIZE 20
//
// SetupTVPTest is used to create and/or drop the stored procedure,
// TVP table type and DB Table used in the test.
//
SQLRETURN SetupTVPTest (SQLHDBC hdbc, char createEm, char dropEm) {
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode = SQL_SUCCESS; // Return status
// Drop statements
// Stored Procedure
SQLCHAR strDropTVPProc [] =
"IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = "
"object_id(N'dbo.InsertFromTVP') AND OBJECTPROPERTY"
"(id, N'IsProcedure') = 1) DROP PROCEDURE dbo.InsertFromTVP";
// TVP Table Type
SQLCHAR strDropTVPType[] =
"IF EXISTS (SELECT * FROM sys.types WHERE "
"is_table_type = 1 AND name = 'PersonTVP') "
"DROP TYPE dbo.PersonTVP";
// TVP Table (the physical one in the DB)
SQLCHAR strDropTVPTable [] =
"IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = "
"object_id(N'dbo.TestTVPTable1') AND OBJECTPROPERTY(id, N'IsUserTable')"
" = 1) DROP TABLE dbo.TestTVPTable1";
// Create the physical database table
SQLCHAR strCreateTVPTable [] =
"CREATE TABLE TestTVPTable1 ("
"PersonID int NOT NULL IDENTITY(1,1),"
"FirstName varchar(255),"
"LastName varchar(255),"
"Address varchar(255),"
"City varchar(255)"
")";
// Create the in-memory Table-Value Parameter Table
SQLCHAR strCreateTVPType [] =
"CREATE TYPE PersonTVP AS TABLE ( "
"vFirstName varchar(255),"
"vLastName varchar(255),"
"vAddress varchar(255),"
"vCity varchar(255)"
")";
// Create the stored procedure which takes one parameter,
// the persons Table-Value Parameter Table.
// NOTE: Table-value Params Must be read only.
SQLCHAR strCreateTVPProc [] =
"CREATE PROCEDURE InsertFromTVP (@Persons PersonTVP READONLY) "
"AS "
"INSERT INTO TestTVPTable1 (FirstName, LastName, Address, City) "
"SELECT vFirstName, vLastName, vAddress, vCity FROM @Persons";
// Allocate a statement handle
retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
hstmt, SQL_HANDLE_STMT);
if (dropEm=='Y') {
// Execute Drop Procedure
retcode = SQLExecDirect (hstmt, strDropTVPProc, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPPROC)",
hstmt, SQL_HANDLE_STMT);
// Execute Drop Type
retcode = SQLExecDirect (hstmt, strDropTVPType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPTYPE)",
hstmt, SQL_HANDLE_STMT);
// Execute DROP Table
retcode = SQLExecDirect (hstmt, strDropTVPTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPTABLE)",
hstmt, SQL_HANDLE_STMT);
}
if (createEm=='Y')
// Execute Create Table
retcode = SQLExecDirect (hstmt, strCreateTVPTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATETABLE)",
hstmt, SQL_HANDLE_STMT);
// Execute Create Type
retcode = SQLExecDirect (hstmt, strCreateTVPType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATETYPE)",
hstmt, SQL_HANDLE_STMT);
// Execute Create Procedure
retcode = SQLExecDirect (hstmt, strCreateTVPProc, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEPROC)",
hstmt, SQL_HANDLE_STMT);
exit:
// Free statement handle
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
return retcode;
}
//
// Arrays of column values
//
SQLCHAR strFirstName [MAX_ARRAY_SIZE][FIRSTNAME_LEN];
SQLCHAR strLastName[MAX_ARRAY_SIZE][LASTNAME_LEN];
SQLCHAR strAddress[MAX_ARRAY_SIZE][ADDRESS_LEN];
SQLCHAR strCity[MAX_ARRAY_SIZE][CITY_LEN];
//
// Arrays of column values lengths
//
SQLLEN lFirstName[MAX_ARRAY_SIZE];
SQLLEN lLastName[MAX_ARRAY_SIZE];
SQLLEN lAddress[MAX_ARRAY_SIZE];
SQLLEN lCity[MAX_ARRAY_SIZE];
int main () {
SQLHENV henv = SQL_NULL_HENV; // Environment
SQLHDBC hdbc = SQL_NULL_HDBC; // Connection handle
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode; // Return status
int i, numRows=1;
// The name of the TVP table data type
SQLCHAR *TVPTableName = (SQLCHAR *) "PersonTVP";
SQLLEN lTVPRowsUsed;
// Get Number of records to insert
getInt ("Number of Recs", &numRows, 'N', 0);
if (numRows==0) goto exit;
if (numRows>MAX_ARRAY_SIZE) numRows=MAX_ARRAY_SIZE;
// Allocate environment
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
henv, SQL_HANDLE_ENV);
// Set ODBC Verion
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER*)SQL_OV_ODBC3, 0);
CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
henv, SQL_HANDLE_ENV);
// Allocate Connection
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_DBC)",
henv, SQL_HANDLE_DBC);
// Set Login Timeout
retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
CHECK_ERROR(retcode, "SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)",
hdbc, SQL_HANDLE_DBC);
// Connect to DSN
retcode = SQLConnect(hdbc, (SQLCHAR*) "SQLSRV2014", SQL_NTS,
(SQLCHAR*) NULL, 0, NULL, 0);
CHECK_ERROR(retcode, "SQLConnect(DSN:SQLSRV2014;)",
hdbc, SQL_HANDLE_DBC);
// Allocate Statement Handle
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
hstmt, SQL_HANDLE_STMT);
// Setup stored procedure, Table-Value Parameter data type and database
// Table.
// Second parameter indicates whether the above are to be created
// Third parameter indicates whether the above are to be dropped
retcode = SetupTVPTest (hdbc, 'Y', 'Y');
CHECK_ERROR(retcode, "SetupTVPTest()", hdbc, SQL_HANDLE_DBC);
//
// 1st step is bind all parameters in normal way.
// Here we have just one parameter, a Table-Valued Parameter (TVP)
// which requires some specific parameters.
//
retcode = SQLBindParameter(
hstmt, // Statement handle
1, // Parameter Number
SQL_PARAM_INPUT, // Input/Output Type (always INPUT for TVP)
SQL_C_DEFAULT, // C - Type (always this for a TVP)
SQL_SS_TABLE, // SQL Type (always this for a TVP)
MAX_ARRAY_SIZE, // For a TVP this is max rows we will use
0, // For a TVP this is always 0
TVPTableName, // For a TVP this is the type name of the
// TVP, and also a token returned by
// SQLParamData.
SQL_NTS, // For a TVP this is the length of the type
// name or SQL_NTS.
&lTVPRowsUsed); // For a TVP this is the number of rows
// actually available.
//
// Now we need to bind a separate set of parameters for each TVP.
// To do we set the focus on each TVP in turn, bind data to it in the same
// way as for rows in a table.
//
// First set focus on param 1.
retcode = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS,
(SQLPOINTER) 1, SQL_IS_INTEGER);
// Col 1 - Bind start of FirstName array
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIRSTNAME_LEN, 0, &strFirstName[0],
FIRSTNAME_LEN, &lFirstName[0]);
// Col 2 - Bind start of LastName array
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
LASTNAME_LEN, 0, &strLastName[0],
LASTNAME_LEN, &lLastName[0]);
// Col 3 - Bind start of Address array
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, ADDRESS_LEN,
0, &strAddress[0],
ADDRESS_LEN, &lAddress[0]);
// Col 4 - Bind start of City array
retcode = SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, CITY_LEN,
0, &strCity[0], CITY_LEN, &lCity[0]);
//
// If there were further TVPs we would set the focus to the next one,
// bind buffers to that, and so on on until all TVPs have bound buffers.
// As we have no more, reset param focus to zero.
//
retcode = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS,
(SQLPOINTER) 0, SQL_IS_INTEGER);
//
// The data for each row of the TVP is represented by the nth element of
// the arrays bound for columns FirstName, LastName, Address and City,
// held in strFirtstName[n], strLastName[n], strAddress[n], and strCity[n]
// Prompt for data, filling columns and rows until numRows reached.
//
for (i=0;i<numRows;i++) {
// Clear Buffers
memset (strFirstName[i], ' ', FIRSTNAME_LEN);
memset (strLastName[i], ' ', LASTNAME_LEN);
memset (strAddress[i], ' ', ADDRESS_LEN);
memset (strCity[i], ' ', CITY_LEN);
// Get data from user
printf ("\nRecord %i, out of %i\n\n", i+1, numRows);
getStr ("First Name", strFirstName[i], FIRSTNAME_LEN, 'N');
getStr ("Last Name", strLastName[i], LASTNAME_LEN, 'N');
getStr ("Address ", strAddress[i], ADDRESS_LEN, 'N');
getStr ("City ", strCity[i], CITY_LEN, 'N');
// Set lengths
lFirstName[i]=strlen(strFirstName[i]);
lLastName[i]=strlen(strLastName[i]);
lAddress[i]=strlen(strAddress[i]);
lCity[i]=strlen(strCity[i]);
}
//
// We now have arrays holding FirstNames, Lastnames, Addresses and Citys,
// and corresponding lengths for each.
//
// Set the StrLen_or_IndPtr value to the actual number of rows to write
// (i.e. in the buffers) and execute the stored procedure. Although just
// one parameter is passed, because it is a TVP, several rows are actually
// written to the table.
//
lTVPRowsUsed=numRows;
retcode = SQLExecDirect(hstmt, "{CALL InsertFromTVP(?)}", SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(InsertFromTVP)",
hstmt, SQL_HANDLE_STMT);
exit:
printf ("\nComplete.\n");
// Free handles
// Statement
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
// Connection
if (hdbc != SQL_NULL_HDBC) {
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
// Environment
if (henv != SQL_NULL_HENV)
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
Further information