基础训练

mysql存储过程:
先看完基础训练,再看代码,就容易了。

基础训练1:按指定词语分割句子
CREATE DEFINER=`root`@`localhost` PROCEDURE `z1`()
BEGIN
declare say varchar(100);#全句
declare start1char varchar(10);#句子开始的1个字符
declare start2char varchar(10);#句子开始的2个字符
declare last1char varchar(10);#句子最后的1个字符
declare last2char varchar(10);#句子最后的2个字符

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 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 word2_left varchar(50);#第2个分割词的左边句
declare word1_word2 varchar(50);#两个分割词之间的内容
declare word_con varchar(10);#词语连接

set say = "abcdefg";
/*
函数说明:substring(全句,截取的位置,截取的长度)
截取的位置:正数从左数,负数从右数
截取的长度:从左向右数
*/
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个字符

-- 分割词
set word = "d";

-- 按分割词截取
set position_start = instr(say,word);#分割词在句中的开始位置
set word_length = char_length(word);#分割词的长度
set position_last = position_start + word_length;#分割词在句中的结束位置
set word_left = substring(say,1,position_start-1);#分割词左边句
set word_right = substring(say,position_last);#分割词右边句
-- 合并,这样仅需要全句和分割词两个参数
set word_left = substring(say,1,instr(say,word)-1);#分割词左边句
-- 或word_left = substring_index(say,word,1);
set word_right = substring(say,instr(say,word) + char_length(word));#分割词右边句
-- 或word_right = substring_index(say,word,-1);

-- 单个字符
set word_left1 = "";
set word_left1 = substring(word_left,-1,1);#分割词左边1个字符
set word_left2 = substring(word_left,-2,2);#分割词左边2个字符
set word_right1 = substring(word_right,1,1);#分割词右边1个字符
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);

-- 再添加一个分割词word2
set word2 = "f";
/*
函数说明:substring_index(全句,截取的字符,第几次出现该字符)
这个函数是以第n次出现的分割词,从右向左截取句子,如果要从左向右,第三个参数填负数
*/
set word2_left = substring_index(say,word2,1);

-- 两个分割词之间的内容: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);

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

/*显示的值:
say:abcdefg
start1char:a
start2char:ab
last1char:g
last2char:fg
word:d
position_start:4
word_length:1
position_last:5
word_left:abc
word_right:efg
word_left1:c
word_left2:bc
word_right1:e
word_right2:ef
word2:f
word2_left:abcde
word1_word2:e
word_con:d-f
*/

END



基础训练2:游标的使用
这个存储过程用于找到句子中包含的名词。
在主谓宾句型中,先找动词,后找名词。动词左边的名词就是主语,动词右边的名词就是宾语。这样,主谓宾都找到了。

CREATE DEFINER=`root`@`localhost` PROCEDURE `z2`()
BEGIN
/*
游标可以一行行逐个从词语表中读取词语
游标有个缺点,符合条件的最后一行,会重复读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:兔子
*/

END



基础训练3:动态SQL
CREATE DEFINER=`root`@`localhost` PROCEDURE `z3`()
BEGIN
-- 动态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:鼠

END



基础训练4:select时,常用的函数
CREATE DEFINER=`root`@`localhost` PROCEDURE `z4`()
BEGIN
/*
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;#把多个返回值拼接到一起返回

END


基础训练5:最简单句型的语义理解和问答
CREATE DEFINER=`root`@`localhost` PROCEDURE `z5`()
BEGIN
-- 基础训练:最基本的语义理解和问答
-- 定义变量
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:猫
*/

END



基础训练6:按标点符号分割段落
CREATE DEFINER=`root`@`localhost` PROCEDURE `z6`()
BEGIN
-- 基础训练:按标点符号(逗号、句号、问号、感叹号)分割句子
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

         -- 给每个单句赋值
         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;

         -- 单句处理完成
         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:肯定会的
*/

END



基础训练7:词语槽
CREATE DEFINER=`root`@`localhost` PROCEDURE `z7`()
BEGIN
-- 基础训练:词语槽
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:空值
这样就不会出现名词:熊、猫
*/

END



基础训练8:找出话语中的数字
CREATE DEFINER=`root`@`localhost` PROCEDURE `z8`()
BEGIN
-- 基础训练:找数词
-- 先找数词单位,数词单位左边就是数字
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:只
*/

END


基础训练9:词性辨析
CREATE DEFINER=`root`@`localhost` PROCEDURE `z9`()
BEGIN
/*
基础训练:动词的词性辨析
词性辨析是多种方法并用,现在介绍其中一种方法
词性辨析表(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,表示“美丽的学校”中的“学”字不是动词

END