程序員與笛卡爾積

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與笛卡爾積 轉自:

相關文章
相關標籤/搜索