精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

關系代數、SQL語句和Go語言示例

數據庫
近些年,數據庫領域發展日新月異,除傳統的關系型數據庫外,還出現了許多新型的數據庫。

近些年,數據庫領域發展日新月異,除傳統的關系型數據庫外,還出現了許多新型的數據庫,比如:以HBase、Cassandra、MongoDB為代表的NoSQL數據庫,以InfluxDB、TDEngine為代表的時序數據庫,以Neo4J、Dgraph為代表的圖數據庫,以Redis、Memcached等為代表的內存數據庫,以Milvus為代表的向量數據庫,以CockroachDB、TiDB為代表的HTAP融合數據庫以及云原生數據庫等。各類型數據庫都有自己的優勢,開發者可以根據應用場景選擇最合適的數據庫。

不過,關系型數據庫依舊是當今最流行的數據庫管理系統,廣泛應用于企業應用,也是大多數數應用開發人員日常接觸最多的一種數據庫類型。關系型數據庫通過關系模型和關系代數的理論基礎,實現了對關系數據的高效組織和操作。但許多開發人員在使用SQL進行數據庫開發時,往往感到關系代數晦澀難懂,對SQL語句的語義理解不透徹,這給數據庫應用開發帶來了困難。

在這篇文章中,我們就來研究一下關系模型和關系代數,探究其與SQL語句的對應關系,并用Go語言代碼示例實現相關查詢,期望能幫助讀者增進對關系數據庫的理解,減輕數據庫開發痛點,提高數據庫應用能力。

1. 關系模型(Relational Model)

20世紀70年代,IBM研究員E.F. Codd在“A Relational Model of Data for Large Shared Data Banks”這篇論文中提出了關系模型的概念。隨后,E.F.Codd又陸續發表了多篇文章,用數學理論奠定了關系數據庫的基礎,為關系數據庫建立了一個數據模型 —— 關系數據模型。

關系模型基于謂詞邏輯和集合論,有嚴格的數學基礎,提供了高級別的數據抽象層次,并不規定數據存取的具體過程,而是交由DBMS(數據庫管理系統)自己實現。

關系模型之所以成為DBMS領域的主流模型,正是由于其非常簡單(相較于更早的網絡模型(network model)和層次模型(hierarchical model)),下面是關系模型中定義的一些概念:

  • 關系(Relation)

E.F.Codd的論文對關系(Relation)的定義是這樣的:“這里的關系是指公認的數學意義上的關系。給定集合S1, S2, ... ,Sn(不一定互不相關),如果 R是由n元組(n-tuples)組成的集合,其中每個元組的第一個元素來自S1,第二個元素來自S2,以此類推,那么R就是這n個集合(S1~Sn)上的一個關系”。

不用你說,我也知道這段文字太過抽象!下面我盡力用一個圖來呈現一下Relation的含義:

我們看到,關系(Relation)是一個集合,實質上是一個“二維表格結構”,把上圖中不屬于R中的元組去掉,看起來可能更清晰一些:

這個結構中的每一行就是1個n元組(n-tuples),列則是S1到Sn,一共n個列。n元組中的數據依次分別來自S1、S2、...Sn。

  • 元組(Tuple)

關系(Relation)這個“二維表格結構”中的每一個n元組,即每一行,被稱作元組(Tuple)。

  • 屬性(Attribute)

關系(Relation)這個“二維表格結構”中的每一列(Sn)被稱作一個屬性(Attribute)。

  • 域(Domain)

屬性可能取值的范圍被稱為該屬性的域,以圖中屬性S3為例,S3-e1、S3-e2一直到S3-ek都在該屬性的域中,顯然{S3-e1, S3-e2, ..., S3-ek}這個集合是屬性S3的域的一個子集。有個特殊的值null是所有域的一個成員,它一般表示值為"unknown"。

論文在定義關系模型時,還定義了一些模型的額外特征,比如:

  • 元組的順序是不重要的;
  • 所有的元組(行)是不同的;
  • ... ...

有了關系模型的定義,接下來就可以在模型基礎上定義以關系操作對象的運算了,這種運算的集合就構成了關系代數

2. 關系代數(Relational Algebra)

關系代數由一系列操作組成,這些操作將一個或兩個關系作為輸入,并產生一個新的關系作為結果。概括來說就是關系代數的運算通過輸入有限數量的關系進行運算,運算結果仍為關系。

關系代數定義了一些基本關系運算和擴展關系運算,其中基本關系運算包括:

  • 選擇(Selection)
  • 投影(Projection)
  • 笛卡兒積(Cartesian Product)
  • 連接(Join)
  • 除(Division)
  • 關系并(Union)
  • 關系差(Difference)

擴展運算包括:

  • 關系交(Intersection)
  • 重命名(Rename)
  • ... ...

注:關于關系代數的基本關系運算與擴展關系運算的定義在不同書籍里或資料里有所不同。比如在《數據庫查詢優化器的藝術》一書中,作者認為:關系代數(Relational Algebra)是在集合代數基礎上發展起來的,其數據的操作可分為傳統的集合運算和專門的關系運算兩類。傳統的集合運算包括并(Union)、差(Difference)、交(Intersection)和笛卡兒積(Cartesion Product),專門的關系運算包括選擇(Select)、投影(Project)、連接(Join)和除(Division)。關系代數中五個基本的操作并(Union)、差(Difference)、笛卡兒積(Cartesion Product)、選擇(Select)和投影(Project)組成了關系代數完備的操作集。

關系代數中的一些操作(如選擇、投影和重命名操作)被稱為一元操作(unary operation),因為它們只對一個關系進行操作。其他操作,如關系并、笛卡爾積和關系差,則是對一對關系進行操作,因此稱為二元操作(binary operation):

到這里,我們知道了關系模型的概念定義以及基于關系的代數運算都有哪些。那么關系模型、代數運算與我們日常的關系數據庫以及我們使用的SQL語句的對應關系是什么呢?接下來我們就逐一說明一下。

3. 關系模型與關系數據庫實現的對應關系

講到這里,其實大家心里或多或少都有個數了,關系模型與關系數據庫實現中概念的對應關系十分明顯:

  • 關系型數據庫中的表(table)對應關系模型中的關系(relation);
  • 關系型數據庫中的表的記錄行(row)對應關系模型中的元組(triple);
  • 關系型數據庫中的表的列(column)對應關系模型中的屬性(attribute);
  • 關系型數據庫中的表的列數據類型(column type)對應關系模型中的屬性的域(domain)。

