从"数据(Data)"一词中我们可以看出它是用来描述数据的,也就是说它的对象(或者说核心)应该是数据,要描述“(数据)是什么?”;从"流(Flow)"一词中我们又可以提炼出它的角度是(数据的)传递过程与加工,即“(数据)由有哪些属性构成?从哪儿来?到哪儿去?要干啥?”;最后着眼"图(Diagram)"可以得出这是以图形为基础的模型,也就是用不同的图形区分不同的类别。
所以总结起来,一句话概括:数据流图(DFD)是从数据(Data)的传递和加工(Flow)角度,以图形(Diagram)的方式来表达系统的逻辑功能、数据在系统内部的逻辑流向和逻辑变换(Flow)过程。
来,我们先看一个简单的实例(现在不知道怎么做很正常,一会儿详细解释)。
大家试想一下,我们现在已经拥有了一张"数据流图(DFD)",那么光有这么一张图可以吗?比如说,你知道\(Entity\_001\)具体是啥嘛?不知道叭,这时肯定有吐槽“那就把名称加上呗”,但\(Entity\_001\)仅仅只是一个名称嘛,既然是一个实体那是不是就还有属性呢?这时将这些属性一一标记上去这张图会是什么样,显然就非常的凌乱。所以我们就用一个代号来表示一个对象(包括实体、数据流、加工、存储),在图之外引入新概念——数据字典(DD)来对这些代号进行解释。下面就来简单看一下数据字典是什么。
不用多说肯定是一个描述"数据(Data)"的东西,那怎么描述的呢?答案就是用"字典(Dictionary)"。字典是啥,只知道《辞海》、《康熙》或者《牛津》?umm……我可以说这差不多,字典就是一种映射关系,我们小时候“作弊”用的“暗号”就是一个不成文的字典,譬如\(X\)同学规定揿动一次就选\(A\)、揿动两次就选\(B\)……,那么我们就可以得到如下的字典:
所以字典的本质也就是一张表,又由于是对数据流图的描述,那很自然地就包括如下几个方面:(Ⅰ)数据项;(Ⅱ)数据结构;(Ⅲ)数据流;(Ⅳ)数据存储;(Ⅴ)处理过程;这里先各出一个实例(用上面的数据流图为例,构造一个合理的、简化的数据字典):
(ⅰ)数据项:
(ⅱ)数据结构:
(ⅲ)数据流:
(ⅳ)数据存储:
(ⅴ)处理及加工:
我们先从最简单的图形分类入手,这里给出常用的四种基本图形及其含义【在图形中标注编号(\(ID\))或者名称(\(name\)),或者两者都标上,这样看图的时候既更直观还容易查找\(ID\)索引】:
在加工的时候不可避免会出现多种数据进出的情况,那么数据流图是怎样表示的呢?在做表之前我们先画出数据加工和数据流的图【\((\theta_1,\theta_2)\in\theta=\{+,*,\bigoplus\}\)】:
(1)、任意流不能重名因为流的名字即为他们的\(ID\)号,常识告诉我们不存在两个完全一样的物体,为了区分必须避免重名;
(2)、数据守恒原则对于任意一个数据处理来说,其全部的输出数据流中的数据必定能从其输入数据流中的数据直接获得。这一原则也说明了数据流是必须与数据处理有关的,应该也只能依附与数据流。我们可以用能量守恒来理解这一原则,将数据存储和外部实体比作物体、数据比作能量,数据处理即为物体之间的相互作用,只有在物体进行相互作用的时候才会存在能量的流动(传递),所以同理可得只有在数据处理的推动下,数据才得以传递,从而形成数据流。
①、外部实体与外部实体之间不存在数据流;
②、外部实体与数据存储之间不存在数据流;
③、数据存储与数据存储之间不存在数据流;
④、数据流必须经过(其中一端为)数据处理(加工);
(3)、守恒加工原则(前两条都是针对加工而言)
①、对于任意一个加工,至少有一个数据输入流和一个数据输出流;
②、由任意流不能重名可以推出:对于任意一个加工,输入数据流与输出数据流必须不同名,即使输入的数据与输出的数据结构一模一样,但是这样的情况常见于向数据存储文件写入数据的时候:由于数据守恒原则,数据流必须经过数据加工,但是这样的数据加工并不会改变数据流中的数据。所以人们规定:对流进或者流出数据存储文件的数据流不需要标注名字,因为文件本身就足以说明数据流。所以,如果一种数据流需要被多次引用时可以建立一个临时的数据存储(对应的就是内存数据库),这样就没有必要为同一种数据流进行标号。另外,当输入输出流一模一样的情况发生在非数据存储文件操作时,就要考虑其加工或者数据源存在的合理性和必要性了,因为这相当于没有对数据进行处理,属于冗余操作;
③、数据源包含数据存储文件、最终新形成的外部实体和数据存储文件必须与数据源同级,也就是说数据流的终点一定是在外部并且指向外部实体或者数据存储文件。因为只要有外部数据流参与,那么有入必有出,这个出就是指向新形成外部实体或数据文件的输出数据流;比如下图中的数据文件:\(Student\_Course\)。
④、与③相对应的,如果新形成的外部实体或者数据存储文件在图的内部,就表明它不是数据流的终点而是一个结点,那么必须具备一条输入流和一条输出流;比如说在②中提及的临时数据存储文件。
⑤、与③和④相反的,如果在内部的数据存储文件不是新形成的,也就是说它们是该数据库管理系统直接向操作系统中发送请求并载入内存的数据存储文件,那么这些文件可以只含有输入或者输出流;比如说,如图③所示,显然\(Course\)、\(Student\)都不是新形成的数据存储文件,所以它们虽然在顶层数据流图的内部,但是只有一条数据流(输出)。
⑥、每一个(或一组)输入流与特定的一个(或一组)形成对应,这样一对数据输入输出流在同层级图中的同深度处以及彼此的后继(更深层的)输入输出流对应该是相互独立的,去掉其中任意一对或多对之后数据流图仍然保持守恒加工原则;如图:
(ⅰ)\(DF-A\)系列与\(DF-B\)系列相互独立;
(ⅱ)\(DF-A1\)系列之间(\(DF-A1\_1\)与\(DF-A1\_2\))相互独立;
(ⅲ)彼此的后继之间(\(DF-A1\_1\)与\(DF-A1\_2-A2\)系列、\(DF-A1\_2\)与\(DF-A1\_1-A2\)系列、\(DF-A1\_1-A2\)系列与\(DF-A1\_2-A2\)系列)相互独立。
⑦、很容易被忽视的一点:数据存储不可能凭空出现在数据库管理系统中,必须通过操作系统从磁盘中读取相应的文件载入内存,当对数据存储文件操作完成之后,再写入磁盘。但是正是因为所有的数据流图都有这一类数据流,所以常常被作为枝节被省略,这导致在研究细节的时候发现很多数据流图都没有遵守守恒加工原则;所以完整的数据流图应该是这样(用虚线表示省略的):
这个时候我们就可以理解为:从操作系统中读取的文件与外部实体给予的信息进行组合(数据加工),最后形成了新的文件并写入磁盘中。
(4)、父图与子图应该统一输入输出流:父图可以看做是一个整体,也就是一个黑盒子,其中包含了很多个子图,那么子图的输入输出流当然就源自黑盒子(父图);比如学生选课后出了成绩:
一共分了两个区域(上下),上部分为整体的局部数据流图,数据处理由一个父图构成;下部分即为父图中更加细节的子图数据流图。这个时候我们可以看到父图由外部数据流(\(CourseSchedulingInfo\)、\(ScoreInfo\)、\(CourseInfo\)、\(StudentInfo\))的参与产生了一个新的数据存储(\(Student\_Course\)),必须将其提出放在父图的外部,如果不这样做的话就会违反守恒加工原则。由父图子图流一致原则我们可以得出另一重要的原则:加工细节隐蔽原则,就是说没有必要一味地对父图进行拆分,可以适当地只保留父图不去关心子图的内在联系(类比整体法)。
(5)、简化加工之间的关系加工间的数据流越少,各个加工就越相对独立、耦合度就越低,如果数据流很多的话,虽然可以实现较为复杂的功能但是也增加了运维成本,甚至可能造成动一发而牵全身的混乱局面。这就要求我们做到先抓住主要矛盾,暂时忽略枝节。
(6)、均匀分解父图尽量不要出现对数据处理分解的两极化,比如一个分了七八层而另一个只分了两三层,这样会造成轻重不一的局面存在考虑不周的潜在隐患。
在这些原则中最重要的当属:保持父图与子图平衡,保持数据平衡,加工细节隐蔽。
根据之前的分析,我们可以得出:数据字典的就是对数据流图中出现的所有被命名的图形元素在数据字典中作为一个词条加以定义,使每个图形元素的名称都有一个确切的解释。
在数据字典中最特殊的当属数据结构了,因为数据结构是描述其他字典条目的基础,所以准确来说,数据字典中应该是包含下面4种条目:
①、数据项条目:数据类型、取值范围……等;
②、数据流条目:定义、给出组成数据流的数据项(利用数据结构描述);
③、文件条目:定义、给出组成文件的数据项(利用数据结构描述);
④、加工条目:加工条目必须具备原子性,给出说明(激发条件、逻辑、处理顺序……等);
我们言归正传,数据结构的描述通常采用如下表中的符号:
比如我们要表示学生这个实体集(或者学生表)时,就可以这样:
\(StudentEntity=studentID+studentName+studentIDCard+studentSex+\{studentInterest\}+(studentInfo)\),
再比如我们新建一个可以实现查询的任务,包括查询学生信息、课程信息以及学生选课信息,这是当数据库管理员(或者教务系统管理员)向教务管理系统发出查询请求(数据流)的时候就可以这样表示:
\(selectRequestInfo=[selectStudentInfo\|\selectCourseInfo\|\selectStudent\_CourseInfo]+\{IllegalRequest\}\),
某教务管理系统的主要功能为:师生教学管理和信息查询。对于新招的师生,系统按照既定的法则自动生成师生的编号,并和他们的基本信息一起分别写入学生文件和教师文件,同样适用于新开设的课程。
(1)、新增师生和课程新招师生和课程的时候需要分别编制《入校学生单》、《新招教师单》以及《新开设课程单》以便直接对新增对象进行管理与研究,然后将信息分别写入《学生表》、《教师表》和《课程表》。
(3)、学生选课出成绩学生选课时需要编制《学生选课申请单》和《学生最新选课单》。
①、《学生选课申请提交单》:学生的\(ID\)号、课程的\(ID\)号、选课权重
②、《学生最新选课单》:学生的\(ID\)号、课程的\(ID\)号、选课权重;
假设选课权重在同一学号记录中不能重复,且为[1,6]之间的整数,当学生提交《学生选课申请单》时系统给出选项,学生自行选择,即限定每个学生的选课门数最多为6门。此外系统应该有三层过滤网(这三层过滤可以放置在一个加工中——加工细节隐蔽原则):
(ⅰ)系统首先通过聚合运算检查在《学生最新选课单》中该学生的选课门数是否为6,若不是则允许将该学生《学生选课申请提交单》的选课信息添加至《学生最新选课单》;若是则拒绝添加。
(ⅱ)如果选课门数小于6则进行冲突检查,查找《学生选课表》中该学号对应的选课课程号集合,再在《课程表》中查找"上课信息"这一字段,然后与该学生新选课程的上课信息进行比对。若这时没有冲突才能将《学生选课申请提交单》的选课信息写入《学生最新选课单》;若有冲突则拒绝写入。
(ⅲ)排处第一层冲突之后,系统应该检查该学生所有新选课程的"上课信息"进行比对,并按照选课权重选取权重最大的课程,其他与之相冲突的选课记录将被删去。
经过系统的三层排除冲突之后的《学生最新选课单》方能写入《学生选课表》。之后学生考试填写试卷,教师批阅提供分数信息,将分数信息与选课信息合并存入《学生选课表》。
(1)、查询教师的基本信息可以查询到教师的基本信息;
(2)、查询学生的基本信息可以查询到学生的基本信息;
(3)、查询课程的基本信息可以查询到课程的基本信息;
(4)、查询学生成绩的基本信息可以查询到学生选课的信息以及学生在此课程中的分数情况;
(ⅰ)对处理学生选课以及成绩信息的分解:
数据字典不单单是用于解释数据流图,还能在数据存储文件设计的时候提供援助。当现实世界中的主体有不断变化的属性时(属性的数量和属性的取值),尤其是变化得非常频繁的时候,就会引入数据字典辅助数据表设计。
在构建数据库管理系统之前,我先将两种数据字典摆出来:
(Ⅰ)将主体的属性编码(设置每一个属性\(ID\)号),并将这些\(ID\)号放入独立的数据库表中作为主键,其对应的值就是属性的取值;(这对应于数据库表规范化中的模式分解)
(Ⅱ)将结构(模式)相同的数据库表整合在一起,用统一的编码规范设置\(ID\)号并将其作为主键,对应的值为属性的名称以及属性的取值;(这对应于数据库表规范化中的模式合并)
我们来看这样一个需求:假设我们已经设计出了该教务管理系统的逻辑模型初稿,其中有一个模式为“学生(学号,姓名,系别名称)”。在这一模式中体现出了“学生”实体和“学生属于系别”联系,虽然一箭双雕提高了查询的效率,但是这里很明显存在一个问题:系别的名称如果更改,则学生表中的"系别名称"属性的取值都要做出相应的调整,这系别名称还好,如果是国籍呢,比如前苏联到俄罗斯,这就要涉及到上亿份数据。怎样解决这一问题呢?先生指出我们可以参考(Ⅰ)数据字典,新建一张《系别表》,其中存放"系别\(ID\)"和"系别名称",这样在学生表中直接引入外键"系别\(ID\)"即可,这是更改系别名称就只用在《系别表》中更改"系别名称"而"系别\(ID\)"不用改,保证了《学生表》的稳定性。上述文字转换为数据表的动态变换就是:
更改之前的数据库表《学生表》及实例:
更改之后的数据库表《学生表》及实例:
新建的《系别表》及实例:
根据上级的命令更改数据,将“地理信息系统”改为“地理信息科学”这样的话就会有:
显然《学生表》没有任何改动。
这样很\(nice\)但是有没有想过如果一个主体中的属性不是两三个,而是几十个甚至上百个,那么每一次查询都涉及到上百个夺标连接操作,这时的查询效率简直不敢恭维。于是先生又提出了第二种方案:(Ⅱ)数据字典,将属性数据表进行合并,但是并不是将所有的数据表进行合并,而是针对模式(结构)一样的一组表。这样就能将查询所涉及到的表大幅度的减少,从而提高了查询的效率。下面我们来看一个案例,解释(Ⅰ)的缺点和(Ⅱ)。
我们现在有一个需求:假设我们更新了模式,最后变为“学生(学号,姓名,证件类型,证件号,国籍,民族,系别名称,政治面貌)”,根据(Ⅰ)的分解,我们可以将模式继续升级为“学生(学号,姓名,证件类型\(ID\),国籍\(ID\),民族\(ID\),系别\(ID\),政治面貌\(ID\))”、“证件类型(证件类型\(ID\),证件类型,证件号)”、“国籍(国籍\(ID\),国籍)”、“民族(民族\(ID\),民族名称)”、“系别(系别\(ID\),系别名称)”、“政治面貌(政治面貌\(ID\),政治面貌名称)”,就这样一直被分解,如果只是5个左右的表那查询起来也不是很费劲,但是一个学生显然不可能只有这样几个属性,所以如果继续分解下去就会带来过于沉重的查询计算量。
既然我们的目的是为了提高查询效率,那个合并数据表显然是一个不错的选择,但是我们知道一个数据表的模式是定死了的,并且属性的取值是必须规定在同一个域,所以只有表结构相同、对应属性的取值范围相同的表才能够合并。我们将目光转移至分解之后的属性表,发现除了《证件类型》这张表之外都很相似,模式都是“属性的名称(ID,属性的值)”,那我们就可以合并了。回过头看《证件类型》这张表,不难发现"证件号"可以作为主码,从而代替"证件类型\(ID\)",这样《证件类型》表的模式为“证件类型(证件号,证件类型)”。这时我们整合这些属性表,得到一个新的表模式“属性(属性\(ID\)号,属性分类\(ID\)号,属性取值)”,构造一个实例:
《系统属性代码表》
《学生表》
《属性表》
这样在查询任意一个学生的时候最多也就是3张表的查询,一张差姓名、另外两张查找其他属性\(ID\)以及对应的内容,这样查询效率是不是就大大提升了呢?并且令人振奋的是,第一种类型的字典可以解决的问题第二种类型的字典同样可以解决,因为《系统属性代码表》中ID号就相当于一个代号了,只不过这个代号的域比第一种类型的字典要大!数据流图&&数据字典就到这了,不知道你们有没有弄懂?(反正我是明白了【狗头保命】)