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 就能帮你把它实现为脚本或服务。

一人开发,不再孤独。


相关推荐
小璐乱撞5 小时前
从原理到实战:基于SpringAI的RAG应用探索
spring·ai编程
志辉AI编程5 小时前
我终究还是放弃ClaudeCode,转战DeepSeek 新工具
ai编程
量子位5 小时前
实测豆包 1.6,最火玩法 all in one!Seedance 登顶视频生成榜一,豆包 APP 全量上线
ai编程·豆包marscode
志辉AI编程5 小时前
数据需求全部交给 Claude 搞定?结果我还是 vibe coding 3 小时写了个系统
ai编程
coco01246 小时前
打造趣味策略游戏:OXO 网页游戏开发实战
后端·ai编程
chentao1067 小时前
5-工具调用 vs RAG-你喜欢主动还是被动?
ai编程
Captaincc7 小时前
从 AI Coding 到 AI Development:TRAE 亮相 2025 火山引擎 FORCE 原动力大会
ai编程·trae
Captaincc7 小时前
字节跳动技术副总裁洪定坤:TRAE 想做 AI Development
ai编程
星际码仔10 小时前
揭秘Claude系统提示词:这才是Artifacts的正确打开方式
ai编程·claude
倔强的石头_11 小时前
Trae Linux命令助手Agent:从自然语言到Linux命令的无缝转换
ai编程