5 分钟教会 ChatGPT 自动生成 SQL 注释脚本,真香!

用 ChatGPT 辅助开发 SQL 注释生成工具的实践

在日常开发中,我们经常会收到产品或业务方整理好的 Excel 字段文档,比如字段名和字段说明,期望我们将这些信息转化成数据库字段的注释(COMMENT ON COLUMN)语句。

手动转写不仅枯燥,还容易出错。这次我用 ChatGPT 辅助完成了一个自动读取 Excel 并生成 SQL 注释脚本的 Java 工具,整个过程不到半小时,效果非常好。


我的需求

初始需求非常明确:

读取目录下所有 Excel 文件的所有 Sheet,如果前两列是 "FIELD NAME"和 "FIELD DESCRIPTION",就生成如下 SQL: comment on column <目录名>.<sheet名>.<FIELD NAME> is '<FIELD DESCRIPTION>';

并将所有结果合并输出到一个 .sql 文件中。


用 ChatGPT 辅助开发的过程

我首先将需求发给了 ChatGPT,大致描述如下:

"我的jdk版本是java8,用 Java 写一个小工具,遍历目录中所有 Excel 文件,读取每个 sheet 的前两列,字段名是 FIELD NAME 和 FIELD DESCRIPTION,就生成 SQL 注释语句。"

ChatGPT 很快给出了完整的实现建议,包括:

  • 使用 Apache POI 读取 Excel 文件(兼容 .xls.xlsx);
  • 判断 Sheet 的前两列标题;
  • 自动拼接 SQL 并输出;
  • 遍历目录、写文件逻辑。

甚至还考虑到了文件命名安全、空值过滤等细节。

ChatGPT 的具体回复

下面是一个完整的 Java 小工具,它会:

  1. 遍历指定目录中的所有 Excel 文件(支持 .xlsx 格式)。

  2. 对每个 Excel 文件的所有 Sheet,读取前两列。

  3. 如果前两列的标题是 FIELD NAMEFIELD DESCRIPTION,则处理该 Sheet:

    • 为每一行生成 SQL:comment on column <目录名>.<sheet名>.<FIELD NAME> is '<FIELD DESCRIPTION>';
  4. 将所有 SQL 语句合并写入同一个 .sql 文件。


🔧 依赖

请确保你使用的是支持 Maven 的项目,并在 pom.xml 中添加如下依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
</dependencies>

📂 工具类 Java 源码

java 复制代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ExcelCommentSqlGenerator {

    public static void main(String[] args) throws IOException {
        if (args.length < 2) {
            System.out.println("用法: java ExcelCommentSqlGenerator <excel目录路径> <输出sql文件路径>");
            return;
        }

        File excelDir = new File(args[0]);
        File outputSqlFile = new File(args[1]);

        if (!excelDir.isDirectory()) {
            System.err.println("错误:路径 " + excelDir.getAbsolutePath() + " 不是一个有效的目录。");
            return;
        }

        List<String> allSqlStatements = new ArrayList<>();

        File[] excelFiles = excelDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".xlsx") && !name.startsWith("~$"); // 排除临时文件
            }
        });

        if (excelFiles == null || excelFiles.length == 0) {
            System.err.println("未找到任何 Excel 文件。");
            return;
        }

        for (File excelFile : excelFiles) {
            String directoryName = excelDir.getName(); // 用文件夹名作为"目录名"

            FileInputStream fis = null;
            Workbook workbook = null;
            try {
                fis = new FileInputStream(excelFile);
                workbook = new XSSFWorkbook(fis);

                for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                    Sheet sheet = workbook.getSheetAt(i);
                    String sheetName = sheet.getSheetName();

                    Row headerRow = sheet.getRow(0);
                    if (headerRow == null || headerRow.getPhysicalNumberOfCells() < 2) {
                        continue;
                    }

                    String col1 = getCellStringValue(headerRow.getCell(0));
                    String col2 = getCellStringValue(headerRow.getCell(1));

                    if (!"FIELD NAME".equalsIgnoreCase(col1) || !"FIELD DESCRIPTION".equalsIgnoreCase(col2)) {
                        continue;
                    }

                    for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
                        Row row = sheet.getRow(rowIndex);
                        if (row == null) continue;

                        String fieldName = getCellStringValue(row.getCell(0));
                        String fieldDesc = getCellStringValue(row.getCell(1)).replace("'", "''"); // 转义单引号

                        if (!fieldName.isEmpty() && !fieldDesc.isEmpty()) {
                            String sql = "comment on column " + directoryName + "." + sheetName + "." + fieldName
                                    + " is '" + fieldDesc + "';";
                            allSqlStatements.add(sql);
                        }
                    }
                }

            } catch (Exception e) {
                System.err.println("处理文件失败: " + excelFile.getName() + ",错误:" + e.getMessage());
            } finally {
                if (workbook != null) workbook.close();
                if (fis != null) fis.close();
            }
        }

        // 写入 .sql 文件
        BufferedWriter writer = new BufferedWriter(new FileWriter(outputSqlFile));
        for (String sql : allSqlStatements) {
            writer.write(sql);
            writer.newLine();
        }
        writer.close();

        System.out.println("共生成 " + allSqlStatements.size() + " 条 SQL,已写入: " + outputSqlFile.getAbsolutePath());
    }

    private static String getCellStringValue(Cell cell) {
        if (cell == null) return "";
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim();
            case NUMERIC:
                return String.valueOf(cell.getNumericCellValue());
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                try {
                    return cell.getStringCellValue().trim(); // 若为字符串公式
                } catch (IllegalStateException e) {
                    try {
                        return String.valueOf(cell.getNumericCellValue()); // 若为数字公式
                    } catch (Exception ex) {
                        return "";
                    }
                }
            default:
                return "";
        }
    }
}

