前言

所有人工智能程序都是mysql存储过程,存放于数据库中。而网页程序只是输入输出的显示,不含人工智能程序。
看完教程,就能轻松看懂人工智能源码。源码做了充分的注释,源码本身也是一种教程,从而构成完整的学习体系。


1.按指定词语分割句子

-- 字符串分割是自然语言处理的基础。
declare say varchar(100);#全句
declare word varchar(10);#分割词

declare position_start varchar(10);#分割词在句中的开始位置
declare word_length varchar(10);#分割词的长度
declare position_last varchar(10);#分割词在句中的结束位置
declare word_left varchar(10);#分割词左边句(重要)
declare word_right varchar(10);#分割词右边句(重要)

declare start1char varchar(10);#句子开始的1个字符
declare start2char varchar(10);#句子开始的2个字符
declare last1char varchar(10);#句子最后的1个字符
declare last2char varchar(10);#句子最后的2个字符


declare word_left1 varchar(3);#分割词左边1个字符
declare word_left2 varchar(6);#分割词左边2个字符
declare word_right1 varchar(3);#分割词右边1个字符
declare word_right2 varchar(6);#分割词右边2个字符

declare word2 varchar(10);#第2个分割词
declare word1_word2 varchar(50);#两个分割词之间的内容
declare word_con varchar(10);#词语连接

-- (1)用谓语分割句子,从而找到主语和宾语
set say = "猫吃鼠";#全句
set word = "吃";#分割词(谓语)

set position_start = instr(say,word);#分割词在句中的开始位置,值为2,也就是第二个字
/*
函数instr(全句,分割词),返回值是分割词在全句中的位置
如果全句不含该分割词,instr的返回值为0,从而能判断全句是否包含该分割词
*/
set word_length = char_length(word);#分割词的长度,值为1,就是长为1个字符
/*
函数char_length用于测量变量的长度
char_length函数和length函数的区别在于,char_length中,一个汉字长度为1,而length中,一个汉字长度为3
*/
set position_last = position_start + word_length;#分割词在句中的结束位置,值为2加1,等于3
set word_left = substring(say,1,position_start-1);#分割词左边句,值为猫,就是主语
/*
函数substring(全句,截取的位置,截取的长度)
截取的位置:正数从左数,负数从右数
截取的长度:从左向右数
*/
set word_right = substring(say,position_last);#分割词右边句,值为鼠,就是宾语
-- 合并,这样仅需要全句和分割词两个参数
set word_left = substring(say,1,instr(say,word)-1);#分割词(吃)的左边句,值为猫,就是主语
/*
或word_left = substring_index(say,word,1);
函数说明:substring_index(全句,截取的字符,第几次出现该字符)
这个函数是以第n次出现的分割词,从右向左截取句子,如果要从左向右,第三个参数填负数
*/
set word_right = substring(say,instr(say,word) + char_length(word));#分割词(吃)的右边句,值为鼠,就是宾语
-- 或word_right = substring_index(say,word,-1);

-- (2)从句子的头尾截取字符
set start1char = substring(say,1,1);#句子开始的第1个字符:猫
set start2char = substring(say,1,2);#句子开始的前2个字符:猫吃
set last1char = substring(say,-1,1);#句子最后的1个字符:鼠
set last2char = substring(say,-2,2);#句子最后的2个字符:吃鼠

-- (3)从分割词左右截取字符
set say = "白色的猫吃黑色的鼠";#全句内容变长,分割词还是“吃”
set word_left1 = substring(word_left,-1,1);#分割词左边1个字符,word_left已经是分割词左边的句子了,值为猫
set word_left2 = substring(word_left,-2,2);#分割词左边2个字符,值为的猫
set word_right1 = substring(word_right,1,1);#分割词右边1个字符,word_right已经是分割词右边的句子了:值为黑
set word_right2 = substring(word_right,1,2);#分割词右边2个字符:值为黑色
-- 展开,这样仅需要全句和分割词两个参数
set word_left1 = substring(substring(say,1,instr(say,word)-1),-1,1);
set word_left2 = substring(substring(say,1,instr(say,word)-1),-2,2);
set word_right1 = substring(substring(say,instr(say,word) + char_length(word)),1,1);
set word_right2 = substring(substring(say,instr(say,word) + char_length(word)),1,2);

-- (4)两个分割词之间的内容
set word2 = "鼠";#添加第二个分割词“鼠”,第一个分割词是“吃”
-- 两个分割词之间的内容:substring_index(substring_index(全句,分割词1,-1),分割词2,1)
set word1_word2 = substring_index(substring_index(say,word,-1),word2,1);#“吃”字和“鼠”字之间的内容:黑色的

-- 词语连接
-- 函数说明:concat(要连接起来的各个词语)
set word_con = concat(word,"-",word2);#值为吃-鼠
-- 注意:如果一个变量没有赋初值,或为NULL值,concat是不能连接的

-- 显示结果
select say,word,
word_left,word_right,
position_start,word_length,position_last,
start1char,start2char,last1char,last2char,
word_left1,word_left2,word_right1,word_right2,
word2,word1_word2,word_con;

2.游标