當然關系型數據庫與關系模型還有一些對應關系不是本文重點,比如:

  • 關系模型中的關系完整性約束(如實體完整性、參照完整性等)對應于關系數據庫中的約束(如主鍵約束、外鍵約束等)。
  • 關系模型中的范式理論(如第一范式、第二范式等)對應于關系數據庫中的數據規范化過程。

我們下面要關注的一個最重要的對應就是關系模型中的關系代數運算對應于關系數據庫中的查詢操作,我們可以使用SQL語句來實現關系模型中的運算,這也是下面我們要重點說明的內容,通過了解SQL語句背后實現的關系代數運算的本質,將可以幫助我們更好地理解關系模型,對后續數據庫設計以及數據操作的高效性都大有裨益。

4. 關系代數與SQL的對應關系

終于來到最重要的內容了,其實就是通過SQL如何實現關系代數的操作,這也是作為應用開發人員最最關心的內容。

4.1 預先定義的關系

為了便于后續的說明,這里我們預先定義一些關系(表),它們將用在后續說明各個關系運算符的示例中,這些表見下圖:

這里包含一個學生表(Students)、一個課程清單表(Courses)以及兩年年度的選課表:CourseSelection2022和CourseSelection2023(注:這里不討論表設計的合理性)。

文中使用sqlite做為數據庫管理系統(DBMS)的代表,主要是為了簡單,SQL標準的兼容性也不錯。下面的Go代碼用于創建上圖中的表并插入樣例數據:

// relational-algebra-examples/create_database/main.go

package main

import (
 "database/sql"
 "fmt"

 _ "modernc.org/sqlite"
)

func createTable(db *sql.DB, sqlStmt string) error {
 stmt, err := db.Prepare(sqlStmt)
 if err != nil {
  fmt.Println("prepare statement error:", err)
  return err
 }

 _, err = stmt.Exec()
 if err != nil {
  fmt.Println("exec prepared statement error:", err)
  return err
 }

 return nil
}

func createTables(db *sql.DB) error {
 // 創建Students表
 err := createTable(db, `CREATE TABLE IF NOT EXISTS Students (
    Sno INTEGER PRIMARY KEY,
    Sname TEXT,
    Gender TEXT, 
    Age INTEGER
  )`)
 if err != nil {
  fmt.Println("create table Students error:", err)
  return err
 }

 // 創建Courses表
 err = createTable(db, `CREATE TABLE IF NOT EXISTS Courses (
    Cno INTEGER PRIMARY KEY,
    Cname TEXT,
    Credit INTEGER
  )`)
 if err != nil {
  fmt.Println("create table Courses error:", err)
  return err
 }

 // 2022選課表
 err = createTable(db, `CREATE TABLE CourseSelection2022 (
  Sno INTEGER,
  Cno INTEGER,
  Score INTEGER,

  PRIMARY KEY (Sno, Cno),
  FOREIGN KEY (Sno) REFERENCES Students(Sno),
  FOREIGN KEY (Cno) REFERENCES Courses(Cno)
)`)
 if err != nil {
  fmt.Println("create table CourseSelection2022 error:", err)
  return err
 }

 // 2023選課表
 err = createTable(db, `CREATE TABLE CourseSelection2023 (
  Sno INTEGER,
  Cno INTEGER, 
  Score INTEGER,
  
  PRIMARY KEY (Sno, Cno),
  FOREIGN KEY (Sno) REFERENCES Students(Sno),
  FOREIGN KEY (Cno) REFERENCES Courses(Cno)
)`)

 if err != nil {
  fmt.Println("create table CourseSelection2023 error:", err)
  return err
 }
 return nil
}

func checkErr(err error) {
 if err != nil {
  panic(err)
 }
}

func insertData(db *sql.DB) {
 // 向Students表插入數據
 stmt, err := db.Prepare("INSERT INTO Students VALUES (?, ?, ?, ?)")
 checkErr(err)

 _, err = stmt.Exec(1001, "張三", "M", 20)
 checkErr(err)
 _, err = stmt.Exec(1002, "李四", "F", 18)
 checkErr(err)
 _, err = stmt.Exec(1003, "王五", "M", 19)
 checkErr(err)

 // 向Courses表插入數據
 stmt, err = db.Prepare("INSERT INTO Courses VALUES (?, ?, ?)")
 checkErr(err)

 _, err = stmt.Exec(1, "數據庫", 4)
 checkErr(err)
 _, err = stmt.Exec(2, "數學", 2)
 checkErr(err)
 _, err = stmt.Exec(3, "英語", 3)
 checkErr(err)

 // 插入2022選課數據
 stmt, _ = db.Prepare("INSERT INTO CourseSelection2022 VALUES (?, ?, ?)")
 _, err = stmt.Exec(1001, 1, 85)
 checkErr(err)
 _, err = stmt.Exec(1001, 2, 80)
 checkErr(err)
 _, err = stmt.Exec(1002, 1, 83)
 checkErr(err)
 _, err = stmt.Exec(1003, 1, 76)
 checkErr(err)
 // ...

 // 插入2023選課數據
 stmt, _ = db.Prepare("INSERT INTO CourseSelection2023 VALUES (?, ?, ?)")
 stmt.Exec(1001, 3, 75)
 checkErr(err)
 stmt.Exec(1002, 2, 81)
 checkErr(err)
 stmt.Exec(1003, 3, 86)
 checkErr(err)
}

func main() {
 db, err := sql.Open("sqlite", "../test.db")
 defer db.Close()
 if err != nil {
  fmt.Println("open test.db error:", err)
  return
 }

 err = createTables(db)
 if err != nil {
  fmt.Println("create table error:", err)
  return
 }

 insertData(db)
}

這里我們使用了cznic大神[3]實現并開源的modernc.org/sqlite,這是一個純Go的sqlite3數據庫driver。Go社區另一個廣泛使用的sqlite3的driver庫為go-sqlite3,只不過go-sqlite3是使用cgo對sqlite3 C庫的封裝。

執行上面go代碼,便可以建立一個名為test.db的sqlite數據庫,我們通過sqlite官方的命令行工具(cli)也可以與該數據庫文件交互(這里我們使用的是容器版cli),比如:

$docker pull  nouchka/sqlite3

// cd到test.db文件路徑下

$docker run -v {test.db文件所在目錄的絕對路徑}:/root/db -it nouchka/sqlite3
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open ./test.db
sqlite> .databases
main: /root/db/test.db r/w
sqlite> .tables
CourseSelection2022  Courses            
CourseSelection2023  Students
sqlite>

接下來,我們就先從關系代數運算中最容易理解的一元運算符開始說起。

4.2. 選擇(Selection)

“選擇”是一元關系運算,它的運算符為σ,語義如下:

R' = σ[p](R "p") = {t | t∈R ∩ p(t) = true } // 這里用[p]表示數學符號的下標

其中R為關系,t為元組,p是謂詞(predicate)表達式的組合,可以由一個或多個謂詞表達式構成。

這個語義相對好理解一些:它對R的操作結果依然是關系R',即一個新元組集合,這個元組集合中的元組來自R,但必須滿足p(t) = true的條件。說直白一些,就是選擇滿足給定條件的元組。下面是一個“選擇”操作的示意圖:

我們可以用下面最常見的SQL語句實現對單一關系(表)的選擇運算:

SELECT * FROM R WHERE p(t) = true;

對應Go示例的代碼片段如下:

// relational-algebra-examples/query/main.go

func doSelection(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 where score >= 80") // p(t)為score >= 80
    var selections []CourseSelection
    for rows.Next() {
        var s CourseSelection
        rows.Scan(&s.Sno, &s.Cno, &s.Score)
        selections = append(selections, s)
    }
    fmt.Println(selections)
}

輸出結果為:

[{1001 1 85} {1001 2 80} {1002 1 83}]

4.3 投影(Projection)

“投影”也是一元關系運算,它的運算符為∏,語義如下:

R' = ∏[A1,A2,...,An](R "A1,A2,...,An") = {t[A1,A2,...,An]| t∈R } // 這里A1,A2,...,An表示從R中取出的列名

顯然和“選擇”通過謂詞表達式選元組不同,“投影”選擇一個關系中的指定列(A1,A2,...,An),即選擇需要的屬性。下面是其運算過程的示意圖:

“投影”對應的SQL語句也是我們最熟悉的語句:

SELECT A1, A2, ..., An FROM R;

對應Go示例的代碼片段如下:

// relational-algebra-examples/query/main.go

func doProjection(db *sql.DB) {
    rows, _ := db.Query("SELECT Sno, Sname FROM Students") // A1 = Sno, A2 = Sname
    var students []Student
    for rows.Next() {
        var s Student
        rows.Scan(&s.Sno, &s.Sname)
        students = append(students, s)
    }
    fmt.Println(students)
}

輸出結果為:

[{1001 張三  0} {1002 李四  0} {1003 王五  0}]

不過要注意的是:取消某些關系列后可能出現重復行,違反了關系的定義(關系是一個元組的集合),因此必須檢查并去除結果關系中重復的元組。

4.4 運算符的組合(Composition)

關系運算的輸入是關系,結果也是一個關系,因此我們可以將關系運算符組合成一個更復雜的關系運算符表達式來實現更復雜的運算。比如將上面的兩個一元關系運算符組合在一起“先選元組,再選屬性”:

R' = ∏[A1,A2,...,An](σ[p](R "A1,A2,...,An"))

其運算過程如下圖所示:

上述運算符組合對應的SQL語句如下:

SELECT A1, A2, ..., An FROM R where p(t) = true;

對應Go示例的代碼片段如下:

// relational-algebra-examples/query/main.go

func doCompositionOperation(db *sql.DB) {
    rows, _ := db.Query("SELECT Sno, Sname FROM Students where age >= 20")
    var students []Student
    for rows.Next() {
        var s Student
        rows.Scan(&s.Sno, &s.Sname)
        students = append(students, s)
    }
    fmt.Println(students)
}

輸出結果為:

[{1001 張三  0}]

無論是選擇運算還是投影運算,亦或是組合之后的運算,理解起來都相對容易,因為只涉及一個“關系”。接下來我們就看一下涉及兩個關系的二元運算符,我們先來看看集合運算

4.5 關系交(Intersection)

如果沒有記錯,我們是在高中學習的集合代數。那時定義兩個集合的交集運算是這樣的:

對于集合A和B,其交運算(Intersction)為:

A ∩ B = { x | x ∈ A且 x ∈ B}

用一個一維空間的數的集合的例子來說,就是當A = {1, 2, 3, 4, 5},B = { 3, 5, 6, 9}時,A ∩ B = {3, 5}。我們通常用維恩圖來示意集合運算:

在關系模型中,元組是一維集合,關系是元組的集合,即是一個二維集合,那么基于關系的交運算就要有一個前提:那就是參與運算的兩個關系的屬性必須是兼容的。

兩個關系的屬性兼容需滿足以下條件:

  • 屬性數量相同

兩個關系中的屬性數量必須相同。

  • 屬性類型相同或可轉換

兩個關系中對應位置的屬性類型必須相同或可以通過類型轉換進行兼容。例如,一個關系中的屬性類型是整數,而另一個關系中的屬性類型是浮點數,這種情況下屬性類型是兼容的,因為整數可以隱式轉換為浮點數。

  • 屬性名稱可以不同

兩個關系中對應位置的屬性名稱可以不同,只要它們的屬性類型兼容即可。屬性名稱的不同不會影響屬性兼容性。

在關系模型中,兩個關系的屬性兼容性是判斷兩個關系是否可以進行某些操作(包括集合操作)的重要條件之一。

回到集合運算,如果兩個關系的屬性不兼容,則這兩個關系無法進行集合運算,比如Students表和Courses表的屬性個數不同,如果對它們進行關系交運算,會導致報錯:

SELECT * FROM Students INTERSECT SELECT * FROM Courses; 
Parse error: SELECTs to the left and right of INTERSECT do not have the same number of result columns

介紹完集合運算的前提后,我們再來看關系交運算,其語義入下:

R' = R1 ∩ R2

即兩個關系R1和R2在屬性兼容的前提下進行關系交運算的結果為返回兩個關系中相同的元組。

關系交運算對應的SQL語句如下:

SELECT * FROM R1 INTERSECT SELECT * FROM R2;

對應Go示例的代碼片段如下:

// relational-algebra-examples/query/main.go

