如何通过代码判断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
相关产品推荐
相关产品推荐