/*
游标用来判断句子中的一个词是否属于数据库的词语表
游标可以一行行逐个从词语表里读取词语,然后和句子中的词进行比对。如果相同,就说明句子里的词属于词语表
游标有个缺点,符合条件的最后一行,会重复读2次,为了防止重复,可以加个old_word变量
*/
declare say varchar(100);#输入的话语
declare find_word varchar(10);#要找的词
declare old_word varchar(10);#游标上次读取的值
declare basket varchar(10);#临时变量,用于存储游标读出的数据
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare noun_fish cursor for select word_col from noun;#游标:找名词
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

set say = "可爱的兔子蹦蹦跳跳";#前提是名词表里已经有名词“兔子”
set find_word = "";#要找的词,默认为空。如果经过游标程序,依然为空,就说明没找到
set sign = 0;#0表示游标还没有读到底
set old_word= "";#初始化为空
set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
open noun_fish;#打开游标
while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
	fetch noun_fish into basket;#把读到的值放入临时变量basket中
  -- 函数说明:instr(全句,词语),如果词语包含在句子中,返回值大于0(词语在句子中的位次),否则返回值等于0
	if instr(say,basket) > 0 and basket != old_word then#游标这次从表格中读出的词语,包含在句子中,且与上次读到的值不一样
		set find_word = basket;#找到了,就赋值
		set old_word = basket;#现在找到的新词,对下一次循环来说,就是旧词了
  end if;
end while;#结束循环
close noun_fish;#关闭游标

-- 显示结果
select find_word;
/*显示的值
find_word:兔子
*/

3.动态SQL

-- 动态SQL:根据不同情况,把不同的SQL程序碎片拼接到一起,组成程序,然后执行,从而实现机器人自己给自己编程
declare n1 varchar(100);
declare n2 varchar(100);
declare n3 varchar(100);
declare n4 varchar(100);
declare n5 varchar(100);
declare n varchar(500);
declare result varchar(10);

set n1 = "select object_col ";
set n2 = "from know ";
set n3 = "where subject_col = ";
set n4 = "'猫'";#双引号里要加单引号
set n5 = " limit 1";#只要一个结果
set result = "";#默认为空值

set n = concat(n1,n2,n3,n4,n5);#p1的值为select subject_col from know where subject_col = '猫' limit1
set @p1 = n;
prepare p2 FROM @p1;#准备
execute p2 ;#执行查询
deallocate prepare p2;#释放
set result = @p3;#获取结果

-- 显示结果
select result;

-- 显示的值
-- result:鼠

4.select时的常用函数

/*
select的对象的值,不能是NULL值。如果是NULL值,就会报错,所以要用IFNULL函数
所以select subject_col from know的安全写法(避免报错的写法)是select IFNULL(subject_col,'') from know
IFNULL(列名,'')意思是把列中找到的NULL值,变为空值,这样就避报错

空值和NULL的区别:
计算行数count(列名)时,如果遇到NULL就忽略不计(跳过),但是遇到空值要记为一行数据
存储上,空值不占存储空间,而NULL值要占存储空间
可见,空值表示没有,NULL表示忽略
空值用 = 和 != 比较,而NULL值只能用is和is not比较

group_concat函数:select时,把同一列的多个查询结果,连接到一起,成为一个聚合值

concat_ws函数;select时,把同一行上,不同列的查询结果,连接到一起,成为一个聚合值
concat_ws('分隔符',列名1,列名2)

如果输入set result = (select object_col from know where subject_col = '猫');会报错,因为返回值不止一个
解决方法1:用group_concat函数,把所有返回值,拼接成一个返回值
解决方法2:写limit 1 就只返回第一个返回值
*/

declare result varchar(100);
declare result2 varchar(100);
declare result3 varchar(100);

set result = (select group_concat(object_col) from know where subject_col = '猫');
-- 再复杂一点:返回值不要有重复值,用distinct
set result2 = (select group_concat(distinct object_col) from know where subject_col = '猫');

-- 把三列的结果,以逗号连接到一起,作为一个结果返回
set result3 = (select concat_ws(',',verb1_col,verb2_col,object_col) from know where subject_col = '猫' limit 1);

-- 显示结果
select result,result2,result3;#把多个返回值拼接到一起返回

5.基本的语义理解和问答

-- 定义变量
declare say varchar(100);#输入的话语
declare subject_find varchar(10);#主语
declare verb_find varchar(10);#谓语动词
declare object_find varchar(10);#宾语

declare left_part varchar(100);#谓语动词左边的句子
declare right_part varchar(100);#谓语动词右边的句子

declare sl_col varchar(100);#动态sql中,select要查询的列的内容
declare wr varchar(100);#动态sql中,select的条件where的内容
declare s1 varchar(500);#动态sql中,临时变量,s1内容量大

declare judge varchar(10);#判断要插入表的数据,表中是否已存在

declare reply varchar(100);#电脑的回答(回答语义理解)
declare answer varchar(100);#电脑的回答(普通方式问答)
declare answer2 varchar(100);#电脑的回答(动态sql方式问答)

-- 定义游标
declare old_word varchar(10);#游标上次读取的值
declare basket varchar(10);#临时变量,用于存储游标读出的数据
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare noun_fish cursor for select word_col from noun;#游标:找名词(从名词表noun的word_col列寻找)
declare verb_fish cursor for select word_col from verb;#游标:找动词(从动词表verb的word_col列寻找)
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