func doIntersection(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 INTERSECT SELECT * FROM CourseSelection2023")
    var selections []CourseSelection
    for rows.Next() {
        var s CourseSelection
        rows.Scan(&s.Sno, &s.Cno, &s.Score)
        selections = append(selections, s)
    }
    fmt.Println(selections)
}

由于CourseSelection2022和CourseSelection2023這兩個關系沒有相同元組,所以上述Go程序輸出的結果為空。

4.6 關系并(Union)

和關系交一樣,兩個關系進行關系并運算的前提也是屬性兼容。關系并運算的語義如下:

R' = R1 ∪ R2

即兩個關系R1和R2在屬性兼容的前提下進行關系并運算的結果為返回兩個關系中的所有元組,但要去除重復元組。

關系并對應的SQL語句如下:

SELECT * FROM R1 UNION SELECT * FROM R2;

對應Go示例的代碼片段如下:

// relational-algebra-examples/query/main.go

func doUnion(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 UNION SELECT * FROM CourseSelection2023")
    var selections []CourseSelection
    for rows.Next() {
        var s CourseSelection
        rows.Scan(&s.Sno, &s.Cno, &s.Score)
        selections = append(selections, s)
    }
    fmt.Println(selections)
}

CourseSelection2022和CourseSelection2023這兩個關系沒有重復元組,所有關系并運算后得到的結果關系中包含了這兩個關系的全部元組,上述程序的輸出結果為:

[{1001 1 85} {1001 2 80} {1001 3 75} {1002 1 83} {1002 2 81} {1003 1 76} {1003 3 86}]

4.7 關系差(Difference)

在集合代數中,對于集合A和B,其差運算為:

A - B = { x | x ∈ A且 x ? B}

即從A集合中排除掉B集合中的元素。

在關系模型中,關系差運算即是從一個關系中排除另一個關系中的元組,其語義如下:

R' = R1-R2={t|t∈R1 ∩ t?R2} // t為關系中的元組

在SQL中,我們可以用NOT IN實現:

SELECT * FROM R1 WHERE A1 NOT IN (SELECT A1 FROM R2 WHERE 條件)

下面是對應的Go語言代碼片段:

// relational-algebra-examples/query/main.go

func doDifference(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 WHERE Cno NOT IN (SELECT Cno FROM CourseSelection2023)")
    var selections []CourseSelection
    for rows.Next() {
        var s CourseSelection
        rows.Scan(&s.Sno, &s.Cno, &s.Score)
        selections = append(selections, s)
    }
    fmt.Println(selections)
}

這段示例的含義是選出CourseSelection2022的元組,但去掉Cno值在CourseSelection2023出現過的元組。下面是運行結果:

[{1001 1 85} {1002 1 83} {1003 1 76}]

注意:關系差運算的前提也是兩個關系的屬性兼容。

最后看看略復雜的二元運算符:笛卡爾積和連接。

4.8 笛卡爾積(Cartesian-product)

在關系代數中,關系積,即笛卡爾積(Cartesian Product)這種運算(也被稱為關系叉乘)用于取兩個關系的所有可能的組合。它的數學語義可以描述為:給定關系R1和R2,它們的笛卡爾積結果是一個新的關系,其中的元組由R1中的每個元組與R2中的每個元組的組合構成。

在SQL中,笛卡爾積可以通過使用CROSS JOIN關鍵字來實現:

SELECT * FROM R1 CROSS JOIN R2;

也可以通過下面SQL語句來實現:

SELECT R1.*, R1.* FROM R1, R2;

對應的Go代碼片段如下:

// relational-algebra-examples/query/main.go

// StudentCourse結果
type StudentCourse struct {
    Sno    int
    Sname  string
    Gender string
    Age    int
    Cno    int
    Cname  string
    Credit int
}

func doCartesianProduct(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM Students CROSS JOIN Courses")
 // rows, _ := db.Query("SELECT Students.*, Courses.* FROM Students, Courses")
    var selections []StudentCourse
    for rows.Next() {
        var s StudentCourse
        rows.Scan(&s.Sno, &s.Sname, &s.Gender, &s.Age, &s.Cno, &s.Cname, &s.Credit)
        selections = append(selections, s)
    }
    fmt.Println(len(selections))
    fmt.Println(selections)
}

示例的運行結果如下:

9
[{1001 張三 M 20 1 數據庫 4} {1001 張三 M 20 2 數學 2} {1001 張三 M 20 3 英語 3} {1002 李四 F 18 1 數據庫 4} {1002 李四 F 18 2 數學 2} {1002 李四 F 18 3 英語 3} {1003 王五 M 19 1 數據庫 4} {1003 王五 M 19 2 數學 2} {1003 王五 M 19 3 英語 3}]

我們看到對Students和Courses兩個關系(表)進行笛卡爾積運算后,結果包含了Students中的每個元組與Courses中的每個元組進行組合的結果(3x3=9個)。

需要注意的是,由于笛卡爾積可能導致非常大的結果集,因此在實際使用中應謹慎使用,并且通常需要與其他運算符和條件結合使用,以限制結果的大小和提高查詢效率。通常我們會用連接來達到這些目的。

4.9 連接(Join)

連接(Join)運算(?)是從兩個關系的笛卡兒積中選取屬性間滿足一定條件的元組形成一個新的關系,即將笛卡爾積和選擇(selection)運算合并達到一個操作中。從這個角度來看,笛卡爾積可以視為一種無條件的連接。

連接代數運算符是關系代數中很有用的關系代數運算符,也是日常經常使用的運算符,它有很多種不同的子類別,下面我們分別看看各種子類型的語義、SQL語句以及對應的Go代碼示例。

4.9.1 等值連接(Equijoin)

等值連接是通過比較兩個關系(表)之間的屬性值是否相等來進行連接的操作。連接條件使用等號(=)來比較屬性值的相等性。

我們直接看Go示例:

// relational-algebra-examples/query/main.go

func dumpOperationResult(operation string, rows *sql.Rows) {
    cols, _ := rows.Columns()

    w := tabwriter.NewWriter(os.Stdout, 0, 2, 1, ' ', 0)
    defer w.Flush()
    w.Write([]byte(strings.Join(cols, "\t")))
    w.Write([]byte("\n"))

    row := make([][]byte, len(cols))
    rowPtr := make([]any, len(cols))
    for i := range row {
        rowPtr[i] = &row[i]
    }

    fmt.Printf("\n%s operation:\n", operation)
    for rows.Next() {
        rows.Scan(rowPtr...)
        w.Write(bytes.Join(row, []byte("\t")))
        w.Write([]byte("\n"))
    }
}