📌 使用说明

假设你已经将该工具编译并打包为 ExcelCommentSqlGenerator.jar,可以这样使用命令行运行它:

bash 复制代码
java -jar ExcelCommentSqlGenerator.jar ./excel目录 ./output/comment_output.sql

需要我帮你封装为命令行工具、打包成可运行 jar 或处理 .xls 格式也可以继续告诉我。


最终效果

生成的 SQL 类似如下:

sql 复制代码
comment on column finance.customer.ID is '客户编号';
comment on column finance.customer.NAME is '客户名称';
comment on column hr.employee.ENTRY_DATE is '入职日期';

随后,为了适配sqlserver数据库,又向ChatGPT提了新的需求,让其帮我编写一个可以将comment on结构的sql语句转为适用于sqlserver的小工具。

当然可以!下面是将 Oracle 注释语句转换为 SQL Server sp_addextendedproperty 格式的 Java 脚本,支持从指定文件读取 Oracle 注释语句并生成 SQL Server 格式的输出文件。


✅ Java 脚本功能说明:

  • 输入文件格式:Oracle 的注释语句,形如:

    sql 复制代码
    COMMENT ON COLUMN EXIMSYS.TRX_VOUCHER_HEADER.AB_TIMES IS 'System Reserved Field';
  • 输出文件格式(SQL Server):

    sql 复制代码
    EXEC sp_addextendedproperty 
      @name = N'MS_Description',
      @value = N'System Reserved Field',
      @level0type = N'SCHEMA', @level0name = N'EXIMSYS',
      @level1type = N'TABLE',  @level1name = N'TRX_VOUCHER_HEADER',
      @level2type = N'COLUMN', @level2name = N'AB_TIMES';

✅ Java 脚本:OracleCommentConverter.java

java 复制代码
import java.io.*;
import java.nio.file.*;
import java.util.regex.*;

public class OracleCommentConverter {

