Obsidian 插件(一):DataView 的使用

news2024/9/30 5:34:35

文章目录

  • DataView 的使用
    • 一、 环境配置
    • 二、 入门介绍
      • 1、 快速开始
      • 2、 页面和字段
      • 3、 创建查询
      • 4、 系统字段
    • 三、 接口讲解
      • 1、 表达式
        • 1.1 概述
        • 1.2 表达式类型
        • 1.3 特定类型的交互
      • 2、 函数
        • 2.1 构造器
        • 2.2 常用函数
        • 2.3 工具函数

DataView 的使用

一、 环境配置

首先,我们需要安装 Obsidian,同时,在这里 Obsidian 的基本使用就不会去介绍了。

Dataview 是一个覆盖OB知识库的实时索引和查询引擎。可以将数据(如标签、日期、代码段、数字等)与笔记相关联,然后查询(如筛选、排序、转换)数据。用一种数据库处理的形式,通过查找字段并筛选,进而使用列表、表格的形式展现出来,也支持JavaScript的高级查询形式。

安装方式:

或者,我们也可以使用 GitHub 来进行安装,我们将文件下载下来,解压到<vault>/.obsidian/plugins/

二、 入门介绍

1、 快速开始

官方文档:https://blacksmithgu.github.io/obsidian-dataview/

Dataview 是一个在你的知识库中生成数据的动态视图的高级查询引擎/索引。你可以通过使用任意和页面相关联的值,如标签(tag),文件夹(folder), 内容(content),或者字段(field)来生成视图。

我们可以使用 ::来生成 dataview 的数据

或者,这些信息放在 frontmatter,如:

那我们查询的话,

```dataview
LIST "<br>**电影名**:" + Movie + "<br>**简介**:" + Brief + "<br>**评分**:" + Score + "<br>**年份**:" + Year + "<br>**类型**:" + Type + " " + Location 
FROM #entertain/movie 
WHERE contains(file.folder, "record_2023")
SORT Date desc
```

那么,我们就可以生成这样的一个页面:

2、 页面和字段

