c++11实现SQL Server数据库与表格的创建
##```create_database.cpp```
```
#define _CRT_SECURE_NO_WARNINGS
// 仅在 Windows 下使用,可通过条件编译处理
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include <iostream>
#include <string>
#include <stdexcept>
/**
* @brief 检查ODBC操作的错误信息并抛出异常。
*
* 此函数用于获取ODBC操作的诊断信息,将错误信息拼接成字符串,并抛出一个std::runtime_error异常。
*
* @param handle ODBC句柄,用于标识需要检查错误的对象。
* @param type 句柄的类型,如SQL_HANDLE_ENV、SQL_HANDLE_DBC等。
* @param message 自定义的错误消息,将在抛出异常时包含在内。
*
* @throws std::runtime_error 如果检测到错误信息,则抛出包含自定义消息和ODBC错误信息的异常。
*/
void checkError(SQLHANDLE handle, SQLSMALLINT type, const std::string &message)
{
// 存储SQL状态码,长度为6个字符
SQLCHAR sqlState[6];
// 存储错误消息文本,最大长度为SQL_MAX_MESSAGE_LENGTH 默认512
SQLCHAR messageText[SQL_MAX_MESSAGE_LENGTH];
// 存储原生错误代码
SQLINTEGER nativeError;
// 存储错误消息的实际长度
SQLSMALLINT length;
// 存储SQL函数的返回值
SQLRETURN ret;
// 用于存储拼接后的错误消息
std::string errorMsg;
// 循环获取诊断记录,直到没有更多记录或出现错误
while ((ret = SQLGetDiagRec(type, handle, 1, sqlState, &nativeError, messageText, SQL_MAX_MESSAGE_LENGTH, &length)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
{
// 将错误消息文本添加到errorMsg中
errorMsg += std::string(reinterpret_cast<char *>(messageText));
// 如果这次获取到的错误消息长度小于每次能获得的最大长度,说明后续已经没有了,此时则跳出循环
if (length < SQL_MAX_MESSAGE_LENGTH - 1)
break;
}
// 如果errorMsg不为空,则抛出包含自定义消息和错误消息的异常
if (!errorMsg.empty())
{
throw std::runtime_error(message + ": " + errorMsg);
}
}
class DatabaseCreator
{
private:
SQLHDBC hDbc;
std::string dbName;
std::string dbDataFilePath;
std::string dbLogFilePath;
public:
/**
* @brief DatabaseCreator类的构造函数。
*
* 该构造函数用于初始化DatabaseCreator对象,接受数据库连接句柄、数据库名称、数据库数据文件路径和数据库日志文件路径作为参数。
*
* @param hDbc 数据库连接句柄,用于与数据库进行交互。
* @param dbName 要创建的数据库的名称。
* @param dbDataFilePath 数据库数据文件的路径。
* @param dbLogFilePath 数据库日志文件的路径。
*/
DatabaseCreator(SQLHDBC hDbc, const std::string &dbName, const std::string &dbDataFilePath, const std::string &dbLogFilePath)
// 使用成员初始化列表初始化类的私有成员
: hDbc(hDbc), dbName(dbName), dbDataFilePath(dbDataFilePath), dbLogFilePath(dbLogFilePath) {}
/**
* @brief 创建数据库的方法。
*
* 此方法首先检查指定名称的数据库是否已经存在。
* 如果数据库不存在,则创建一个新的数据库,并指定其数据文件和日志文件的属性。
* 如果数据库已经存在,则输出相应的提示信息并跳过创建过程。
*/
void createDatabase()
{
// 检查数据库是否已存在
// 构建查询语句,用于查询指定名称的数据库是否存在
std::string checkDBQuery = "SELECT COUNT(*) FROM sys.databases WHERE name = '" + dbName + "'";
// 初始化检查数据库是否存在的语句句柄
SQLHSTMT hStmtCheck = SQL_NULL_HSTMT;
// 分配语句句柄,用于执行检查数据库是否存在的查询
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmtCheck);
// 检查语句句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,调用检查错误函数,输出错误信息
checkError(hDbc, SQL_HANDLE_DBC, "Failed to allocate statement handle for checking database");
}
// 执行检查数据库是否存在的查询
ret = SQLExecDirectA(hStmtCheck, (SQLCHAR *)checkDBQuery.c_str(), SQL_NTS);
// 检查查询执行是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若执行失败,调用检查错误函数,输出错误信息
checkError(hStmtCheck, SQL_HANDLE_STMT, "Failed to execute query for checking database");
}
// 用于存储查询结果,判断数据库是否存在
SQLINTEGER dbExists = 0;
// 将查询结果的第一列绑定到 dbExists 变量
ret = SQLBindCol(hStmtCheck, 1, SQL_C_LONG, &dbExists, sizeof(dbExists), NULL);
// 检查绑定列是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若绑定失败,调用检查错误函数,输出错误信息
checkError(hStmtCheck, SQL_HANDLE_STMT, "Failed to bind column for checking database");
}
// 从结果集中获取一行数据
ret = SQLFetch(hStmtCheck);
// 检查获取数据是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若获取失败,调用检查错误函数,输出错误信息
checkError(hStmtCheck, SQL_HANDLE_STMT, "Failed to fetch data for checking database");
}
// 释放检查数据库是否存在的语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hStmtCheck);
// 如果数据库不存在
if (dbExists == 0)
{
// 创建数据库的 SQL 语句
// 构建创建数据库的 SQL 语句,指定数据库名称、数据文件和日志文件的属性
std::string createDBQuery = "CREATE DATABASE " + dbName + " ON PRIMARY "
"(NAME = " +
dbName + "_data, "
"FILENAME = '" +
dbDataFilePath + "', "
"SIZE = 2MB, "
"MAXSIZE = 10MB, "
"FILEGROWTH = 1MB) "
"LOG ON "
"(NAME = " +
dbName + "_log, "
"FILENAME = '" +
dbLogFilePath + "', "
"SIZE = 1MB, "
"MAXSIZE = 5MB, "
"FILEGROWTH = 1MB);";
// 分配语句句柄,用于执行创建数据库的操作
SQLHSTMT hStmt = SQL_NULL_HANDLE;
// 分配语句句柄
ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
// 检查语句句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,调用检查错误函数,输出错误信息
checkError(hDbc, SQL_HANDLE_DBC, "Failed to allocate statement handle");
}
// 执行创建数据库的 SQL 语句
ret = SQLExecDirectA(hStmt, (SQLCHAR *)createDBQuery.c_str(), SQL_NTS);
// 检查创建数据库的操作是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若创建失败,调用检查错误函数,输出错误信息
checkError(hStmt, SQL_HANDLE_STMT, "Failed to create database");
}
else
{
// 若创建成功,输出成功信息
std::cout << "Database " << dbName << " created successfully!" << std::endl;
}
// 释放执行创建数据库操作的语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
else
{
// 如果数据库已经存在,输出提示信息并跳过创建过程
std::cout << "Database " << dbName << " already exists, skipping creation." << std::endl;
}
}
};
class TableCreator
{
private:
SQLHDBC hDbc;
std::string dbName;
std::string tableName;
public:
/**
* @brief TableCreator类的构造函数。
*
* 该构造函数用于初始化TableCreator对象,接受数据库连接句柄、数据库名称和表名作为参数。
*
* @param hDbc 数据库连接句柄,用于与数据库进行交互。
* @param dbName 要操作的数据库的名称。
* @param tableName 要创建的表的名称。
*/
TableCreator(SQLHDBC hDbc, const std::string &dbName, const std::string &tableName)
// 使用成员初始化列表初始化类的私有成员
: hDbc(hDbc), dbName(dbName), tableName(tableName) {}
/**
* @brief 创建表格的方法。
*
* 此方法首先选择指定的数据库,然后检查指定名称的表格是否已经存在。
* 如果表格不存在,则创建一个新的表格,并定义表格的列和数据类型。
* 如果表格已经存在,则输出相应的提示信息并跳过创建过程。
*/
void createTable()
{
// 选择数据库
// 构建用于选择数据库的 SQL 语句
std::string useDBQuery = "USE " + dbName + ";";
// 初始化用于执行 SQL 语句的语句句柄
SQLHSTMT hStmt = SQL_NULL_HANDLE;
// 分配语句句柄,用于后续执行 SQL 语句
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
// 检查语句句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,调用检查错误函数,输出错误信息
checkError(hDbc, SQL_HANDLE_DBC, "Failed to allocate statement handle for using database");
}
// 执行选择数据库的 SQL 语句
ret = SQLExecDirectA(hStmt, (SQLCHAR *)useDBQuery.c_str(), SQL_NTS);
// 检查 SQL 语句执行是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若执行失败,调用检查错误函数,输出错误信息
checkError(hStmt, SQL_HANDLE_STMT, "Failed to use database");
}
// 检查表格是否存在
// 构建用于检查表格是否存在的 SQL 语句
std::string checkTableQuery = "SELECT COUNT(*) FROM sysobjects WHERE name = '" + tableName + "' AND xtype = 'U';";
// 初始化用于检查表格是否存在的语句句柄
SQLHSTMT hStmtCheckTable = SQL_NULL_HANDLE;
// 分配语句句柄,用于执行检查表格是否存在的 SQL 语句
ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmtCheckTable);
// 检查语句句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,调用检查错误函数,输出错误信息
checkError(hDbc, SQL_HANDLE_DBC, "Failed to allocate statement handle for checking table");
}
// 执行检查表格是否存在的 SQL 语句
ret = SQLExecDirectA(hStmtCheckTable, (SQLCHAR *)checkTableQuery.c_str(), SQL_NTS);
// 检查 SQL 语句执行是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若执行失败,调用检查错误函数,输出错误信息
checkError(hStmtCheckTable, SQL_HANDLE_STMT, "Failed to execute query for checking table");
}
// 用于存储查询结果,判断表格是否存在
SQLINTEGER tableExists = 0;
// 将查询结果的第一列绑定到 tableExists 变量
ret = SQLBindCol(hStmtCheckTable, 1, SQL_C_LONG, &tableExists, sizeof(tableExists), NULL);
// 检查绑定列是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若绑定失败,调用检查错误函数,输出错误信息
checkError(hStmtCheckTable, SQL_HANDLE_STMT, "Failed to bind column for checking table");
}
// 从结果集中获取一行数据
ret = SQLFetch(hStmtCheckTable);
// 检查获取数据是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若获取失败,调用检查错误函数,输出错误信息
checkError(hStmtCheckTable, SQL_HANDLE_STMT, "Failed to fetch data for checking table");
}
// 释放检查表格是否存在的语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hStmtCheckTable);
// 如果表格不存在
if (tableExists == 0)
{
// 创建表格的 SQL 语句
// 构建创建表格的 SQL 语句,定义表格的列和数据类型
std::string createTableQuery = "CREATE TABLE " + tableName + " ("
"EmployeeID INT PRIMARY KEY, "
"FirstName NVARCHAR(50), "
"LastName NVARCHAR(50), "
"HireDate DATE "
");";
// 执行创建表格的 SQL 语句
ret = SQLExecDirectA(hStmt, (SQLCHAR *)createTableQuery.c_str(), SQL_NTS);
// 检查创建表格的 SQL 语句执行是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若执行失败,调用检查错误函数,输出错误信息
checkError(hStmt, SQL_HANDLE_STMT, "Failed to create table");
}
else
{
// 若创建成功,输出成功信息
std::cout << "Table " << tableName << " created successfully!" << std::endl;
}
}
else
{
// 如果表格已经存在,输出提示信息并跳过创建过程
std::cout << "Table " << tableName << " already exists, skipping creation." << std::endl;
}
// 释放执行 SQL 语句的语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
};
/**
* @brief 程序的入口点,用于建立与SQL Server的ODBC连接。
*
* 此函数初始化ODBC环境,分配环境和连接句柄,尝试连接到指定的SQL Server实例。
* 如果连接成功,将输出成功消息;如果连接失败,将捕获并输出异常信息。
*
* @return int 程序的退出状态码,0表示正常退出,非0表示异常退出。
*/
int main()
{
// 初始化ODBC环境句柄
SQLHENV hEnv = SQL_NULL_HANDLE;
// 初始化ODBC连接句柄
SQLHDBC hDbc = SQL_NULL_HANDLE;
// 存储SQL函数的返回值
SQLRETURN ret;
try
{
// 分配ODBC环境句柄
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
// 检查环境句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,抛出异常,这里std::runtime_error捕捉运行时错误
throw std::runtime_error("Failed to allocate the environment handle");
}
// 设置ODBC版本为ODBC 3
SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
// 分配ODBC连接句柄
ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
// 检查连接句柄分配是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若分配失败,检查错误信息并输出
checkError(hEnv, SQL_HANDLE_ENV, "Failed to allocate the connection handle");
// 释放环境句柄
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
return 1;
}
// 定义连接字符串
const char *connStr = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost;UID=sa;PWD=123;";
// 尝试连接到SQL Server
ret = SQLDriverConnectA(hDbc, NULL, (SQLCHAR *)connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
// 检查连接是否成功
if (!SQL_SUCCEEDED(ret))
{
// 若连接失败,检查错误信息并输出
checkError(hDbc, SQL_HANDLE_DBC, "Failed to connect to SQL Server");
}
// 若连接成功,输出成功信息
std::cout << "Successfully connected to SQL Server!" << std::endl;
DatabaseCreator dbCreator(hDbc, "myDatabase", "D:\\sqlData\\myDatabase.mdf", "D:\\sqlData\\myDatabase.ldf");
dbCreator.createDatabase();
TableCreator tableCreator(hDbc, "myDatabase", "Employees");
tableCreator.createTable();
}
catch (const std::exception &e)
{
// 捕获并输出异常信息
std::cerr << e.what() << std::endl;
}
// 释放连接句柄
if (hDbc != SQL_NULL_HANDLE)
{
SQLDisconnect(hDbc);
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
}
// 释放环境句柄
if (hEnv != SQL_NULL_HANDLE)
{
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
}
return 0;
}
```