“Theory is when you know everything but nothing works. Practice is when everything works but no one knows why. In our lab, theory and practice are combined: nothing works and no one knows why”
– 理论就是当你知道一切却没有一样东西好使,实践就是所有东西都好使却没人知道为什么。在我们的实验室中,理论和实践是相结合:没有一样东西好使,也没人知道为什么 :)
很多理论可能仅限于学术研究和考试内容 :| 这么多年的工作也训练出了自己的一套工作方法,理论再好还是得服务于实践,不然也只能束之高阁。但通过对理论的系统梳理,也了解了自己的工作方法在理论中处于怎样的位置,以后实践中遇到问题应该去哪里寻找答案。
注:为了熟悉各种名词的英文说法,所以很多概念都用英文表示。
逻辑是数学的基石,当然也是我们写程序、程序执行的基石。没有逻辑,数据库查询的执行无从谈起。因此,要学习数据库查询的内部是运转的,就要从逻辑开始学起。重点了解量词的使用和从循环角度思考,这对于后面学习查询理论时是很有用处的!
命题(Proposition)就是一个能够判断真假的声明式语句。例如,华盛顿是美国的首都,1+1=2等。用来表示命题的字符就叫做命题变量(Propositional variable)。已存在的命题可以通过与或非等逻辑运算符组成新的命题,也叫做复合命题(Compound proposition),这些逻辑运算符也叫做连接符(Connective)。
“if p, then q”这种两个命题组合起来叫做条件语句(Conditional statement),表示为: a→b 。要注意的是,当p为False时,不论q真假如何,整个条件语句都为True。举个例子,当你考100分时,那么你拿到A。当你没考100分时,不论你是否拿A,整个条件语句都是True,因为条件语句的判断条件没有发生。
编程语言中的if语句
逻辑中的条件语句与编程语言中的if语句是完全不同的。例如Java中的if-then语句,if中的确是一个可以判断真假的命题,但是then中是一个动作而不是一个命题或事实。这也是SICP中提到过的,编程语言与数学语言的区别,你不可能像推导数学公式一样让程序一步步自动推导出下一步要执行的语句。具体请参考:《程序员修炼之路-(1)基础(下):正确性证明 》。
在所有Case下都具有相同真值的复合命题就叫做逻辑等价,表示为: p≡q 。通过真值表(Truth table)我们可以得到一些简单命题的逻辑等价性,例如其中很重要的一组等价就是De Morgen定律:
¬(p∧q)≡¬p∨¬q
¬(p∨q)≡¬p∧¬q
当我们掌握了这些基本符合命题的等价性后,就可以抛弃真值表,而用它们去推导更加复杂的复合命题的真值了。因为对于更加复杂的复合命题来说,用真值表的方式实在太麻烦了。
一个复合命题只要在一种Case下是True,我们就是它是Satisfiable的。
前面讲了这么多命题的知识,其实命题并不能充分地表达出数学语言或自然语言中语句的含义。因为命题只是粗略地表示出一整句的真值,并与另一语句进行逻辑运算,获得组合后的真值。所以现在就引出更为强大的谓词逻辑(Predicate logic),正如其名,我们将一个语句分为主语(Subject)和谓语(Predicate)。例如,语句“变量x大于3”分为主语“变量x”和谓语“大于3”。可以用P(x)表示这个语句,P(x)就叫做命题函数(Propositional function)。一旦变量x分配了值,语句P(x)就成了一个命题,就可以计算其真值了。
除了给变量分配值的方式外,还可以用量化(Quantification)的方式将命题函数转换为命题。量化表示了谓词在何种范围内为真,例如所有、一些、没有等等,最常见的Universal和Existential量化表示为: ∀xP(x) 或 ∃xP(x) 。符号 ∀ 表示P(x)对于域中的所有x, ∃ 表示域中存在一个x使得P(x),它们就叫做量词(Quantifier)。要想成功把谓词逻辑通过量化转换为命题,就要满足:
例如, ∀x(x<0−>x2>0) 或简化写法 ∀x<0(x2>0) 就是一个标准的由Quantifier、Bound Variable、Propositional function以及Domain四部分构成的命题。
从循环与查找的角度思考Quantification的判真是很有帮助的。例如,假设变量x的domain里有n个值,那么对于 ∀xP(x) 的判真,就相当于是:循环domain中的所有n个值,看P(x)是否为真。遇到一个为假的情况,就立即终止。如果一直循环完成都是真,那么说明为真。怎么样,跟我们代码里写的循环很像吧?同理, ∃xP(x) 就是遇到一个为真,就立即终止。如果一直到最后都没有,就说明为假。
下面真正有挑战的来了,那是多个量词的嵌套。注意:除了最后一组 ∃∃ 外,量词的顺序都是至关重要的!同样的,我们可以从嵌套循环(Nested Loop)的角度来帮助我们理解:
其实SQL并非起源与我们熟知的关系代数(Relational Algebra),实际上关系代数更多地应用于查询的分析和执行。SQL真正之母是很少听说过的关系演算(Relational Calculus),它的查询形式是基于谓词逻辑(Predicate logic)来定义的。具体来说,有元组关系演算(TRC)和域关系演算(DRC)两种:
TRC被Codd引入作为关系模型的一部分,用来为关系模型提供一种声明式的数据库查询语言。TRC查询的形式为:{T | Condition(T)}。其中T叫做目标(Target),是遍历所有元组的变量。Condition是查询的条件,用具体元组替换T来判断真值。例如, {S|Staff(S)∧S.salary>10000} 。
TRC与SQL有什么对应关系吗?例如上面的例子,S可以看做是SQL中的SELECT部分,但TRC只能返回整个Tuple。Staff(S)可以看做是FROM,而S.salary > 10000可以看做是WHERE。执行方式为:用具体的Tuple去替换S(不能凭空用任意值去替换S吧?没有SQL易理解),看是否满足S在Staff中和S.salary大于10000两个条件。
来看一个复杂一些的TRC,这里就要用到前面逻辑中的概念了。下面S是Free变量,T被量词限定所以是Bound变量。在TRC中,Bound变量用在条件中,用来判断关于Tuple的真假,决定是否保留。而Free变量则用在Target中,用来指定要返回的Tuple。条件中只能有一个Free变量,否则就没法判断真假了。
转换很简单,等价的SQL就是:
上面这样一对比就能看出,本质上,SQL不过是TRC的一层语法糖:
所以说,TRC更加通用,但SQL更加简单。
DRC的Target部分是由域变量(Domain Variable)而非TRC中的整个Tuple组成,即Tuple某个属性的值域中对应的变量。DRC查询的形式为: {X1,X2,...,Xn|condition(X1,X2,...,Xn)} 。其中每个X都要么是域变量要么是常量。例如,TRC查询 {S|Staff(S)∧S.salary>10000} ,在DRC中可以写作 {S.id|Staff(S.id,S.salary)∧S.salary>10000}
因为以属性而非整个元组为Target,所以查询条件中经常出现非Target属性的大量 ∃ 也是DRC的标志,但DRC能自动匹配属性值和同名属性关联。所以,某些查询用TRC表达简单,而有些用DRC能更简单些。
TRC和DRC可以相互转换,但转换成关系代数却不那么容易,有些查询甚至无法转换。