-- 变量初始化
set say = "白色的猫吃黑色的鼠";#输入的语句
set subject_find = "";#主语初始化为空值
set verb_find = "";#谓语动词初始化为空值
set object_find = "";#宾语初始化为空值
set left_part = "";#谓语动词左边句初始化为空值
set right_part = "";#谓语动词右边句初始化为空值
set sl_col = "";#select对象内容初始化为空值
set wr = "";#条件where的内容初始化为空值
set s1 = "";#动态sql的临时变量初始化为空值
set judge = "";#judge变量用于判断要插入表中的数据是否已存在于表中,初始化为空
set reply = "";#电脑的回答(语义理解)初始化为空值
set answer = "";#电脑的回答(普通方式问答)初始化为空值
set answer2 = "";#电脑的回答(动态sql方式问答)初始化为空值

-- 第一步:找谓语动词
set sign = 0;#0表示游标还没有读到底
set old_word= "";#初始化为空
set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
open verb_fish;#打开找动词的游标
while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
	fetch verb_fish into basket;#把读到的值放入临时变量basket中
  -- 函数说明:instr(全句,词语),如果词语包含在句子中,返回值大于0(词语在句子中的位次),否则返回值等于0
	if instr(say,basket) > 0 and basket != old_word then#游标这次从表格中读出的词语,包含在句子中,且与上次读到的值不一样
		set verb_find = basket;#找到了词语“吃”,就赋值给动词
		set old_word = basket;#现在找到的新词,对下一次循环来说,就是旧词了
  end if;
end while;#结束循环
close verb_fish;#关闭游标

/*
第二步:按谓语动词分割句子
谓语动词左边句找到的名词,就是主语
谓语动词右边句找到的名词,就是宾语
*/
if verb_find != "" then#找到了谓语动词“吃”
	-- 按分割词分割句子的方法,前面存储过程z1已经讲过了
	set left_part = substring(say,1,instr(say,verb_find)-1);#分割词(谓语动词“吃”)的左边句,既“白色的猫”
	set right_part = substring(say,instr(say,verb_find) + char_length(verb_find));#分割词(谓语动词“吃”)的右边句,既“黑色的鼠”
	
	-- 第三步:谓语动词左边句(left_part)中,找主语
	set sign = 0;#0表示游标还没有读到底
	set old_word= "";#初始化为空
	set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
	open noun_fish;#打开找名词的游标
	while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
		fetch noun_fish into basket;#把读到的值放入临时变量basket中
		-- 函数说明:instr(全句,词语),如果词语包含在句子中,返回值大于0(词语在句子中的位次),否则返回值等于0
		if instr(left_part,basket) > 0 and basket != old_word then#游标这次从表格中读出的词语,包含在句子中,且与上次读到的值不一样
			set subject_find = basket;#找到了词语“猫”,就赋值给主语
			set old_word = basket;#现在找到的新词,对下一次循环来说,就是旧词了
		end if;
	end while;#结束循环
	close noun_fish;#关闭游标
	
	-- 第四步:谓语动词右边句(right_part)中,找宾语
	set sign = 0;#0表示游标还没有读到底
	set old_word= "";#初始化为空
	set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
	open noun_fish;#打开找名词的游标
	while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
		fetch noun_fish into basket;#把读到的值放入临时变量basket中
		-- 函数说明:instr(全句,词语),如果词语包含在句子中,返回值大于0(词语在句子中的位次),否则返回值等于0
		if instr(right_part,basket) > 0 and basket != old_word then#游标这次从表格中读出的词语,包含在句子中,且与上次读到的值不一样
			set object_find = basket;#找到了词语“鼠”,就赋值给宾语
			set old_word = basket;#现在找到的新词,对下一次循环来说,就是旧词了
		end if;
	end while;#结束循环
	close noun_fish;#关闭游标
	
	-- 第五步:把找到的主谓宾,插入到知识表中
	-- 如果要插入表中的数据,已存在于表中,judge就会被赋值,否则judge的值为空值或NULL值
	-- limit 1表示只要一个返回结果,如果返回多个结果就会报错
	set judge = (select subject_col from know where verb1_col = verb_find and object_col = object_find limit 1);
	-- 如果表中没有此数据,才插入
	if judge = "" or judge is NULL then
		insert into know(subject_col,verb1_col,object_col) values(subject_find,verb_find,object_find);
	end if;
	
	-- 第六步:电脑的回答(语义理解)
	set reply = concat("主语:",subject_find," 谓语动词:",verb_find," 宾语:",object_find);#concat函数用于连接字符串和变量
	
else#没有找到谓语动词
	set reply = "没有谓语动词";#电脑的回答
end if;

/*
第七步(方式一):普通方式问答
用户输入“什么吃鼠”

按前面的方法,找到主语是词语“什么”,谓语动词是词语“吃”,宾语是词语“鼠”
前提是词语“什么”,已经作为名词,存入名词表中
提问主语,那么select的对象就是主语,where条件的对象是谓语动词“吃”和宾语“鼠”
*/
set say = "什么吃鼠";
-- 找寻方法(语义理解)就不重复写了,按前面的方法就行,下面只写找寻结果
set subject_find = "什么";#主语为“什么”
set verb_find = "吃";#谓语动词为“吃”
set object_find = "鼠";#宾语为“鼠”