func doEquijoin(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 JOIN Students ON CourseSelection2022.Sno = Students.Sno")
    dumpOperationResult("Equijoin", rows)
}

這個示例使用等值連接將CourseSelection2022表和Students表連接起來,連接條件是CourseSelection2022.Sno = Students.Sno,即學生編號相等,返回的結果將包含CourseSelection2022和Students兩個表中滿足連接條件的元組。

我們看看程序運行的輸出結果:

Equijoin operation:
Sno  Cno Score Sno  Sname Gender Age
1001 1   85    1001 張三    M      20
1001 2   80    1001 張三    M      20
1002 1   83    1002 李四    F      18
1003 1   76    1003 王五    M      19

在這個結果中,我們看到一個“奇怪”的情況,那就是出現了兩個Sno屬性。在等值連接中,如果連接的兩個表中存在相同名稱的屬性(例如這里兩個表中都有名為"Sno"的屬性),那么在連接結果中會出現兩個相同名稱的屬性。

這是因為等值連接會將兩個表中具有相同連接條件的屬性進行匹配,并將匹配成功的元組進行組合。由于兩個表中都有名為"Sno"的屬性,因此連接結果中會保留這兩個屬性,以顯示連接操作前后的對應關系。

為了區分來自不同表的相同屬性名,通常在連接結果中會使用表別名或表名作為前綴,以區分它們的來源。這樣可以確保結果中的屬性名稱是唯一的,避免歧義。 例如,如果在等值連接中連接了名為"CourseSelection2022"的表和名為"Students"的表,并且兩個表中都有名為"Sno"的屬性,那么連接結果中可能會出現類似于"CourseSelection2022.Sno"和"Students.Sno"的屬性名稱,以明確它們的來源。

需要注意的是,數據庫管理系統的具體實現和查詢工具的設置可能會影響連接結果中屬性的顯示方式,但通常會采用類似的方式來區分相同屬性名的來源。

4.9.2 自然連接(Natural Join)

自然連接是基于兩個表中具有相同屬性名的屬性進行連接的操作,重點在于它會自動匹配具有相同屬性名的屬性,并根據這些屬性的相等性進行連接,而無需手工指定。

我們來看自然連接的Go示例:

// relational-algebra-examples/query/main.go

func doNaturaljoin(db *sql.DB) {
    rows, _ := db.Query("SELECT * FROM CourseSelection2022 NATURAL JOIN Students")
    dumpOperationResult("Naturaljoin", rows)
}

這個示例使用自然連接將CourseSelection2022表和Students表連接起來,自然連接會自動基于兩個表中所有具有相同屬性名的屬性進行連接,返回的結果將包含CourseSelection2022和Students兩個表中所有滿足連接條件的元組,并自動消除重復屬性,這是與等值連接的一個明顯的區別。

我們看看程序運行的輸出結果:

Naturaljoin operation:
Sno  Cno Score Sname Gender Age
1001 1   85    張三    M      20
1001 2   80    張三    M      20
1002 1   83    李四    F      18
1003 1   76    王五    M      19

如果兩個表(比如R1和R2)有一個以上的屬性名相同,比如2個(比如:A1和A2),那就會自動針對這兩個屬性名(一起)在兩個表中進行等值連接:只有R2.A1 = R1.A1且R2.A2 = R1.A2時,才將元組連接并放入結果關系中。

4.9.3 θ連接(Theta Join)

θ連接是一種通用的連接操作,它使用比等號更一般化的連接條件進行連接。連接條件可以使用除了等號之外的比較運算符(如大于、小于、不等于等)來比較兩個表之間的屬性。

我們來看θ連接的Go示例:

// relational-algebra-examples/query/main.go

func doThetajoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM CourseSelection2022
JOIN Students ON CourseSelection2022.Sno > Students.Sno`)
    dumpOperationResult("Thetajoin", rows)
}

這個示例使用Join將CourseSelection2022表和Students表連接起來,連接條件是CourseSelection2022.Sno > Students.Sno,即學生編號大于學生表中的學生編號,返回的結果將包含CourseSelection2022和`Students兩個表中滿足連接條件的元組。

Thetajoin operation:
Sno  Cno Score Sno  Sname Gender Age
1002 1   83    1001 張三    M      20
1003 1   76    1001 張三    M      20
1003 1   76    1002 李四    F      18

這個結果的生成過程大致如下:

  • 先看CourseSelection2022表的第一個元組,其Sno為1001,該Sno不大于Students表中的任一個Sno;
  • 再看CourseSelection2022表的第二個元組,其Sno為1002,該Sno僅大于Students表中的Sno為1001的那一個元組,于是將CourseSelection2022表的第二個元組和Students表中第一個元組連接起來作為結果表中的第一個元組;
  • 最后看CourseSelection2022表的第三個元組,其Sno為1003,該Sno大于Students表中的Sno為1001和1002的元組,于是將CourseSelection2022表的第三個元組分別和Students表中第一個和第二個元組連接起來作為結果表中的第二個和第三個元組。

4.9.4 半連接(Semi Join)

半連接是一種特殊的連接操作,它返回滿足連接條件的左側關系中的元組,并且只返回右側關系中與之匹配的屬性。半連接通常用于判斷兩個關系中是否存在匹配的元組,而不需要返回右側關系的詳細信息。

我們來看半連接的Go示例:

// relational-algebra-examples/query/main.go

func doSemijoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM Students
WHERE EXISTS (
    SELECT *
    FROM CourseSelection2022
    WHERE Students.Sno = CourseSelection2022.Sno
)`)
    dumpOperationResult("Semijoin", rows)
}

這個示例使用半連接操作,以Students表為左側關系,CourseSelection2022表為右側關系。它使用子查詢來判斷左側關系中是否存在滿足連接條件的元組,即Students.Sno = CourseSelection2022.Sno。它返回的結果將只包含滿足連接條件的Students表中的元組。

下面是程序輸出的結果:

Semijoin operation:
Sno  Sname Gender Age
1001 張三    M      20
1002 李四    F      18
1003 王五    M      19

半連接返回的結果關系中只包含左關系中的行,其中每一行只返回一次,即使在右關系中有多個匹配項。

4.9.5 反連接(Anti Join)

反連接是半連接的補集操作,它返回左側關系中不存在滿足連接條件的元組。反連接通常用于查找在左側關系中存在而在右側關系中不存在的元組。

我們來看反連接的Go示例:

// relational-algebra-examples/query/main.go

func doAntijoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM Students
WHERE NOT EXISTS (
    SELECT *
    FROM CourseSelection2022
    WHERE Students.Sno = CourseSelection2022.Sno
)`)
    dumpOperationResult("Antijoin", rows)
}

