程序员与笛卡尔积

SQL与笛卡尔积

首先,先简单解释一下笛卡尔积。html

如今,咱们有两个集合A和B。java

A = {0,1}     B = {2,3,4}
复制代码

集合 A×B 和 B×A的结果集就能够分别表示为如下这种形式:c++

A×B = {(0,2),(1,2),(0,3),(1,3),(0,4),(1,4)};

B×A = {(2,0),(2,1),(3,0),(3,1),(4,0),(4,1)};
复制代码

以上A×B和B×A的结果就能够叫作两个集合相乘的笛卡尔积es6

从以上的数据分析咱们能够得出如下两点结论:算法

  1. 两个集合相乘,不知足交换率,既 A×B ≠ B×A;sql

  2. A集合和B集合相乘,包含了集合A中元素和集合B中元素按顺序结合的全部的可能性。既两个集合相乘获得的新集合的元素个数是 A集合的元素个数 × B集合的元素个数;数据库

  3. 其实和高中数学里的排列很相似,不过排列里含有(2,0)、(0,2),而笛卡尔积只有其中一个:AxB则是(0,2),BxA则是(2,0)。json

数据库表链接数据行匹配时所遵循的算法就是以上提到的笛卡尔积,表与表之间的链接能够当作是在作乘法运算。vim

好比如今数据库中有两张表,student表和 student_subject表,以下所示: 数组

咱们执行如下的sql语句,只是纯粹的进行表链接。

SELECT * from student JOIN student_subject;
SELECT * from student_subject JOIN student;
复制代码

看一下执行结果:

从执行结果上来看,结果符合咱们以上提出的两点结论;

以第一条sql语句为例咱们来看一下他的执行流程,

  1. from语句把student表 和 student_subject表从数据库文件加载到内存中。

  2. join语句至关于对两张表作了乘法运算,把student表中的每一行记录按照顺序和student_subject表中记录依次匹配。

  3. 匹配完成后,咱们获得了一张有 (student中记录数 × student_subject表中记录数)条的临时表。 在内存中造成的临时表如表1.0所示。咱们又把内存中表1.0所示的表称为笛卡尔积表

针对以上的理论,咱们提出一个问题,难道表链接的时候都要先造成一张笛卡尔积表吗?

若是两张表的数据量都比较大的话,那样就会占用很大的内存空间这显然是不合理的。因此,咱们在进行表链接查询的时候通常都会使用JOIN xxx ON xxx的语法,ON语句的执行是在JOIN语句以前的,也就是说两张表数据行之间进行匹配的时候,会先判断数据行是否符合ON语句后面的条件再决定是否JOIN

  所以,有一个显而易见的SQL优化的方案是,当两张表的数据量比较大,又须要链接查询时,应该使用 FROM table1 JOIN table2 ON xxx的语法,避免使用 FROM table1,table2 WHERE xxx 的语法,由于后者会在内存中先生成一张数据量比较大的笛卡尔积表,增长了内存的开销。

根据上一篇博客( www.cnblogs.com/cdf-opensou… ),及本篇博客的分析,咱们能够总结出一条查询sql语句的执行流程。

1. From
 2. ON
 3. JOIN
 4. WHERE
 5. GROUP BY
 6. SELECT
 7. HAVING
 8. ORDER BY
 9. LIMIT
复制代码

最后,针对两张数据库表链接的底层实现,我用java代码模拟了一下,感兴趣的能够看一下,可以帮助咱们理解:

package com.opensource.util;

import java.util.Arrays;

public class DecareProduct {
    
    public static void main(String[] args) {
        
        //使用二维数组,模拟student表
        String[][] student ={
                {"0","jsonp"},
                {"1","alice"}
        };
        
        //使用二维数组,模拟student_subject表
        String[][] student_subject2 ={
                {"0","0","语文"},
                {"1","0","数学"}
        };

        //模拟 SELECT * from student JOIN student_subject;
        String[][] resultTowArray1 = getTwoDimensionArray(student,student_subject2);
        //模拟 SELECT * from student_subject JOIN student;
        String[][] resultTowArray2 = getTwoDimensionArray(student_subject2,student);
        
        int length1 = resultTowArray1.length;
        for (int i = 0; i <length1 ; i++) {
            System.out.println(Arrays.toString(resultTowArray1[i]));
        }
        System.err.println("-----------------------------------------------");
        int length2 = resultTowArray2.length;
        for (int i = 0; i <length2 ; i++) {
            System.out.println(Arrays.toString(resultTowArray2[i]));
        }
    }
    