if subject_find = "什么" then#提问主语,如果用户输入“什么吃鼠”,那么此时subject_find就为“什么”
	-- 知识表中,主语作为select的对象,谓语动词和宾语作为select的条件(where条件)
	set answer = (select subject_col from know where verb1_col = verb_find and object_col = object_find limit 1);
elseif verb_find = "怎么" then#提问谓语动词,如果用户输入“猫怎么鼠”,那么此时verb_find就为“怎么”
	-- 知识表中,谓语动词作为select的对象,主语和宾语作为select的条件(where条件)
	set answer = (select verb1_col from know where subject_col = subject_find and object_col = object_find limit 1);
elseif object_find = "什么" then#提问宾语,如果用户输入“猫吃什么”,那么此时object_find就为“什么”
	-- 知识表中,宾语作为select的对象,主语和谓语动词作为select的条件(where条件)
	set answer = (select object_col from know where subject_col = subject_find and verb1_col = verb_find limit 1);
end if;

/*
第七步(方式二):动态sql方式问答
动态sql方式,就是根据需要,组装sql指令碎片,形成查询程序,然后执行
*/
if subject_find = "什么" then
	set sl_col = "subject_col";#select的对象列为subject_col
	/*
	如果subject_col为没有值,就会返回NULL值,就会报错
	所以最好把select subject_col写为select IFNULL(subject_col,'')
	这样的话,如果subject_col为NULL值时,就会替换成空值,而防止报错
	因此最好写为sl_col = "IFNULL(subject_col,'')";
	注意:双引号"里要包单引号',双引号里不能包双引号,单引号里又可以包双引号
	concat函数连接字符串和变量,逗号把各部分连接在一起
	变量verb_find左右,都要有单引号'包着,因为执行时,verb_find的内容是'吃'
	这里的concat把8个部分连接在一起,逗号是各部分的分隔符
	第1个部分:verb1_col = 
	第2个部分:'
	第3个部分:verb_find
	第4个部分:'
	第5个部分:and object_col =
	第6个部分:'
	第7个部分:object_find
	第8个部分:'
	这8部分执行时为verb1_col = '吃' and object_col = '鼠'
	这8部分就是select语句中,条件where的内容
	执行时,变量verb_find已经替换为吃,变量object_find已经替换为鼠
	*/
	set wr = concat("verb1_col = ","'",verb_find,"'"," and object_col = ","'",object_find,"'");
elseif verb_find = "怎么" then#如果提问词是谓语“怎么”,例如猫怎么鼠。可见不同的提问词,产生的动态sql指令不同
	set sl_col = "verb1_col";#select的对象列为谓语动词列,既verb1_col
	set wr = concat("subject_col = ","'",subject_find,"'"," and object_col = ","'",object_find,"'");
elseif object_find = "什么" then#如果提问词是宾语“什么”,例如猫吃什么
	set sl_col = "object_col";#select的对象列为宾语列,既object_col
	set wr = concat("subject_col = ","'",subject_find,"'"," and verb1_col = ","'",verb_find,"'");
end if;
/*
sql碎片组装成sql指令
变量sl_col存放的是select的对象列
变量wr存放的是where的条件内容
group_concat函数是吧多个返回值拼接成一个返回值
separator '、'表示这些返回值用顿号(、)隔开
distinct表示不要重复的值,保证每个值都不一样
into @find 是吧select的查询结果,放到变量find里
from know 是从知识表know里查询
动态sql的语法规则是有些古怪
先要prepare准备,然后execute执行,最后deallocate prepare释放变量
有些变量必须加@,有些变量不用加@
s1要定义,带@号的不用定义,prepare对象(s3)也不用定义
*/
set s1 = concat("select group_concat(distinct ",sl_col," separator '、') into @find from know where ",wr);
set @s2 = s1;
prepare s3 FROM @s2;#准备
execute s3 ;#执行查询
deallocate prepare s3;#释放
set answer2 = @find;#获取结果

-- 第八步:输出结果
select reply,answer,answer2;
/*
显示:
reply:主语:猫 谓语动词:吃 宾语:鼠
answer:猫
answer2:猫
*/

6.按标点符号分割段落

declare say varchar(300);#输入的话语
declare sp1 varchar(100);#第一次循环,按逗号分割句子,所得到的分割句。sp是split的简写
declare sp2 varchar(100);#第二次循环,按句号分割句子,所得到的分割句
declare sp3 varchar(100);#第三次循环,按问号分割句子,所得到的分割句
declare sp4 varchar(100);#第四次循环,按感叹号分割句子,所得到的分割句

declare i1 int;#第一次循环的循环变量
declare i2 int;#第二次循环的循环变量
declare i3 int;#第三次循环的循环变量
declare i4 int;#第四次循环的循环变量

declare sentence varchar(100);#符号(。,?!)分成的每个单句
declare n1 varchar(100);#第1个单句
declare n2 varchar(100);#第2个单句
declare n3 varchar(100);#第3个单句
declare n4 varchar(100);#第4个单句
declare n5 varchar(100);#第5个单句
declare n6 varchar(100);#第6个单句

-- 初始化
set say = "猫在院子里玩,看见了一只鼠。竟然有老鼠!猫走过去,猫会吃它吗?肯定会的。";
set n1 = "";
set n2 = "";
set n3 = "";
set n4 = "";
set n5 = "";
set n6 = "";