這個示例使用反連接操作,以Students表為左側關系,CourseSelection2022表為右側關系,并使用NOT EXISTS子查詢來判斷左側關系中不存在滿足連接條件的元組,即Students.Sno = CourseSelection2022.Sno。返回的結果將只包含左側關系Students表中不存在連接條件的元組。

Antijoin operation:
Sno Sname Gender Age

我們看到輸出的元組集合為空。

4.9.6 左(外)連接(Left Outer Join)

左外連接是將左側關系中的所有元組與滿足連接條件的右側關系中的元組進行連接,并返回所有左側關系的元組。如果右側關系中沒有與左側關系匹配的元組,對應的屬性值將為NULL。

我們來看左(外)連接的Go示例:

// relational-algebra-examples/query/main.go

func doLeftjoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM Students
LEFT JOIN CourseSelection2022 ON Students.Sno = CourseSelection2022.Sno`)
    dumpOperationResult("Leftjoin", rows)
}

這個示例使用左外連接將Students表和CourseSelection2022表連接起來,其連接條件是Students.Sno = CourseSelection2022.Sno,即學生編號相等。示例的返回結果將包含Students表中的所有元組,并將滿足連接條件的CourseSelection2022表中的元組加入結果中。如果沒有匹配的元組,右側關系中的屬性值將為NULL。 ` 下面是程序輸出的結果:

Leftjoin operation:
Sno  Sname Gender Age Sno  Cno Score
1001 張三    M      20  1001 1   85
1001 張三    M      20  1001 2   80
1002 李四    F      18  1002 1   83
1003 王五    M      19  1003 1   76

4.9.7 右(外)連接(Right Outer Join)

右外連接是將右側關系中的所有元組與滿足連接條件的左側關系中的元組進行連接,并返回所有右側關系的元組。如果左側關系中沒有與右側關系匹配的元組,對應的屬性值將為NULL。

我們來看右(外)連接的Go示例:

// relational-algebra-examples/query/main.go

func doRightjoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM Students
RIGHT JOIN CourseSelection2022 ON Students.Sno = CourseSelection2022.Sno`)
    dumpOperationResult("Rightjoin", rows)
}

這個示例使用右外連接將Students表和CourseSelection2022表連接起來,它的連接條件是Students.Sno = CourseSelection2022.Sno,即學生編號相等。返回的結果將包含CourseSelection2022表中的所有元組,并將滿足連接條件的Students表中的元組加入結果中。如果沒有匹配的元組,左側關系中的屬性值將為NULL。

下面是程序輸出的結果:

Rightjoin operation:
Sno  Sname Gender Age Sno  Cno Score
1001 張三    M      20  1001 1   85
1001 張三    M      20  1001 2   80
1002 李四    F      18  1002 1   83
1003 王五    M      19  1003 1   76

4.9.8 全連接(Full Outer Join)

全連接是將左側關系和右側關系中的所有元組進行連接,并返回所有滿足連接條件的元組。如果左側關系或右側關系中沒有與對方匹配的元組,對應的屬性值將為NULL。

我們來看全連接的Go示例:

// relational-algebra-examples/query/main.go

func doFulljoin(db *sql.DB) {
    rows, _ := db.Query(`SELECT *