    /** * 模拟两张表链接的操做 * @param towArray1 * @param towArray2 * @return */
    public static String[][] getTwoDimensionArray(String[][] towArray1,String[][] towArray2){
        
        //获取二维数组的高(既该二维数组中有几个一维数组,用来指代数据库表中的记录数)
        int high1 = towArray1.length;
        int high2 = towArray2.length;
        
        //获取二维数组的宽度(既二位数组中,一维数组的长度,用来指代数据库表中的列)
        int wide1 = towArray1[0].length;
        int wide2 = towArray2[0].length;
        
        //计算出两个二维数组进行笛卡尔乘积运算后得到的结果集数组的高度和宽度,既笛卡尔积表的行数和列数
        int resultHigh = high1 * high2;
        int resultWide = wide1 + wide2;
        
        //初始化结果集数组,既笛卡尔积表
        String[][] resultArray = new String[resultHigh][resultWide];
        
        //迭代变量
        int index = 0;
        
        //先对第二二维数组遍历
        for (int i = 0; i < high2; i++) {
            
            //拿出towArray2这个二维数组的元素
            String[] tempArray = towArray2[i];
            
            //循环嵌套,对第towArray1这个二维数组遍历
            for (int j = 0; j < high1; j++) {
                
                //初始化一个长度为'resultWide'的数组,做为结果集数组的元素,既笛卡尔积表中的一行
                String[] tempExtened = new String[resultWide];
                
                //拿出towArray1这个二维数组的元素
                String[] tempArray1 = towArray1[j];
                
                //把tempArray1和tempArray两个数组的元素拷贝到结果集数组的元素中去。(这里用到了数组扩容)
                System.arraycopy(tempArray1, 0, tempExtened, 0, tempArray1.length);
                System.arraycopy(tempArray, 0, tempExtened, tempArray1.length, tempArray.length);
                
                //把tempExtened放入结果集数组中
                resultArray[index] = tempExtened;
                
                //迭代加一
                index++;
            }
        }
         
        return resultArray;   
    }
}
复制代码

执行结果:

几个join的笛卡尔积:

  • 两表直接链接,笛卡尔积的结果数量是两表的数据量相乘
  • 带where/on条件id相等的笛卡尔积和inner join结果相同,可是inner join效率快一点
  • left join:TEST_A表的ID为空时拼接TEST_B表的内容为空,
  • right join则相反
  • full join:等于left join和right join的并集

所以若是程序的确须要多表联合查询,尽可能两两链接,并经过where或on或inner join缩小结果集,再将结果集对其余表继续链接……

JavaIO的装饰模式与笛卡尔积

在学习 java.io 包的时候,InputStream 那一群类很让人反感,子类繁多就不用说,使用起来很是奇怪,由于它使用了装饰模式……

假设咱们想以缓存的方式从文件中读取字节流,通常常见的操做老是:先建立一个FileInputStream,而后把这个FileInputStream放入BufferedInputStream构造函数中去建立BufferedInputStream。完成这些工做后才能开始读取文件:

try (FileInputStream fis = new FileInputStream("c:/a.txt");
	     BufferedInputStream bis = new BufferedInputStream(fis)) {

		byte[] buffer = new byte[1024];
		int len;
		StringBuilder result = new StringBuilder();
		while ((len = bis.read(buffer)) != -1) {
			result.append(new String(buffer, 0, len));
		}
            
	} catch (IOException e) {
		//handle
	}
复制代码

为何 sun 不能直接建立以缓存方式从文件中读取数据的输入流类呢?

或者说为何InputStream选择装饰者模式,而非直接继承的方法来扩展,也就是装饰者模式VS继承。

为了回答这个问题,就以InputStream与FilterInputStream二者组合,若是我用了继承,看看咱们的类图是什么样的:

似曾相识,咱们再看一下:

InputStream:[ FileInputStreamByteArrayInput StreamSequenceInputStreamObjectInputreamPipedInputStreamStringBufferInputStream……还包括其余二方、三方继承InputStream自实现的InputStream子类,目前至少有两百多个各类实现]

FilterInputStream(它也继承自InputStream):[BufferedInputStreamDataInputStreamPushbackInputStream……]

二者假设进行任意组合,便可构成一个所谓的输出流类,那么这种输出流类的数量将是一个笛卡儿积,即爆炸增加,同时InputStream内部还能够进行互相组合。

而若是采用装饰模式,具体大家怎么搭配我不关心,只须要套个装饰,即变成了一个新的功能的输出流类

SQL与笛卡尔积 转自:

相关文章
相关标签/搜索