/*
第一次循环:先按逗号分割句子。因为段落中,逗号最多,能分出最多的段
第二次循环:逗号分割出的分割段中,再用句号进一步分割
第三次循环:句号分割出的分割段中,再用问号进一步分割
第四次循环:问号分割出的分割段中,再用感叹号进一步分割
四次循环下来,就是单句了,存放于sp4中
*/
-- 第一层循环:按逗号分割句子,循环次数取决于逗号把句子分成的段落数
set sp1 = "";
set i1 = 0;
/*
长度1:char_length(say)::既原本句子的长度
长度2:char_length(REPLACE(say,',','')),既逗号被变空(去掉逗号)后,句子的长度
replace是替换函数,把say中的逗号,替换为空无
那么长度1减长度2,就是逗号数量。之后再加1,就是逗号把句子分割成的段落数。为什么要加1?因为2个逗号分割出3段,而不是2段
当循环变量i1小于逗号把句子分成的段落数,就执行循环,循环结束后,i1会增加1,直到每个段落都被处理完,既i1增加到与段落数量值相等
*/
while i1 < char_length(say)-char_length(REPLACE(say,',',''))+1 do
	set sp1 = SUBSTRING_INDEX(SUBSTRING_INDEX(say,',',i1+1),',',-1);#sp:split,逗号把句子分成的段
	
	-- 第二循环:按句号分割句子
	set sp2 = "";
	set i2 = 0;
	while i2 < char_length(sp1)-char_length(REPLACE(sp1,'。',''))+1 do
		set sp2 = SUBSTRING_INDEX(SUBSTRING_INDEX(sp1,'。',i2+1),'。',-1);
		
		-- 第三层循环:按问号分割句子
		set sp3 = "";
		set i3 = 0;
		while i3 < char_length(sp2)-char_length(REPLACE(sp2,'?',''))+1 do
			set sp3 = SUBSTRING_INDEX(SUBSTRING_INDEX(sp2,'?',i3+1),'?',-1);
			
			-- 第四层循环:按感叹号分割句子
			set sp4 = "";
			set i4 = 0;
			while i4 < char_length(sp3)-char_length(REPLACE(sp3,'!',''))+1 do
				set sp4 = SUBSTRING_INDEX(SUBSTRING_INDEX(sp3,'!',i4+1),'!',-1);
				-- 现在,按符号分割句子完成,已都是符号分出的单句sp4
				
				-- 注意要写sp4不为空,因为如果只有一句话和一个标点符号,标点符号右边就会分割出空值
				if sp4 != "" then
					if n1 = "" then
						set n1 = sp4;
					elseif n2 = "" then
						set n2 = sp4;
					elseif n3 = "" then
						set n3 = sp4;
					elseif n4 = "" then
						set n4 = sp4;
					elseif n5 = "" then
						set n5 = sp4;
					elseif n6 = "" then
						set n6 = sp4;
					end if;
				end if;

				-- 单句处理完成
				set i4 = i4 + 1;#循环变量自增1
			end while;#结束第四层循环	

			set i3 = i3 + 1;#循环变量自增1
		end while;#结束第三层循环
	
		set i2 = i2 + 1;#循环变量自增1
	end while;#结束第二层循环
	
	set i1 = i1 + 1;#循环变量自增1
end while;#结束第一层循环

-- 输出结果
select n1,n2,n3,n4,n5,n6;
/*
显示的值:
n1:猫在院子里玩
n2:看见一只老鼠
n3:竟然有老鼠
n4:猫走过去
n5:猫会吃它吗
n6:肯定会的
*/

7.词语槽

declare say varchar(100);#输入的话语
declare noun1_basket varchar(10);#名词槽1
declare noun2_basket varchar(10);#名词槽2
declare noun3_basket varchar(10);#名词槽3
declare noun4_basket varchar(10);#名词槽4

-- 游标(从词库逐个读取词语)
declare cover int;
declare basket varchar(10);#临时变量,用于存储游标读出的数据
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare noun_fish cursor for select word_col from noun;#游标:找名词
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

-- 初始化
set say = "熊猫吃竹子";
set noun1_basket = "";
set noun2_basket = "";
set noun3_basket = "";
set noun4_basket = "";