    public static void main(String[] args) throws IOException {
        if (args.length < 2) {
            System.out.println("用法: java OracleCommentConverter <输入文件路径> <输出文件路径>");
            return;
        }

        Path inputPath = Paths.get(args[0]);
        Path outputPath = Paths.get(args[1]);

        if (!Files.exists(inputPath)) {
            System.err.println("错误:输入文件不存在:" + inputPath);
            return;
        }

        Pattern pattern = Pattern.compile(
            "COMMENT ON COLUMN ([^.]+)\\.([^.]+)\\.([ ]*[^ ]+) IS '(.+?)';?",
            Pattern.CASE_INSENSITIVE
        );

        try (
            BufferedReader reader = Files.newBufferedReader(inputPath);
            BufferedWriter writer = Files.newBufferedWriter(outputPath)
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                Matcher matcher = pattern.matcher(line.trim());
                if (matcher.find()) {
                    String schema = matcher.group(1).trim();
                    String table = matcher.group(2).trim();
                    String column = matcher.group(3).trim();
                    String comment = matcher.group(4).trim().replace("'", "''");

                    writer.write("EXEC sp_addextendedproperty \n");
                    writer.write("  @name = N'MS_Description',\n");
                    writer.write("  @value = N'" + comment + "',\n");
                    writer.write("  @level0type = N'SCHEMA', @level0name = N'" + schema + "',\n");
                    writer.write("  @level1type = N'TABLE',  @level1name = N'" + table + "',\n");
                    writer.write("  @level2type = N'COLUMN', @level2name = N'" + column + "';\n\n");
                }
            }

            System.out.println("✅ 转换完成,输出文件:" + outputPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

🛠 小贴士

  • 输入文件建议是 UTF-8 编码(含中文注释也支持);
  • Java 会自动处理换行、空格;
  • 你可以打成 .jar 方便后续自动批量执行。

/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P password -i /GitRepo/Comments.sql > /GitRepo/log.txt

由于sqlserver脚本中sp_addextendedproperty执行时若表或表中对应字段不存在,会直接退出脚本,因此加了需求,遇错不报错 你这段 SQL 脚本会在 sqlcmd执行失败直接退出很可能是因为目标扩展属性已存在 ,而 sp_addextendedproperty 不能重复添加同名属性,会报错:
Msg 15233: Property cannot be added because it already exists.

这类错误会被 sqlcmd 认为是严重错误,导致后续语句不会执行


✅ 解决方案(推荐):

🔁 使用 IF EXISTS 判断后再添加,或先删除再添加

你可以改用如下结构:

sql 复制代码
-- 删除已存在的扩展属性(忽略失败)
BEGIN TRY
  EXEC sp_dropextendedproperty 
    @name = N'MS_Description',
    @level0type = N'SCHEMA', @level0name = N'EXIMTRX',
    @level1type = N'TABLE',  @level1name = N'ADV_BANK_CUST',
    @level2type = N'COLUMN', @level2name = N'BANK_N90_REF_20';
END TRY
BEGIN CATCH
END CATCH;
GO

-- 添加扩展属性
BEGIN TRY
  EXEC sp_addextendedproperty 
    @name = N'MS_Description',
    @value = N'Bank N90 Reference for Tag 20',
    @level0type = N'SCHEMA', @level0name = N'EXIMTRX',
    @level1type = N'TABLE',  @level1name = N'ADV_BANK_CUST',
    @level2type = N'COLUMN', @level2name = N'BANK_N90_REF_20';
END TRY
BEGIN CATCH
  PRINT 'add failed: ' + ERROR_MESSAGE();
END CATCH;
GO

🧪 另一种方式:用 sp_updateextendedproperty 更新

如果你知道属性存在,可以直接更新:

sql 复制代码
EXEC sp_updateextendedproperty 
  @name = N'MS_Description',
  @value = N'New description here',
  @level0type = N'SCHEMA', @level0name = N'EXIMTRX',
  @level1type = N'TABLE',  @level1name = N'ADV_BANK_CUST',
  @level2type = N'COLUMN', @level2name = N'BANK_N90_REF_20';

不过如果它不存在也会报错,所以更安全还是先删除再加 ,或者使用 TRY...CATCH 包裹。


🔧 配套建议

如果你还是希望继续执行,即使有报错:

  • .sql 文件开头加上:

    sql 复制代码
    :ON ERROR CONTINUE
  • 或者你在命令行执行时,不加 -b 参数,并加 stderr 输出:

    bash 复制代码
    /opt/mssql-tools/bin/sqlcmd \
      -S localhost -U sa -P QWEasd2023 \
      -i /GitRepo/EXIMTRX_Comments.sql \
      > /GitRepo/log.txt 2>&1

根据实际需求,最终采用的第一种先删后增的方式来修改脚本,同时向ChatGPT提出了新的需求

改写下结构

  • writer.write("EXEC sp_addextendedproperty \n");
  • writer.write(" @name = N'MS_Description',\n");
  • writer.write(" @value = N'" + comment + "',\n");
  • writer.write(" @level0type = N'SCHEMA', @level0name = N'" + schema + "',\n");
  • writer.write(" @level1type = N'TABLE', @level1name = N'" + table + "',\n");
  • writer.write(" @level2type = N'COLUMN', @level2name = N'" + column + "';\n\n");

你可以将原本的 Java 写文件代码改为自动输出"先删除再添加扩展属性"的形式,每个字段的注释生成代码可以这么写:

java 复制代码
writer.write("-- 删除已存在的扩展属性(忽略失败)\n");
writer.write("BEGIN TRY\n");
writer.write("  EXEC sp_dropextendedproperty \n");
writer.write("    @name = N'MS_Description',\n");
writer.write("    @level0type = N'SCHEMA', @level0name = N'" + schema + "',\n");
writer.write("    @level1type = N'TABLE',  @level1name = N'" + table + "',\n");
writer.write("    @level2type = N'COLUMN', @level2name = N'" + column + "';\n");
writer.write("END TRY\n");
writer.write("BEGIN CATCH\n");
writer.write("END CATCH;\n");
writer.write("GO\n\n");

writer.write("-- 添加扩展属性\n");
writer.write("BEGIN TRY\n");
writer.write("  EXEC sp_addextendedproperty \n");
writer.write("    @name = N'MS_Description',\n");
writer.write("    @value = N'" + comment + "',\n");
writer.write("    @level0type = N'SCHEMA', @level0name = N'" + schema + "',\n");
writer.write("    @level1type = N'TABLE',  @level1name = N'" + table + "',\n");
writer.write("    @level2type = N'COLUMN', @level2name = N'" + column + "';\n");
writer.write("END TRY\n");
writer.write("BEGIN CATCH\n");
writer.write("  PRINT 'add failed: ' + ERROR_MESSAGE();\n");
writer.write("END CATCH;\n");
writer.write("GO\n\n");

✅ 效果举例(生成后)

sql 复制代码
-- 删除已存在的扩展属性(忽略失败)
BEGIN TRY
  EXEC sp_dropextendedproperty 
    @name = N'MS_Description',
    @level0type = N'SCHEMA', @level0name = N'EXIMTRX',
    @level1type = N'TABLE',  @level1name = N'ADV_BANK_CUST',
    @level2type = N'COLUMN', @level2name = N'BANK_N90_REF_20';
END TRY
BEGIN CATCH
END CATCH;
GO

-- 添加扩展属性
BEGIN TRY
  EXEC sp_addextendedproperty 
    @name = N'MS_Description',
    @value = N'Bank N90 Reference for Tag 20',
    @level0type = N'SCHEMA', @level0name = N'EXIMTRX',
    @level1type = N'TABLE',  @level1name = N'ADV_BANK_CUST',
    @level2type = N'COLUMN', @level2name = N'BANK_N90_REF_20';
END TRY
BEGIN CATCH
  PRINT 'add failed: ' + ERROR_MESSAGE();
END CATCH;
GO

总结:AI 编程的价值

这个工具虽然简单,但体现了 AI 编程的几个核心优势:

  • 节省查文档时间:我不再去查 Apache POI 的各种方法;
  • 提升代码组织能力:ChatGPT 给出的逻辑非常清晰,易于维护;
  • 快速从需求到交付:10 分钟内拿到原型代码,稍加修改就可投入使用。

很多人觉得 ChatGPT 是"代码自动生成器",但在我看来,它更像是一个随时待命、可以理解上下文的"资深搭档"。


推荐给你

如果你也经常处理重复性文档转化工作,推荐你试试用 ChatGPT 写个小工具------ 只要你能描述出规则,ChatGPT 就能帮你把它实现为脚本或服务。

一人开发,不再孤独。


相关推荐
funfan051724 分钟前
Claude4、GPT4、Kimi K2、Gemini2.5、DeepSeek R1、Code Llama等2025主流AI编程大模型多维度对比分析报告
ai编程
草梅友仁28 分钟前
草梅 Auth 1.1.0 发布与最新动态 | 2025 年第 30 周草梅周报
开源·github·ai编程
LinXunFeng1 小时前
AI - Gemini CLI 摆脱终端限制
openai·ai编程·gemini
程序员X小鹿2 小时前
腾讯还是太全面了,限时免费!超全CodeBuddy IDE保姆级教程!(附案例)
ai编程
yeshan7 小时前
使用 Claude Code 的自定义 Sub Agent 完善博文写作体验
ai编程·claude·掘金·日新计划
人生都在赌9 小时前
一个AI工作流如何让代码审查从手动到智能?实战拆解
ai编程·devops·cursor
北极的树9 小时前
大模型上下文工程之Prefix Caching技术详解
人工智能·ai编程
软件测试君9 小时前
【Rag实用分享】小白也能看懂的文档解析和分割教程
aigc·openai·ai编程
qiyue779 小时前
AI编程专栏(七)-什么是上下文工程,与提示工程区别
人工智能·ai编程·cursor
wayne2149 小时前
不写一行代码,也能做出 App?一文看懂「Vibe Coding」
人工智能·ai编程