You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过代码判断NL2SQL场景下不同LLM生成SQL的结果是否一致?

如何通过JDBC校验不同LLM生成SQL的结果一致性

完全可以通过JDBC实现这个校验任务,核心是利用ResultSetMetaData获取结果集的结构信息,再逐行逐列对比数据内容。下面是具体实现思路和代码:

核心步骤

  • 获取结果集元数据:通过ResultSet.getMetaData()拿到ResultSetMetaData对象,它能提供列数、列名、数据类型等关键信息。
  • 校验结果集结构:对比两个SQL生成的结果集的列数、列类型、列名是否一致(列名可根据需求决定是否严格匹配,比如允许别名不同但实际列含义一致的情况)。
  • 校验结果集内容:如果结构一致,再逐行逐列对比数据,注意处理NULL值、数据类型转换,以及结果行顺序可能不同的问题(建议先对结果集按列排序后再对比)。

完整代码示例

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class SQLResultComparator {
    // 数据库连接配置,根据实际情况修改
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_db";
    private static final String DB_USER = "your_user";
    private static final String DB_PWD = "your_password";

    public static void main(String[] args) {
        String sql1 = "SELECT id, name, age FROM users WHERE age > 18";
        String sql2 = "SELECT user_id AS id, user_name AS name, user_age AS age FROM user_info WHERE user_age > 18";

        try {
            boolean isConsistent = compareSQLResults(sql1, sql2);
            System.out.println("两个SQL结果是否一致: " + isConsistent);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static boolean compareSQLResults(String sql1, String sql2) throws SQLException {
        // 获取两个SQL的结果集数据和元数据
        ResultSetData data1 = executeSQLAndGetData(sql1);
        ResultSetData data2 = executeSQLAndGetData(sql2);

        // 先校验结构
        if (!compareResultSetStructure(data1.metaData, data2.metaData)) {
            System.out.println("结果集结构不一致");
            return false;
        }

        // 再校验内容(先排序避免行顺序影响)
        List<Map<String, Object>> sortedData1 = sortResultSetData(data1.data);
        List<Map<String, Object>> sortedData2 = sortResultSetData(data2.data);

        if (sortedData1.size() != sortedData2.size()) {
            System.out.println("结果集行数不一致");
            return false;
        }

        for (int i = 0; i < sortedData1.size(); i++) {
            Map<String, Object> row1 = sortedData1.get(i);
            Map<String, Object> row2 = sortedData2.get(i);
            if (!compareRows(row1, row2)) {
                System.out.println("第" + (i+1) + "行数据不一致");
                return false;
            }
        }

        return true;
    }

    // 执行SQL并获取结果集数据和元数据
    private static ResultSetData executeSQLAndGetData(String sql) throws SQLException {
        try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {

            ResultSetMetaData metaData = rs.getMetaData();
            List<Map<String, Object>> data = new ArrayList<>();
            int columnCount = metaData.getColumnCount();

            while (rs.next()) {
                Map<String, Object> row = new TreeMap<>(); // TreeMap自动排序键,方便对比
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    Object value = rs.getObject(i);
                    row.put(columnName, value);
                }
                data.add(row);
            }

            return new ResultSetData(metaData, data);
        }
    }

    // 对比结果集结构:列数、列类型、列名(可根据需求调整是否严格匹配列名)
    private static boolean compareResultSetStructure(ResultSetMetaData meta1, ResultSetMetaData meta2) throws SQLException {
        if (meta1.getColumnCount() != meta2.getColumnCount()) {
            return false;
        }

        for (int i = 1; i <= meta1.getColumnCount(); i++) {
            // 对比列数据类型(JDBC类型)
            if (meta1.getColumnType(i) != meta2.getColumnType(i)) {
                return false;
            }
            // 如果需要严格匹配列名,取消下面注释
            // if (!meta1.getColumnName(i).equalsIgnoreCase(meta2.getColumnName(i))) {
            //     return false;
            // }
        }

        return true;
    }

    // 对结果集数据按列排序,避免行顺序不同导致的对比失败
    private static List<Map<String, Object>> sortResultSetData(List<Map<String, Object>> data) {
        // 这里简单按行的所有键值对拼接后的字符串排序,也可以指定主键列排序
        data.sort((row1, row2) -> {
            String str1 = row1.toString();
            String str2 = row2.toString();
            return str1.compareTo(str2);
        });
        return data;
    }

    // 对比单行数据,处理NULL值
    private static boolean compareRows(Map<String, Object> row1, Map<String, Object> row2) {
        for (String key : row1.keySet()) {
            Object val1 = row1.get(key);
            Object val2 = row2.get(key);

            if (val1 == null && val2 == null) {
                continue;
            }
            if (val1 == null || val2 == null) {
                return false;
            }
            // 注意:对于浮点数可能需要精度处理,比如BigDecimal的compareTo
            if (!val1.equals(val2)) {
                return false;
            }
        }
        return true;
    }

    // 存储结果集元数据和数据的内部类
    private static class ResultSetData {
        ResultSetMetaData metaData;
        List<Map<String, Object>> data;

        ResultSetData(ResultSetMetaData metaData, List<Map<String, Object>> data) {
            this.metaData = metaData;
            this.data = data;
        }
    }
}

注意事项

  • NULL值处理:必须单独判断NULL,因为null.equals(null)会抛空指针异常。
  • 数据类型精度:比如浮点数(如FLOAT、DOUBLE)或DECIMAL类型,直接用equals可能因为精度问题误判,建议用BigDecimal.compareTo这类方法做精确对比。
  • 结果行顺序:如果SQL没有指定ORDER BY,数据库返回的行顺序可能随机,所以必须先对结果集排序后再对比,最好用业务主键或唯一键排序。
  • 列名别名:如果允许不同SQL用不同列别名但实际数据一致,可以注释掉结构对比中的列名校验逻辑,只对比列类型和数量。

内容的提问来源于stack exchange,提问作者ZhaoGang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 04:10:56