/*
游标找名词,有时会出现一种错误:
例如词语“熊猫”,会被当成三个名词:熊、猫、熊猫,这样就不对了,解决方法:
长词(游标找到的新词)覆盖短词(已找的旧词):“熊猫”覆盖“熊”和“猫”
长词(已找的旧词)吸收短词(游标找到的新词):“熊猫”吸收“熊”和“猫”
这样最后只有一个名词:熊猫,不会出现熊、猫
游标新词,赋值或覆盖或被吸收后,变量cover从0变1,这是为了避免向新的空名词槽再次赋值
*/
-- 找名词
set sign = 0;#0表示游标还没有读到底
set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
open noun_fish;#打开游标
while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
	fetch noun_fish into basket;#把读到的值放入临时变量basket中
	-- 如果游标从词语表读出的词,包含在句子中
	if instr(say,basket) > 0 then
			set cover = 0;#控制basket只能给新的空名词槽赋值一次
			
			-- 名词槽1
			if noun1_basket = "" then#第一个槽子就是空的,后面几个槽子更是空的
				set noun1_basket = basket;#游标找到名词(熊猫,或熊,或猫),给槽子赋值,占据名词槽1
				set cover = 1;#赋值后,占据标志变为1
				/*
				下面的else,表示第一个槽子已经有值了(之前已被其它名词占据)
				那就要考虑词语的覆盖和吸收:
				如果这次游标找到的名词是熊猫,而之前槽子里的词是熊,就要长词覆盖短词
				如果这次游标找到的名词是熊,而之前槽子里的词是熊猫,就要长词吸收短词
				*/
			else
				/*
				instr(basket,noun1_basket) > 0表示basket(游标找到的新词)包含noun1_basket(名词槽1的已有名词)
				也就是说,游标找的新词(熊猫)包含槽子里的旧词(熊),所以长词(basket)覆盖短词(noun1_basket),长词(熊猫)成为槽子里的新词
				*/
				if instr(basket,noun1_basket) > 0 then
					set noun1_basket = basket;
					set cover = 1;
				/*
				instr(noun1_basket,basket) > 0表示noun1_basket(名词槽1已有名词)包含basket(游标找到的新词)
				也就是说,槽子里的旧词(熊猫)包含了游标找到的新词(熊),所以长词(noun1_basket)吸收短词(basket),也就是不赋值,保留原值
				*/
				elseif instr(noun1_basket,basket) > 0 then
					set cover = 1;
				end if;
			end if;
			
			-- 名词槽2
			if noun2_basket = "" then
				if cover = 0 then
					set noun2_basket = basket;
					set cover = 1;
				end if;
			else
				if instr(basket,noun2_basket) > 0 then
					if cover = 0 then
						set noun2_basket = basket;
						set cover = 1;
					else
						set noun2_basket = "";
					end if;
				elseif instr(noun2_basket,basket) > 0 then
					set cover = 1;
				end if;
			end if;
			
			-- 名词槽3
			if noun3_basket = "" then
				if cover = 0 then
					set noun3_basket = basket;
					set cover = 1;
				end if;
			else
				if instr(basket,noun3_basket) > 0 then
					if cover = 0 then
						set noun3_basket = basket;
						set cover = 1;
					else
						set noun3_basket = "";
					end if;
				elseif instr(noun3_basket,basket) > 0 then
					set cover = 1;
				end if;
			end if;
			
			-- 名词槽4
			if noun4_basket = "" then
				if cover = 0 then
					set noun4_basket = basket;
					set cover = 1;
				end if;
			else
				if instr(basket,noun4_basket) > 0 then
					if cover = 0 then
						set noun4_basket = basket;
						set cover = 1;
					else
						set noun4_basket = "";
					end if;
				elseif instr(noun4_basket,basket) > 0 then
					set cover = 1;
				end if;
			end if;

	end if;
		
end while;#结束循环
close noun_fish;#关闭游标

-- 输出结果
select noun1_basket,noun2_basket,noun3_basket,noun4_basket;
/*
noun1_basket:熊猫
noun2_basket:竹子
noun3_basket:空值
noun4_basket:空值
这样就不会出现名词:熊、猫
*/

8.找数词

-- 先找数词单位,数词单位左边就是数字
declare say varchar(100);#输入的话语
declare num varchar(10);#数字
declare unit varchar(10);#数词单位

declare unit_left varchar(50);#数词单位的左边
declare unit_left_length int;#数词单位的左边的字符串的长度

declare i int;#要检测的字符位置
declare n varchar(3);#单个字符的内容
declare w int;#判断字符是否是数字

-- 游标
declare basket varchar(20);#临时变量,用于存储游标读出的数据
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare unit_fish cursor for select unit_col from other_word;#游标:找数词单位
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

-- 初始化
set say = "山上有162只猫";
set num = "";
set unit = "";
set unit_left = "";
set unit_left_length = 0;

-- 游标操作
set sign = 0;
set basket = "";
open unit_fish;
while sign != 1 do
	fetch unit_fish into basket;
	if instr(say,basket) > 0 then#找到数词单位
		set unit = basket;
		
		-- 开始找数字
		set unit_left = substring_index(say,unit,1);#数词单位左边的字符串
		set unit_left_length = char_length(unit_left);#数词单位左边内容的长度。数字在数词单位左边,所以往左边看
		set i = 1;#数词单位左边的内容的从右向左数第1个字符,例如64个,i=1时,n的值是4,i=2时,n的值是6
		lable_num: begin#设置标记,为了后面跳出循环
			-- 循环是逐个字符的操作,从第1个字符(i=1),一直到左边最后一个字符。unit_left_length就是最后一个字符的位置
			while(i <= unit_left_length) do
				set n = substring(unit_left,-i,1);#该字符的内容,-i是从右向左计算
				-- FIND_IN_SET函数判断一个字符(变量n中的字符),是否属于字符集合(双引号里的数字)中的一个
				set w = FIND_IN_SET(n,"0,1,2,3,4,5,6,7,8,9,零,一,二,两,三,四,五,六,七,八,九,十,百,千,万,亿,兆");#如果该字符属于集合中的数字,返回值就大于0
				if w > 0 then #该字符为数字
					set num = concat(n,num);#每次循环新找的数字字符,要与之前的数字字符拼接。因为数字往往不止是一个字符,例如64就是2个字符,需要把6和4拼接到一起
				else#遇到不是数字的字符,就该跳出循环了。例如山上有162只猫,从数词单位“只”向左找数字,遇到“有”字,找数字就结束了,找到的数字是162
					leave lable_num;#跳出循环
				end if;
				set i = i + 1;#要检测的字符位置,向左移动一位,为了看看下一个字符是否还是数字
			end while;
		end lable_num;#跳出循环后,到这里
		
	end if;
