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

```

posted @ 2025-03-23 16:25  小犟  阅读(34)  评论(0)    收藏  举报