FROM Students
FULL JOIN CourseSelection2022 ON Students.Sno = CourseSelection2022.Sno`)
    dumpOperationResult("Fulljoin", rows)
}

這個示例使用全連接將Students表和CourseSelection2022表連接起來,連接條件是Students.Sno = CourseSelection2022.Sno,即學生編號相等。示例返回的結果將包含Students表和CourseSelection2022表中的所有元組,并將滿足連接條件的元組進行組合。如果沒有匹配的元組,對應關系中的屬性值將為NULL。

下面是程序輸出的結果:

Fulljoin operation:
Sno  Sname Gender Age Sno  Cno Score
1001 張三    M      20  1001 1   85
1001 張三    M      20  1001 2   80
1002 李四    F      18  1002 1   83
1003 王五    M      19  1003 1   76

以上就是本文要介紹的連接類型,這些連接類型提供了在關系數據庫中操作和組合表數據的靈活性,可以根據特定的需求選擇合適的連接方式來獲取所需的結果。

5. 小結

本文系統地介紹和講解了關系數據庫中的關系代數運算,包括選擇、投影、連接、交、并、積等,以及關系代數的SQL實現,并給出了Go語言示例。

關系模型是關系數據庫的理論基礎,關系代數通過對關系的運算來表達查詢,因此關系代數也構成了SQL查詢語言的理論基礎。理解關系代數與SQL的對應關系,可以更好地使用SQL語言操作關系型數據庫。

本文算是關系數據庫的入門文章,既能讓數據庫初學者快速掌握關系代數,也能讓有基礎的讀者回顧并深入理解概念內涵。通過閱讀學習,能幫助讀者把關系代數運用到實際數據庫應用中,解決查詢優化等問題。


責任編輯:華軒 來源: TonyBai
相關推薦

2010-09-08 16:17:37

SQL循環語句

2010-09-25 14:59:54

SQL語句

2017-08-02 17:00:51

SQL關系代數數據

2010-09-17 09:35:51

SQL中if語句

2024-08-07 09:51:51

2011-06-02 10:20:09

SQL主從關系

2021-10-18 10:17:07

Go Golang語言

2010-09-07 14:45:34

sql語句

2023-03-30 09:10:06

SQLSELECTFROM

2023-05-24 09:31:51

CGo

2022-02-09 16:02:26

Go 語言ArraySlice

2010-09-07 14:36:24

SQL語句

2018-03-12 22:13:46

GO語言編程軟件

2010-09-09 09:49:18

SQL函數存儲過程

2018-04-19 14:54:12

2023-03-29 08:03:53

2010-04-19 13:50:27

Oracle調整

2010-09-08 17:10:24

SQL循環語句

2010-11-18 12:58:25

Oracle條件分支語

2023-12-30 18:35:37

Go識別應用程序
點贊
收藏

51CTO技術棧公眾號

黄色网页在线免费观看| 国产精品乱子伦| 伊人久久大香| 亚洲欧美另类小说| 高清日韩一区| 草久久免费视频| 日韩av密桃| 精品国产一区二区三区忘忧草 | 91精品国产免费久久久久久 | 四虎国产精品成人免费影视| 亚洲最大的成人av| 亚洲精品之草原avav久久| 免费日韩中文字幕| 91最新在线视频| 久久综合九色综合欧美98| 久热精品在线视频| 中文字幕免费在线播放| 韩日一区二区| 亚洲成av人片在线观看| 成人免费激情视频| 日韩av在线电影| 成人在线分类| 欧美性jizz18性欧美| 日韩不卡av| 亚洲精品无amm毛片| 日av在线不卡| 欧美精品videosex极品1| 免费成人深夜天涯网站| av在线播放资源| 中文一区在线播放| 国产精品永久免费| 中文字幕日韩一级| 亚洲欧美伊人| 欧美一区二区三区婷婷月色| 黄色免费观看视频网站| 亚洲www色| 国产精品久久久久久户外露出| 国产嫩草一区二区三区在线观看| 97人妻人人澡人人爽人人精品| 精品日韩免费| 亚洲精品电影网| 男插女视频网站| 成人精品三级| 在线免费观看成人短视频| 亚洲不卡中文字幕无码| 免费污视频在线| 亚洲男人都懂的| 手机在线观看国产精品| 青草久久伊人| av成人免费在线观看| 99精彩视频| 99久久精品国产一区二区成人| 男女激情视频一区| 国产精品视频免费在线观看| 精人妻无码一区二区三区| 欧美一级专区| 57pao精品| 日本午夜视频在线观看| 在线日韩欧美| 国语自产精品视频在免费| 国产一级一片免费播放放a| 欧美国产专区| 欧美成人自拍视频| 国产精品白嫩白嫩大学美女| 加勒比中文字幕精品| 日韩欧美三级在线| 国产免费无码一区二区| 91亚洲无吗| 亚洲大胆美女视频| 激情内射人妻1区2区3区 | 国产做受69高潮| 久久精品www| 亚洲精品一级| 欧美亚洲在线视频| 波多野结衣日韩| 美女网站色91| 亚洲a级在线播放观看| 亚洲AV无码精品色毛片浪潮| 成人动漫中文字幕| 欧美久久久久久久| 97超碰人人在线| 亚洲人成人一区二区在线观看| 在线观看免费黄色片| 手机在线免费av| 性做久久久久久| 一本久道综合色婷婷五月| 电影在线观看一区二区| 666欧美在线视频| 亚洲天堂av网站| 啄木系列成人av电影| 色阁综合伊人av| 久久国产波多野结衣| 国产一区亚洲| 日韩av电影在线播放| 在线观看xxxx| 成人精品一区二区三区中文字幕| 成人一区二区在线| 男操女在线观看| 国产精品国产三级国产aⅴ原创| 成年丰满熟妇午夜免费视频| 在线观看网站免费入口在线观看国内| 欧美专区日韩专区| 国产精品99精品无码视亚| 欧美自拍一区| 日韩少妇与小伙激情| 欧美熟妇精品一区二区蜜桃视频| 欧美freesex8一10精品| 色噜噜狠狠狠综合曰曰曰 | 性做久久久久久久免费看| 毛葺葺老太做受视频| 欧美视频二区欧美影视| 亚洲色图13p| 欧美黄色免费在线观看| 日韩**一区毛片| 国产精品乱码| av网站在线免费看| 韩国av一区二区三区在线观看| 国产精品久久国产三级国电话系列| 可以在线观看的av| 一片黄亚洲嫩模| 天天操天天爱天天爽| 成人在线视频你懂的| 中文字幕日韩欧美在线| 免费看91的网站| 欧美国产高清| 国产精品中文久久久久久久| 五月天婷婷社区| 亚洲免费三区一区二区| 成人性生生活性生交12| 琪琪久久久久日韩精品| 欧美高清视频一区二区| 91亚洲国产成人久久精品麻豆| 久久一夜天堂av一区二区三区 | 精品999成人| 91精品视频一区| 岛国视频免费在线观看| 午夜精品一区二区三区免费视频| 国产不卡的av| 成人网18免费网站| 国产不卡精品视男人的天堂| 五月婷婷深深爱| 一区二区三区四区高清精品免费观看| 自拍偷拍一区二区三区四区| 蜜桃精品在线| 日韩av网址在线观看| 天天躁日日躁aaaxxⅹ| 欧美性久久久| 成人黄色片视频网站| 草莓福利社区在线| 一区二区三区欧美在线观看| www.色就是色.com| 91九色精品国产一区二区| 国产精品视频999| 成人动漫在线免费观看| 在线日韩一区二区| 亚洲日本精品视频| 日韩电影免费在线观看网站| 2020国产精品久久精品不卡| 久草资源在线| 3d动漫精品啪啪1区2区免费 | 午夜精品在线免费观看| 国内黄色精品| 国产精品日韩欧美| 嫩草香蕉在线91一二三区| 欧美日韩中文另类| 成年人网站在线观看视频| 国内精品视频666| 黄色一级视频播放| 日韩视频1区| 久久久亚洲国产| 亚洲欧洲成人在线| 色中色一区二区| 亚洲高清在线不卡| 欧美国内亚洲| 久久国产精品99久久久久久丝袜| 伊人成综合网站| 视频在线观看99| 精品国产av一区二区三区| 亚洲午夜激情av| 一女三黑人理论片在线| 视频一区二区三区入口| 少妇免费毛片久久久久久久久| 国产成人亚洲一区二区三区| 久久综合国产精品台湾中文娱乐网| 精品美女www爽爽爽视频| 亚洲高清不卡在线观看| 五月天综合视频| 国产一区二区三区免费看| 九九热只有这里有精品| 国产不卡av一区二区| 91美女片黄在线观| 超碰资源在线| 这里精品视频免费| 性猛交xxxx乱大交孕妇印度| 欧美日韩一区免费| 成人欧美一区二区三区黑人一| 国产成人免费在线| 亚洲人成无码www久久久| 午夜国产精品视频免费体验区| 欧美精品欧美精品| 日韩视频一二区| 国产精品国产三级国产专播精品人| 中文字幕中文字幕在线中高清免费版| 亚洲精品按摩视频| 国产欧美一区二区三区视频在线观看| 午夜精品福利在线| 亚洲一区电影在线观看| 97久久超碰国产精品电影| caoporm在线视频| 亚洲在线一区| 成人小视频在线观看免费| 久久中文资源| 91在线视频精品| 日韩三区免费| 久久全国免费视频| 米奇精品一区二区三区| 亚洲久久久久久久久久| www香蕉视频| 欧美日韩一区小说| 亚洲s码欧洲m码国产av| 一区二区激情视频| 可以免费看av的网址| 久久一二三国产| 俄罗斯女人裸体性做爰| 蜜桃av噜噜一区二区三区小说| 欧美日本视频在线观看| 免费观看成人www动漫视频| 国产日韩在线看片| 成人毛片av在线| 亚洲色图第一页| 天堂网在线播放| 日韩欧美一区二区免费| 亚洲综合网av| 在线精品观看国产| 久久精品久久久久久久| 精品久久久久久久大神国产| 国产一级在线播放| 亚洲一区二区三区四区不卡| 久草视频手机在线| 国产精品久久午夜夜伦鲁鲁| 成年网站免费在线观看| 水蜜桃久久夜色精品一区的特点| 欧美日韩黄色一级片| 亚洲看片免费| 成人午夜免费在线| 亚洲国产精品第一区二区三区| 可以免费看的黄色网址| 午夜影院欧美| 日本特级黄色大片| 超碰在线亚洲| yellow视频在线观看一区二区| 精品国产三区在线| 亚洲影视中文字幕| 秋霞一区二区三区| 亚洲自拍偷拍网址| 亚洲国产欧美在线观看| 成人在线观看av| 一区二区网站| 国产伦视频一区二区三区| 澳门成人av| 国产伦精品一区二区三区四区视频| 成人三级毛片| 欧美aaaaa喷水| 国产成人1区| 亚洲欧洲精品在线| 亚洲草久电影| a级黄色片免费| 影音先锋亚洲一区| 欧美a在线视频| 日本美女一区二区三区| 五月天av在线播放| 国产在线播精品第三| 精品国产乱码久久久久夜深人妻| 成人动漫一区二区三区| 一级黄色片大全| 中文字幕成人在线观看| 欧美另类videoxo高潮| 一区二区三区精品视频在线| 日韩av女优在线观看| 色狠狠av一区二区三区| 91成人在线免费| 日韩精品一区二区三区在线| 天天射,天天干| 在线色欧美三级视频| 男人天堂网在线视频| 欧美日韩高清在线播放| av网站免费播放| 日韩激情片免费| 午夜免费福利在线观看| 欧美精品第一页在线播放| 性感女国产在线| 91精品国产综合久久香蕉| www.亚洲一二| 日韩精品一区二区三区色偷偷| 亚洲精品小说| 国内性生活视频| 久久爱www久久做| 欧美xxxxx少妇| 国产精品天美传媒沈樵| 久久午夜无码鲁丝片午夜精品| 日韩欧美在线播放| 精品毛片在线观看| 一区二区欧美日韩视频| 美女精品导航| 国产免费一区二区三区香蕉精| 99re8这里有精品热视频免费 | 精品一区不卡| 欧美一区二区视频在线播放| 快she精品国产999| 免费黄色在线播放| 欧美经典一区二区三区| 国产极品在线播放| 欧美精品18+| 三级在线电影| 欧美精品在线网站| 成人全视频在线观看在线播放高清 | 中文字幕有码在线视频| 国产91久久婷婷一区二区| 日韩免费高清视频网站| 五月天国产一区| 国产亚洲精品久久久久婷婷瑜伽| www.亚洲自拍| 中文字幕免费观看一区| 欧美一区二区三区四| 欧美大片日本大片免费观看| 中国日本在线视频中文字幕| 欧美一级视频免费在线观看| 亚洲一区二区电影| 正在播放一区| 男人操女人的视频在线观看欧美| 日本高清www| 午夜精品久久久久久| 亚洲免费视频网| 欧美成人中文字幕在线| 国产 日韩 欧美| 一本一道久久a久久综合精品| 免费欧美在线| 免费黄色三级网站| 亚洲影院在线观看| a在线观看免费| 久久久97精品| 伊人亚洲精品| 综合网五月天| 精品一区二区在线观看| 国产123在线| 欧美亚洲综合色| 韩国三级在线观看久| 日韩免费观看视频| 欧美男gay| 亚洲精品高清无码视频| 国产午夜精品久久久久久免费视| 欧美激情黑白配| 亚洲精品一二区| 日本免费久久| 日韩一本精品| 免费观看一级特黄欧美大片| 日韩女同一区二区三区| 欧美三级资源在线| 日本视频在线| 国产精品自在线| 99久久九九| 久久发布国产伦子伦精品| 亚洲一区二区在线播放相泽| 黄频在线免费观看| 欧美亚洲国产视频| 国产一区二区三区不卡视频网站| 日本女优爱爱视频| 国产精品国模大尺度视频| 国产乱淫a∨片免费视频| 欧美老女人性视频| 嗯啊主人调教在线播放视频 | 中文字幕视频在线播放| zzijzzij亚洲日本成熟少妇| 高清久久一区| 免费国产a级片| 国产亚洲精品中文字幕| 一级片一区二区三区| 欧美激情亚洲一区| 亚洲警察之高压线| 亚洲一级免费观看| 亚洲蜜臀av乱码久久精品| 日韩中文字幕影院| 国产成人精品av| 91精品观看| 青青草视频网站| 欧美中文字幕一区| 亚洲区欧洲区| 蜜桃视频日韩| 麻豆精品在线看| 成年人在线观看av| 一本一道久久a久久精品 | 久久av红桃一区二区小说| 超碰精品在线| 国产原创精品在线| 亚洲图片一区二区| 阿v免费在线观看| 国产精品视频福利| 日本欧美大码aⅴ在线播放| 国产亚洲欧美精品久久久www| 亚洲深夜福利视频|