end while;
close unit_fish;

-- 输出结果
select num,unit;
/*
num:162
unit:只
*/

9.动词的词性辨析

/*
词性辨析是多种方法并用,现在介绍其中一种方法
词性辨析表(verb_judge)有4列
id:编号
word_col:要判断的动词,看它到底是不是动词
type_col:动词的左右位置
content_col:动词左右位置的字符
+----------+----------+-------------+
| word_col | type_col | content_col |
+----------+----------+-------------+
|   学     |    r1    |     校      |
+----------+----------+-------------+
|   生     |    l1    |     花      |
+----------+----------+-------------+
“学”字右边1个字符(r1)是“校”字时,“学”字不做动词,因为“学校”是名词
“生”字左边1个字符(l1)是“花”字时,“生”字不做动词,因为“花生”是名词
*/
declare say varchar(100);#用户输入的话语
declare verb varchar(10);#要判断的动词
declare not_verb int;#是否为动词:0:是动词,1:非动词

declare left_part varchar(50);#分割词(动词)左边句子
declare right_part varchar(50);#分割词右边句子
declare left_1char varchar(3);#分割词左边第1个字符
declare left_2char varchar(6);#分割词左边的2个字符
declare left_3char varchar(9);#分割词左边的3个字符
declare right_1char varchar(3);#分割词右边1个字符
declare right_2char varchar(6);#分割词右边2个字符
declare right_3char varchar(9);#分割词右边3个字符

declare type_data varchar(10);#r1、l1、r2、l2等标识

-- 游标
declare basket varchar(10);
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare horse cursor for select content_col from verb_judge where word_col = verb;#游标
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

-- 初始化
set say = "美丽的学校";
set verb = "学";
set not_verb = 0;#默认是动词
set left_part = "";
set right_part = "";
set left_1char = "";
set left_2char = "";
set left_3char = "";
set right_1char = "";
set right_2char = "";
set right_3char = "";
set type_data = "";

-- 句子分割
-- verb左边的句子
set left_part = substring(say,1,instr(say,verb)-1);
-- verb右边的句子
set right_part = substring(say,instr(say,verb) + char_length(verb));
-- verb左边的1个字符
set left_1char = substring(left_part,-1,1);
-- verb左边的2个字符
set left_2char = substring(left_part,-2,2);
-- verb左边的3个字符
set left_3char = substring(left_part,-3,3);
-- verb右边的1个字符
set right_1char = substring(right_part,1,1);
-- verb右边的2个字符
set right_2char = substring(right_part,1,2);
-- verb右边的3个字符
set right_3char = substring(right_part,1,3);

-- 按判断表(verb_judge)来判断
-- word_col是要判断的字词(动词),type_col是左右位置,content_col是左右位置的字词,数据符合就不是动词了
set sign = 0;
set basket = "";
open horse;
while sign != 1 do
	fetch horse into basket;
  -- 变量verb范围里,basket是游标读出的content_col的一个内容。三列条件知道两个,可以找出剩下一列(type_col)的一个内容
  set type_data = (select type_col from verb_judge where word_col = verb and content_col = basket limit 1);
  if type_data = "r1" and basket = right_1char then#三个数据都对应上了
		set not_verb = 1;#不是动词
  elseif type_data = "l1" and basket = left_1char then
		set not_verb = 1;
	elseif type_data = "r2" and basket = right_2char then
		set not_verb = 1;
	elseif type_data = "l2" and basket = left_2char then
		set not_verb = 1;
	elseif type_data = "r3" and basket = right_3char then
		set not_verb = 1;
	end if;
end while;
close horse;

-- 输出数据
select not_verb;
-- not_verb:1,表示“美丽的学校”中的“学”字不是动词


10.深度遍历

