引言
各位好,相信看见这篇文章的朋友,应该也去体验过了chatGPT了吧~,确实chatGPT拉近了我们与未来科技的距离,所有别人火也是非常有道理的,为其点赞。
本文主要是关注chatGPT的SQL能力;因为本人从事IT教育行业,有大量的学生,在这些学生学习过程中有一个共同的技能学起来非常的难或者痛苦,其就是SQL操作能力。所有在本人心中一直有一个项目就是用AI来实现学生们的SQL训练,而且一直在着手行动。chatGPT出来的第一时间,我其实就特别关注,也去试了试相关的SQL功能,但是非常遗憾,chatGPT无法提供相关的功能。
鉴于此,chatGPT不支持,那么本人也是还有机会的,所有加班赶了3天,做出了一套能「自动识别数据库表关系,自动依据表数据生成SQL练习题的工具」,如下:
自动识别当前数据库中的表、表中的数据,生成对用SQL练习题(每个数据库表数量、数据数量不同,生成的练习数量也不同)
SQL练习题自动生成答案
SQL练习题可直接运行和比对答案
为什么chatGPT不支持SQL呢?
其实这个原因非常简单,SQL练习题,需要数据库表结构还有数据,光有数据库表没有数据的SQL练习就是扯犊子,而chatGPT实现表的结构数据收集非常容易,但是它做不到数据的收集,因此这个课题它实现不了。
为什么您能实现呢?
本人在IT行业十几年的经验,深刻体会数据的价值,因此在去年就开始着手实现一系列的软件工具(主要是数据库、redis等客服端工具,如上图),用户使用工具的同时,其实就是赋予了工具读取到这些数据的能力,那么关键的数据问题就解决了;那么在结合一些表结构的分析方法,就可以做一些表规范、SQL练习等相关的功能,提升用户对工具的使用体验。接下来本人也聊聊我是想这些智能化一点的功能思路。
智能SQL练习题实现功能思路★★★
如下图,整体思路核心还是基于SQL模板来实现,但是在整个实现过程中有以下几个难点,指的关注:
-
如何准确的识别表结构的ER关系? 这个不能基于物理外键关系来实现,因为大多表都没有物理外键;
-
如何让SQL模板引擎生成的题有数据?
-
如何对比生成的答案和用户输入的答案?
如何识别数据库的逻辑ER结构?★★★
在工作中一般是禁止使用物理外键来约束表与表之间的关系的,那么没这种关系怎么样实现逻辑外键的识别和ER结构的关系呢?本人采取的思路是,使用字段名称的自动匹配,这个想法来源于工作经验的总结:大部分程序员设计一张user表,id字段是主键;设置一张用户地址信息表是其中会设计一个user_id字段来逻辑关联用户表;基于这样一种定于,那么就可以基于名称来实现逻辑关系,本人以下ER关系图就是基于此方式来生成的:
如何基于SQL模板技术生成对应试题?★★★
模板技术就是提供好模板,动态的填充各类数据,这个大家应该比较场景,但是指的注意的是:
-
生成的模板包括:
-
题目标题
-
题目提示
-
题目答案
-
题目输出
-
-
生成题目是还需要动态检查数据是否满足,如果不满足则跳过,满足则生成试题
勇哥给出一段模板参考代码:
package com.madou.dbtool.relationship.panel.exercise.question;
import com.madou.common.annotation.HideClass;
import com.madou.dbtool.constans.Constans;
import com.madou.dbtool.relationship.common.BasePanelParm;
import com.madou.dbtool.relationship.panel.exercise.BaseExercise;
import com.madou.dbtool.relationship.panel.exercise.pojo.OutDegree;
import com.madou.dbtool.relationship.panel.exercise.pojo.QuestionTable;
import com.madou.dbtool.relationship.panel.exercise.pojo.ReferenceAnswer;
import com.madou.dbtool.relationship.panel.exercise.pojo.SqlQuestion;
import com.madou.dbtool.relationship.pojo.FieldInfo;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 有多对多的3张表,生成
*/
@HideClass
public class Case2Question extends BaseExercise {
@Override
public String name() {
return "1对多-统计练习题-CASE百分位";
}
@Override
public List<String> lables() {
return Arrays.asList("case when then","1:N","left join","sum","count","min","max");
}
@Override
public String category() {
return "1对多";
}
@Override
public int needTableCount() {
return 1;
}
// 生成问题
@Override
public SqlQuestion generatorQuestion(BasePanelParm basePanelParm, Map<String,List<Map<String, Object>>> datas, QuestionTable questionTable) {
List<OutDegree> outs = questionTable.getOut();
int size = questionTable.getOut().size();
if(size>1){
int index1 = Constans.RANDOM.nextInt(outs.size());
int index2 = Constans.RANDOM.nextInt(outs.size());
while (index2==index1){
index2 = Constans.RANDOM.nextInt(outs.size());
}
OutDegree mainTable = outs.get(index1);// 结果信息表
OutDegree resuTable = outs.get(index2);// 结果信息表
try {
FieldInfo fieldInfo = chooseNumberField(questionTable.getFields());
if (fieldInfo == null) {
return null;
}
int num = 100;
try {
// 判断数据是否满足生成实体的条件,如果满足则生成,不满足则跳过
String sql = String.format("select max(%s) as count from %s", fieldInfo.getName(), questionTable.getTableInfo().getName());
List<Map<String, Object>> queryResult = getDataLimit(basePanelParm, datas, sql, 1);
if (queryResult != null && queryResult.size() > 0) {
Object count = queryResult.get(0).get("count");
if (count != null) {
num = Double.valueOf("" + queryResult.get(0).get("count")).intValue();
} else {
return null;
}
}
} catch (Exception e) {
e.printStackTrace();
}
int nums[] = new int[]{0,num/4,num/2,num*3/4,num};
if (fieldInfo != null) {
String resuDesc = getTableDesc(resuTable.getQuestionTable().getTableInfo());
SqlQuestion sqlQuestion = new SqlQuestion();
sqlQuestion.setName(getQuestionTitle(fieldInfo, nums, resuDesc));
sqlQuestion.setThinking(getThinking());
sqlQuestion.addReferenceAnswer(getAnswer(questionTable,fieldInfo,resuTable,nums));
sqlQuestion.setOutColumn(getOutColumn(resuTable,nums));
return sqlQuestion;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* 生成结果要输出的字段说明
* @param resuTable
* @return
*/
public Map<String,String> getOutColumn(OutDegree resuTable,int[] nums){
Map<String,String> outColumn = new HashMap<>();
outColumn.put("section1","[0,"+nums[1]+"]段");
outColumn.put("section2","["+nums[1]+","+nums[2]+"]段");
outColumn.put("section3","["+nums[2]+","+nums[3]+"]段");
outColumn.put("section4","["+nums[3]+","+nums[4]+"]段");
for (FieldInfo field : resuTable.getQuestionTable().getFields()) {
outColumn.put(field.getName(),getFieldDesc(field));
}
return outColumn;
}
/**
* 生成参考答案
* @param questionTable
* @param fieldInfo
* @return
*/
public ReferenceAnswer getAnswer(QuestionTable questionTable, FieldInfo fieldInfo ,OutDegree resuTable,int[] nums){
String sql = "select c.*, b.section1, b.section2, b.section3, b.section4\n" +
"from %s c\n" +
" left join (\n" +
" select %s\n" +
" , sum(case \n" +
" when %s >= 0\n" +
" and %3$s < %s\n" +
" then 1\n" +
" else 0\n" +
" end) as section1\n" +
" , sum(case \n" +
" when %3$s >= %4$s\n" +
" and %3$s < %s\n" +
" then 1\n" +
" else 0\n" +
" end) as section2\n" +
" , sum(case \n" +
" when %3$s >= %5$s\n" +
" and %3$s < %s\n" +
" then 1\n" +
" else 0\n" +
" end) as section3\n" +
" , sum(case \n" +
" when %3$s >= %6$s\n" +
" and %3$s <= %s\n" +
" then 1\n" +
" else 0\n" +
" end) as section4\n" +
" from %s\n" +
" group by %2$s\n" +
" ) b\n" +
" on c.id = b.%2$s";
return new ReferenceAnswer(String.format(sql,
resuTable.getQuestionTable().getTableInfo().getName(),
resuTable.getFieldInfo().getName(),
fieldInfo.getName(),
nums[1],
nums[2],
nums[3],
nums[4],
questionTable.getTableInfo().getName()
));
}
/**
* 生成问题的标题
* @param resuDesc
* @return
*/
public String getQuestionTitle(FieldInfo fieldInfo, int[] nums,String resuDesc){
return String.format("统计每个%s的各%s段人数[0,%s]、[%3$s,%s]、[%4$s,%s]、[%5$s,%s], 分别取别名为section1、section2、section3、section4.并显示%1$s基本信息",
resuDesc,
getFieldDesc(fieldInfo),
nums[1],
nums[2],
nums[3],
nums[4]);
}
/**
* 生成问题思路
* @return
*/
public String getThinking(){
return String.format("group by 以后的查询结果无法使用别名,所以不要想着先单表 group by 计算出结果再从第二张表里添上课程信息,而应该先将两张表 join 在一起得到所有想要的属性再对这张总表进行统计计算.");
}
}
如何对比生成的答案和用户输入的答案?★★★
首先大家想到的是直接对比输入和答案的SQL语法,但是勇哥没有按这个思路实现,因为同一个需求SQL有多种写发;因此对比结果最好采取数据结果集对比;把用户输入的SQL的结果与标准答案SQL的结果集逐行对比,用户输入的SQL的结果完成匹配则答对。这种方式有点耗性能,但是效果最直观,也是可取的;性能问题可以现在一下两个结果集最大不错过20行数据。