dataview的核心数据抽象是页面(page) ,指在你的库中包含字段(field) 的markdwon页面。字段 是一段任意命名的数据 ——文本,日期,时间段,链接。 这些可被dataview理解,展示,筛选。字段可以通过三种方式定义:

  1. 扉页(Frontmatter): 所有的YAML 扉页内容都将自动的转换成dataview字段。

    ---
    tags: daily_node study/obsidian entertain/movie
    aliases: 
    describe: Obsidian 中 DataView 的使用
    Date: 2023-01-20
    Time: 09:26:28
    Author: Steve Anthony
    Email: 3500515050@qq.com
    ---
    
  2. 内联字段(inline field): 一行格式为<Name>:: <Value>的内容将自动的被dataview解析为一个字段,请注意,你可以对<Name>使用标准的Markdown格式,但以后将不再支持。

    Movie:: [猎屠](https://v.qq.com/x/cover/mzc00200e7w52db/b0044upikcw.html)
    Brief:: 影片讲述某地发生电信诈骗事件,一名警员潜伏到中缅边境,深入电信诈骗中心,与诈骗犯罪集团斗智斗勇的故事,是一部反电信诈骗题材的院线片。
    Score:: 7.6
    Year:: 2022
    Type:: 犯罪 动作 剧情
    Location:: 内地
    
  3. 隐含字段(implicit): dataview自带大量的元数据对页面进行注释,如文件的创建日期、任何相关的日期、文件中的链接、标签等。

    如果文件的标题内有一个日期(格式为yyyy-mm-dd或yyyymmdd),或者有一个Date字段/inline字段,它也有以下属性:

    • file.day: 一个该文件的隐含日期。

字段类型:

dataview支持数种不同的字段类型:

  • 文本(Text): 全局默认为文本。如果一个字段不匹配其它具体的类型,默认为一段纯文本。
  • 数字(Number): 数字类似于’6’ 和 ‘3.6’。
  • 布尔值(Boolean): true/false, 就像编程中的概念。
  • 日期(Date): ISO8601 标准定义的通用日期格式 YYYY-MM[-DDTHH:mm:ss]. 月份后面的内容都是可选的。
  • 时间段(Duration): 时间段的格式为 <time> <unit>, 就像 6 hours 或者 4 minutes。支持常见的英文缩写如6hrs 或者 2m
  • 链接(Link): 普通的Obsidian 链接如 [[Page]] 或者 [[Page|Page Display]]
  • 列表(List): YAML中,其它dataview字段组成的列表将作为普通的YAML列表定义;对于内联字段,它们就只是逗号分隔的列表。
  • 对象(Object):名称(name)到dataview字段的映射。这仅能在YAML扉页中利用通用的YANML对象语法进行定义。 对象语法: field: value1: 1 value2: 2 ...

不同的字段类型非常重要。这能确保dataview理解怎样合理的对值进行比较和排序,并提供不同的操作。

3、 创建查询

一旦你给相关的页面添加了有用的数据,你就可以在某一个地方展示它或者操作它。dataview通过dataview代码块建立内联查询,写下查询代码,将会动态运行并在笔记的预览窗口展示。写这样的查询,有三种方式:

  1. dataview的查询语言是一个用于快速创建视图,简化的,类SQL的语言。它支持基本的算术和比较操作,对基础应用很友好。
  2. 查询语言也提供内联查询,允许你直接在一个页面内嵌入单个值——通过= date(tody)创建今天的日期,或者通过= [[Page]].value来嵌入另一个页面的字段。
  3. dataview JavaScript API为你提供了 JavaScript 的全部功能,并为拉取 Dataview 数据和执行查询提供了 DSL ,允许你创建任意复杂的查询和视图。

与JavaScript API相比,查询语言的功能往往比较滞后,主要是因为JavaScript API更接近实际代码;相反,查询语言更稳定,在Dataview的重大更新中不太可能出现故障。

还可以创建日历:

CALENDAR Date
FROM #daily_node
WHERE contains(file.folder, "record_2023")

4、 系统字段

  1. FROM

    FROM语句决定了哪些页面在初始被收集并传递给其他命令进行进一步的筛选。你可以从任何来源中选择,来源可选择文件夹,标签,内链和外链。

    • 标签(Tags): 从标签(包含子标签)中选择,使用FROM #tag
    • 文件夹(Folders): 从文件夹(包含子文件夹)中选择,使用 FROM "folder"
    • 链接(Links): 你可以选择一个链接到该文件的链接,也可以选择该文件链接到其它页面的链接:
    • 获得链接到[[note]]的所有页面,使用FROM [[note]]
    • 获得从[[note]]链接的所有页面(如,文件中的所有链接),使用FROM outgoing([[note]])

    你可以对过滤器进行组合,以便使用 "and "和 "or "获得更高级的来源。

    举个例子

    • #tag and "folder"将返回在folder中和包含#tag的所有页面。
    • [[Food]] or [[Exercise]] 将给出任何链接到[[Food]][[Exercise]]的页面。
  2. WHERE: 笔记进行过滤,聚合条件

  3. SORT:根据什么条件进行排序

    你可以给出多个字段来进行排序。排序将在第一个字段的基础上进行。接着,如果出现相等,第二个字段将被用来对相等的字段进行排序。如果仍然有相等,将用第三个字段进行排序,以此类推。

    SORT field1 [ASCENDING/DESCENDING/ASC/DESC], ..., fieldN [ASC/DESC]
    
  4. GROUP BY:

    对一个字段的所有结果进行分组。每个唯一的字段值产生一行,它有两个属性:

    • 一个对应于被分组的字段
    • 一个是rows数组字段,包含所有匹配的页面。
  5. LIMIT: 限制输出多少条结果

  6. FLATTEN: 根据字段或计算将一个结果拆分为多个结果。

    FLATTEN field
    FLATTEN (computed_field) AS name
    

三、 接口讲解

1、 表达式

1.1 概述

Dataview查询语言表达式 可以是任何能产生一个值的量,所有字段都是表达式,字面值如6,已计算的值如field - 9都是一个表达式,做一个更具体的总结:

# 常规
field               (directly refer to a field)
simple-field        (refer to fields with spaces/punctuation in them like "Simple Field!")
a.b                 (if a is an object, retrieve field named 'b')
a[expr]             (if a is an object or array, retrieve field with name specified by expression 'expr')
f(a, b, ...)        (call a function called `f` on arguments a, b, ...)

# 算术运算
a + b               (addition)
a - b               (subtraction)
a * b               (multiplication)
a / b               (division)

# 比较运算
a > b               (check if a is greater than b)
a < b               (check if a is less than b)
a = b               (check if a equals b)
a != b              (check if a does not equal b)
a <= b              (check if a is less than or equal to b)
a >= b              (check if a is greater than or equal to b)

# 特殊操作
[[Link]].value      (fetch `value` from page `Link`)

1.2 表达式类型

比较运算符:

你可以使用各种比较运算符来比较大多数数值。<, >, <=, >=, =, !=. 这产生了一个布尔的真或假值,可以在查询中的`WHERE’块中使用。

对象获数组:

你可以通过索引操作符array[<index>]从数组中索引数据,其中<index>是任何已计算的表达式。 数组是以0为索引的,所以第一个元素是索引0,第二个元素是索引1,以此类推。 例如,list(1, 2, 3)[0] = 1.

你也可以使用索引操作符从对象(将文本映射到数据值)中检索数据,此时的索引是字符串/文本而不是数字。你也可以使用快捷方式object.<name>,其中<name>是值的索引。例如object("yes", 1).yes = 1

函数的调用

Dataview支持各种用于操作数据的函数,这些函数在函数文档中有完整描述。它们的一般语法是function(arg1, arg2, ...) - 即lower("yes")regexmatch("text", ".+")

1.3 特定类型的交互

大多数dataview类型与运算符有特殊的相互作用,或者有额外的字段可以使用索引操作符索引。

日期:

你可以通过索引来检索一个日期的不同组成部分:date.yeardate.monthdate.daydate.hourdate.minute, date.second, date.week。你也可以将时间段添加到日期中以获得新的日期。

时间段:

时间段可以相互添加,也可以添加到日期。你可以通过索引来检索一个时间段的各种组成部分。duration.years, duration.months, duration.days, duration.hours, duration.minutes, duration.seconds.

链接:

你可以 "通过索引 "一个链接来获得相应页面上的值。例如,[[Link]].value将获得来自Link页面上的value值。

2、 函数

2.1 构造器

构造器创建值

object(key1, value1, ...)

用给定的键和值创建一个新的对象。在调用中,键和值应该交替出现,键应该总是字符串/文本。

object() => empty object
object("a", 6) => object which maps "a" to 6
object("a", 4, "c", "yes") => object which maps a to 4, and c to "yes"

list(value1, value2, ...)

用给定的值创建一个新的列表。

list() => empty list
list(1, 2, 3) => list with 1, 2, and 3
list("a", "b", "c") => list with "a", "b", and "c"

date(any)

从提供的字符串、日期或链接对象中解析一个日期,解析不出返回null。

date("2020-04-18") = <date object representing April 18th, 2020>
date([[2021-04-16]]) = <date object for the given page, refering to file.day>

number(string)

从给定的字符串中抽出第一个数字,并返回该数字。如果字符串中没有数字,则返回null。

number("18 years") = 18
number(34) = 34
number("hmm") = null

link(path, [display])

从给定的文件路径或名称构建一个链接对象。如果有两个参数,第二个参数是链接的显示名称。

link("Hello") => link to page named 'Hello'
link("Hello", "Goodbye") => link to page named 'Hello', displays as 'Goodbye'

elink(url, [display])

构建一个指向外部网址的链接(如www.google.com)。如果有两个参数,第二个参数是该链接的显示名称。

elink("www.google.com") => link element to google.com
elink("www.google.com", "Google") => link element to google.com, displays as "Google"

2.2 常用函数

数值操作

round(number, [digits])

将一个数字四舍五入到指定的位数。如果没有指定第二个参数,则舍入到最接近的整数。 否则,四舍五入到给定的位数。

round(16.555555) = 17
round(16.555555, 2) = 16.56

对象,数组和字符串操作

对容器对象内部的值进行操作的操作。

contains(object|list|string, value)

检查给定的容器类型中是否有给定的值。这个函数的行为稍有不同,它基于第一个参数是一个对象,一个列表,还是一个字符串。

  • 对于对象,检查该对象是否有一个给定名称的键。如: contains(file, "ctime") = true contains(file, "day") = true (if file has a date in its title, false otherwise)
  • 对于列表,检查数组中是否有元素等于给定的值。如: contains(list(1, 2, 3), 3) = true contains(list(), 1) = false
  • 对于字符串,检查给定的值是否是字符串的子串。 contains("hello", "lo") = true contains("yes", "no") = false

extract(object, key1, key2, ...)

从一个对象中抽出多个字段,创建一个抽出字段的新对象。

extract(file, "ctime", "mtime") = object("ctime", file.ctime, "mtime", file.mtime)
extract(object("test", 1)) = object()

sort(list)

排序列表,返回一个排序好的新列表。

sort(list(3, 2, 1)) = list(1, 2, 3)
sort(list("a", "b", "aa")) = list("a", "aa", "b")

reverse(list)

反转列表,返回一个反转好的新列表。

reverse(list(1, 2, 3)) = list(3, 2, 1)
reverse(list("a", "b", "c")) = list("c", "b", "a")

length(object|array)

返回一个对象中的字段数量,或一个数组中的元素数量。

length(list()) = 0
length(list(1, 2, 3)) = 3
length(object("hello", 1, "goodbye", 2)) = 2

sum(array)

数组中数值元素求和。

sum(list(1, 2, 3)) = 6

all(array)

只有当数组中的所有值都为真,才会返回 “true”。你也可以给这个函数传递多个参数,只有当所有的参数都为真时,它才会返回`true’。

all(list(1, 2, 3)) = true
all(list(true, false)) = false
all(true, false) = false
all(true, true, true) = true

any(array)

只要数组中有值为真,便返回true。也可以给这个函数传递多个参数,只要有参数为真,便返回true

any(list(1, 2, 3)) = true
any(list(true, false)) = true
any(list(false, false, false)) = false
all(true, false) = true
all(false, false) = false

none(array)

如果数组中没有元素,返回none

join(array)

将一个数组中的元素连接成一个字符串(即在同一行呈现所有的元素)。如果有第二个参数,那么每个元素将被给定的分隔符分开。

join(list(1, 2, 3)) = "1, 2, 3"
join(list(1, 2, 3), " ") = "1 2 3"
join(6) = "6"
join(list()) = ""

字符串操作

regexmatch(pattern, string)

检查给定的字符串是否与给定的模式相匹配(使用JavaScript regex引擎)。

regexmatch("\w+", "hello") = true
regexmatch(".", "a") = true
regexmatch("yes|no", "maybe") = false

regexreplace(string, pattern, replacement)

用 "replacement "替换所有在 "string "中匹配regex pattern的实例。这使用了JavaScript的替换方法,所以你可以使用特殊字符如$1来指代第一个捕获组,以此类推。

regexreplace("yes", "[ys]", "a") = "aea"
regexreplace("Suite 1000", "\d+", "-") = "Suite -"

replace(string, pattern, replacement)

replacement替换string中的所有pattern实例。

replace("what", "wh", "h") = "hat"
replace("The big dog chased the big cat.", "big", "small") = "The small dog chased the small cat."
replace("test", "test", "no") = "no"

lower(string)

将一个字符串所有字符转换为小写字符。

lower("Test") = "test"
lower("TEST") = "test"

upper(string)

将一个字符串所有字符转换为大写字符。

upper("Test") = "TEST"
upper("test") = "TEST"

2.3 工具函数

default(field, value)

如果field为空,返回value;否则返回field。对于用默认值替换空值很有用。例如,要显示尚未完成的项目,使用"incomplete"作为其默认值。

default(dateCompleted, "incomplete")

默认值在两个参数中都是矢量;如果你需要在一个列表参数中明确使用默认值,请使用ldefault,它与默认值相同,但没有被矢量化。

default(list(1, 2, null), 3) = list(1, 2, 3)
ldefault(list(1, 2, null), 3) = list(1, 2, null)

choice(bool, left, right)

一个原始的if语句–如果第一个参数为真,则返回第二个参数的内容;否则,返回第三个参数的内容。

choice(true, "yes", "no") = "yes"
choice(false, "yes", "no") = "no"
choice(x > 4, y, z) = y if x > 4, else z

striptime(date)

剥离日期中的时间部分,只留下年、月、日。如果你在比较日期的时候不在乎时间,这种方式挺好。

striptime(file.ctime) = file.cday
striptime(file.mtime) = file.mday

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/174283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32设备驱动-DHT11温度湿度传感器驱动

DHT11温度湿度传感器驱动 1、DHT11介绍 DHT11数字温湿度传感器是一种复合传感器,包含一个经过校准的温湿度数字信号输出。 应用了专用的数字模块采集技术和温湿度传感技术,确保产品具有高可靠性和优异的长期稳定性。 该传感器包括一个电阻感湿元件和一个 NTC 温度测量装置,…

LeetCode题目笔记——1814. 统计一个数组中好对子的数目

文章目录题目描述题目难度——中等方法一&#xff1a;模拟&#xff08;超时&#xff09;&#xff08;参考&#xff09;代码/Python方法二&#xff1a;优化代码总结题目描述 给你一个数组 nums &#xff0c;数组中只包含非负整数。定义 rev(x) 的值为将整数 x 各个数字位反转得…

Kong Api Gateway

Kong Api Gateway什么是API 网关为什么是 Kong安装Kong通过包管理器来安装启动Kong配置文件详解1、常规配置2、Nginx注入配置3、数据库存储配置4、数据库缓存配置DNS解析器配置其他杂项配置API 管理详解1、查看节点信息2、查看节点状态3、添加服务4、查询服务5、查询所有服务6、…

golang入门笔记——Hertz

文章目录Hertz介绍应用层路由层协议层传输层HZ脚手架Hertz的使用一个简单的案例&#xff1a;利用Hertz监听8080端口并编写/ping的get处理函数Hertz和gin一样&#xff0c;提供了分组路由的功能Hertz路由的匹配优先级&#xff1a;静态路由>命名路由>通配路由参数绑定&#…

SD卡读写实验(SPI模式)

对于 SD 卡的 SPI 模式而言&#xff0c;采用的 SPI 的通信模式为模式 3&#xff0c;即 CPOL1&#xff0c;CPHA1&#xff0c;在 SD 卡 2.0 版本协议中&#xff0c;SPI_CLK 时钟频率可达 50Mhz。SD 卡的 SPI 模式&#xff0c;只用到了 SDIO_D3&#xff08;SPI_CS&#xff09;、SD…

16投影矩阵和最小二乘法

投影矩阵和最小二乘法 投影矩阵 **投影矩阵P与向量b相乘将会把投影到的列空间A中。**那么现在我们来考虑两个极端的例子&#xff0c;这两个极端的例子将会加深我们对投影矩阵的理解。 如果b在矩阵A的列空间里&#xff0c;那么 Pb b 如果b垂直于矩阵A的列空间&#xff0c;那…

经典同步问题

同步问题是一个复杂的问题&#xff0c;但是它也有自己的方法去处理、去分析。PV操作系统的解题思路&#xff1a;关系分析。找出题目中描述的各个进程&#xff0c;分析它们之间的同步、互斥关系。(从事件的角度分析)整理思路。根据各进程的操作流程确定P、V操作的大致顺序。设置…

Java设计模式-备忘录模式、备忘录模式应用场景是什么、又怎么使用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 6.11 备忘录模式 6.11.1 定义 又称快照模式&#xff0c;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存此状态&…

深入学习Vue.js(十一)内建组件和模块

文章目录KeepAlive组件的实现原理1.KeepAlive组件实现原理2.KeepAlive组件的代码实现&#xff08;1&#xff09;shouldKeepAlive&#xff08;2&#xff09;keepAliveInstance&#xff08;3&#xff09;keptAlive&#xff08;4&#xff09;move函数3.include和exclude4.缓存策略…

视频生成动画数据OpenPose+OpenCV

我们只是使用OpenPose&#xff0c;不包括深度学习和代码的部分&#xff0c;会用就OK。 1.打开OpenPose的官网&#xff0c;直接进入安装页面&#xff0c;地址如下&#xff1a; OpenPose: OpenPose Doc - Installation 2.安装的说明&#xff0c;大家要好好看&#xff0c;我们就…

吴恩达机器学习课程笔记:多元梯度下降法

1.吴恩达机器学习课程笔记&#xff1a;多元梯度下降法 笔记来源&#xff1a;吴恩达机器学习课程笔记&#xff1a;多元梯度下降法 仅作为个人学习笔记&#xff0c;若各位大佬发现错误请指正 1.1 多元特征&#xff08;变量&#xff09; 每一列代表一个特征&#xff0c;例如&…

【Github CLI】Take GitHub to the command line

目录儿~一、Git、Github、GitLab二、Github CLI——gh2.1 gh简介2.2 gh的使用2.21 Github身份验证&#xff08;必选&#xff09;2.22 常用命令&#xff08;1&#xff09;在Github仓库中打开当前项目&#xff08;2&#xff09;gh配置 gh config&#xff08;3&#xff09;克隆仓库…

(16)go-micro微服务jaeger链路追踪

文章目录一 jaeger链路追踪介绍什么是链路追踪&#xff1a;链路追踪主要功能&#xff1a;二 jaeger链路追踪作用三 jaeger链路追踪主要特性四 jaeger链路追踪原理图1.链路调用原理2. 一次调用链分析3.链路追踪存储与调用五 jaeger链路追踪五个重要组件六 jaeger链路追踪安装1.d…

Junit框架

JUnit 是一个 Java 编程语言的单元测试框架。环境配置创建maven项目&#xff0c;导入Junit配置<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency><groupId>org.junit.jupiter</groupId><artifactId&g…

Linux常用命令——tail命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tail 在屏幕上显示指定文件的末尾若干行 补充说明 tail命令用于输入文件中的尾部内容。tail命令默认在屏幕上显示指定文件的末尾10行。如果给定的文件不止一个&#xff0c;则在显示的每个文件前面加一个文件名…

【docker概念和实践 4】 常见命令和案例(1)

一、说明 本篇讲述当Docker安装完成后&#xff0c;进行的由浅入深的操作过程。命令种类有&#xff1a;1 进程引擎进程命令 2帮助命令 3 镜像命令 4 容器命令 5 仓库命令。 二、关于操作引擎的指令 本节讲操作引擎的启动、关闭、维护等。以下两种形势都是等价的命令格式。 方法…

Java概览——Java运行机制

Java概览—Java运行机制Java的运行过程 Java程序运行时&#xff0c;必须经过编译和运行两个步骤。首先将后缀名为.java的源文件进行编译&#xff0c;最终生成后缀名为.class的字节码文件&#xff0c;然后Java虚拟机&#xff0c;将字节码文件进行解释执行&#xff0c;并将结果显…

Docker学习笔记【part1】概念与安装

一、Docker的概念 Docker 是实现系统平滑移植、容器虚拟化的技术&#xff0c;基于 Go语言&#xff0c;可以实现软件带环境安装&#xff0c;做到“一次镜像&#xff0c;处处运行”。Docker 是一个 C/S 模式的架构&#xff0c;后端是一个松耦合架构&#xff0c;众多模块各司其职…

九龙证券|次新股叠加智慧交通+信创+数字经济概念,开盘冲涨停!

核算机板块1月以来跑赢上证指数&#xff1b;才智交通、成绩高增及严重财物重组个股登上涨停榜。 证券时报•数据宝核算&#xff0c;1月19日&#xff0c;沪深两市收盘涨停股35只&#xff0c;其中ST股6只。群众交通、长久科技两股一字板强势涨停&#xff0c;潞安环能、跃岭股份收…

【MySQL】第五部分 多表查询

【MySQL】第五部分 多表查询 文章目录【MySQL】第五部分 多表查询5. 多表查询5.1 等值连接5.2 非等值连接5.3 自连接5.4 内连接5.5 外连接5.6 满外连接5.7 SQL99语法实现多表查询5.7.1 JOIN...ON语法5.7.2 使用SQL99语法实现内连接5.7.3 使用SQL99语法实现左外连接和右外连接5.…