CREATE PROCEDURE `z10`(in n varchar(100),
in str varchar(300),
in fx int,#1是深度遍历顺着找,2是深度遍历倒着找,3是广度遍历
out res varchar(500))
BEGIN
/*
作用:深度遍历和广度遍历

(1)深度遍历:顺着找(正序)
例如关系链x-a-b-c
表格(test1)里:
+------+------+
| col1 | col2 |
+------+------+
|  x   |  a   |
+------+------+
|  a   |  b   |
+------+------+
|  b   |  c   |
+------+------+
输入:x
显示:x-a-b-c
这就是深度遍历,要用递归方式(自己调用自己),到关系链底端(后面没有值了),就开始返回值
call z10("x","",1,@str);
select @str;
第一调用时,第二个参数str要给个空值
对于深度遍历,游标方式和select语句都可以搞定
对于多个结果值,select语句要把多个结果值串联成一个值返回,用的时候还要再拆分成一个个的单值
而游标对于多个结果值,一次就返回一个,处理起来比较方便

(2)深度遍历:倒着找(倒序)
x-a-b-c的关系中,输入c,显示:c-b-a-x
call z10("c","",2,@str);
select @str;

(3)广度遍历
y同时和d、e、f相连
d
|
y-e
|
f
表格(tese1)里:
+------+------+
| col1 | col2 |
+------+------+
|  y   |  d   |
+------+------+
|  y   |  e   |
+------+------+
|  y   |  f   |
+------+------+
输入:y
显示d、e、f
call z10("y","",3,@str);
select @str;

(4)避免循环
如果i-j-k-i,那么深度遍历会循环下去。为了避免这种情况,一旦找到的新数据已经找到过,说明陷入循环了,应停止
那么就要有一张表(test2)记录已经找到的数据(之前出现过的数据)。如果新找到额数据,存在于test2表里,说明循环了,就停止
表格(test1)里:
+------+------+
| col1 | col2 |
+------+------+
|  i   |  j   |
+------+------+
|  j   |  k   |
+------+------+
|  k   |  i   |
+------+------+
call z10("i","",1,@str);
select @str;
输入i,显示:i-j-k-i,不会循环下去
*/
-- 防止循环
declare judge varchar(100);#判断数据是否已经找过,从而避免循环
declare cycle int;#如果没循环,值为0,如果循环,值为1
-- 游标
declare find_data varchar(100);#游标找到的值
declare old_word varchar(10);#游标上次读取的值
declare basket varchar(10);#临时变量,用于存储游标读出的数据
declare sign int default 0;#游标变量,0表示没有没有读取到底,1表示读取到底
declare fish1 cursor for select col2 from test1 where col1 = n;#游标1:顺着找(正序)
declare fish2 cursor for select col1 from test1 where col2 = n;#游标2:倒着找(倒序)
declare continue handler for not found set sign = 1;#继续读取,读取到底,使sign值变1

-- 初始化
set @@max_sp_recursion_depth = 100;#递归深度为100,就是自己最多调用自己100次
set res = "";
set judge = "";
set cycle = 0;

-- 首次运行,先清空表test2
if str = "" then
	delete from test2;#清空表
end if;

-- (1)深度遍历:顺着找(正序)
if fx = 1 then
	set sign = 0;#0表示游标还没有读到底
	set find_data = "";
	set old_word= "";#初始化为空
	set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
	open fish1;#打开游标
	while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
		fetch fish1 into basket;#把读到的值放入临时变量basket中
		-- 游标找到了值
		if basket != "" and basket != old_word then
			set judge = (select col from test2 where col = basket);#判断新的数据是否已经找到过(之前出现过)
			if judge = "" or judge IS NULL then#之前没有找到过
				set find_data = basket;#找到值就赋值
				set old_word = basket;#现在找到的新值,对下一次循环来说,就是旧值了
				insert into test2(col) values(basket);#存入test2表中,表示已经找到的数据
			else
				set cycle = 1;
			end if;
		end if;
	end while;#结束循环
	close fish1;#关闭游标

	-- 经过游标,找到了值
	if find_data != "" and cycle = 0 then
		if str = "" then
			set str = concat(n,"-",find_data);
		else
			set str = concat(str,"-",find_data);#新数据连接到已查到的数据上
		end if;
		-- 继续找,就是递归方式,自己调用自己
		call z10(find_data,str,1,res);
	-- 经过游标,没有找到值,说明接下来已经没有数据了,也就是数据关系链已经到底了,就开始返回值
	else
		set res = str;
	end if;

-- (2)深度遍历:倒着找(倒序)
elseif fx = 2 then
	set sign = 0;#0表示游标还没有读到底
	set find_data = "";
	set old_word= "";#初始化为空
	set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
	open fish2;#打开游标
	while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作
		fetch fish2 into basket;#把读到的值放入临时变量basket中
		-- 游标找到了值
		if basket != "" and basket != old_word then
			set find_data = basket;#找到值就赋值
			set old_word = basket;#现在找到的新值,对下一次循环来说,就是旧值了
		end if;
	end while;#结束循环
	close fish2;#关闭游标

	-- 经过游标,找到了值
	if find_data != "" then
		if str = "" then
			set str = concat(n,"-",find_data);
		else
			set str = concat(str,"-",find_data);#新数据连接到已查到的数据上
		end if;
		-- 继续找,就是递归方式,自己调用自己
		call z10(find_data,str,2,res);
	-- 经过游标,没有找到值,说明接下来已经没有数据了,也就是数据关系链已经到底了,就开始返回值
	else
		set res = str;
	end if;

-- (3)广度遍历
else
	set sign = 0;#0表示游标还没有读到底
	set find_data = "";
	set old_word= "";#初始化为空
	set basket = "";#游标从表格中,每次读出的一个数据,放入临时变量basket中
	open fish1;#打开游标
	while sign != 1 do#开始循环,游标还没有读到底(sign为0,不为1),就一直循环操作

		fetch fish1 into basket;#把读到的值放入临时变量basket中
		-- 游标找到了值
		if basket != "" and basket != old_word then
			set find_data = basket;#找到值就赋值
			if res = "" then
				set res = find_data;
			else
				set res = concat(res,"、",find_data);
			end if;
			set old_word = basket;#现在找到的新值,对下一次循环来说,就是旧值了
		end if;
	end while;#结束循环
	close fish1;#关闭游标
	
end if;
END