Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

数据库表 blocks 加入 depth 字段(一炮三响) #5769

Closed
88250 opened this issue Aug 31, 2022 · 10 comments
Closed

数据库表 blocks 加入 depth 字段(一炮三响) #5769

88250 opened this issue Aug 31, 2022 · 10 comments

Comments

@88250
Copy link
Member

88250 commented Aug 31, 2022

对了,这个问题的相关讨论请参考 https://ld246.com/article/1634221433631

看了下相关讨论,个人觉得添加一个 depth 能解决很大一部分的问题,这个 depth 值得是当前块在整个列表中的深度。
比如:

* [ ] item1
* [ ] item2
* [ ] item3
    * [ ] item3-1
    * [ ] item3-2

对应的数据库:

type markdown depth
l * [ ] item1 *[ ]item2 ...... 0
i * [ ] item1 0
i * [ ] item2 0
l * [ ] item3 *[ ]item3-1 .... 0
i * [ ] item3 0
l * [ ]item3-1 * []item3-2 .... 1
i * [ ]item3-1 1
i * [ ]item3-2 1

这样根据 type、subtype、depth就能够解决很多关于列表项区别的问题了

Originally posted by @lengyu-lys in #5717 (comment)

@88250
Copy link
Member Author

88250 commented Aug 31, 2022

@lengyu-lys 这个改进的主要思路是 l 保存完整 md,i 的话仅保存第一个子块 md,是这样对吗?

@deerainw
Copy link

能否做到分别进行以下查询(没有一炮三响):

1、查询出文档 A 中所有的任务项(每个任务项的层级各不相同)
2、查询出文档 A 中所有的任务项(每个任务项的层级各不相同),以及这些任务项的全部子列表
3、查询出所有笔记里包含关键词 foo 的 i(foo 出现的每个地方层级不同)
4、查询出所有笔记里包含关键词 foo 的 i(foo 出现的每个地方层级不同),以及这些 i 的全部子列表

@fanglypro
Copy link

fanglypro commented Sep 1, 2022

roam的query能实现以下查询,这也是roam中最基础最常用的query之一,也就是查询同时包含关键词foo1和关键词foo2的块,而且这两个关键词可能不在一个block里面,有三种情况:1、foo1和foo2在一个block里面,2、包含foo1的block的一个子块含有foo2,3、包含foo2的block的一个子块含有foo1。下面这个图片就是第2种情况:
image

思源中目前想实现这个查询,需要这样:
image

如果l 保存完整 md,i 的话仅保存第一个子块 md,加入depth,我没想到如何较为简便地实现上面这个查询

这篇文章介绍了一些roam的数据结构:https://sspai.com/post/65426 ,虽然roam和思源数据库完全不是一个类型,不过或许会有一些启发

@88250
Copy link
Member Author

88250 commented Sep 4, 2022

看来还得继续考虑,先关闭了,感谢各位帮忙。

@88250 88250 closed this as completed Sep 4, 2022
@88250 88250 changed the title 数据库表 blocks 加入 depth 字段 数据库表 blocks 加入 depth 字段(一炮三响) Sep 4, 2022
@lengyu-lys
Copy link

roam的query能实现以下查询,这也是roam中最基础最常用的query之一,也就是查询同时包含关键词foo1和关键词foo2的块,而且这两个关键词可能不在一个block里面,有三种情况:1、foo1和foo2在一个block里面,2、包含foo1的block的一个子块含有foo2,3、包含foo2的block的一个子块含有foo1。下面这个图片就是第2种情况: image

思源中目前想实现这个查询,需要这样: image

如果l 保存完整 md,i 的话仅保存第一个子块 md,加入depth,我没想到如何较为简便地实现上面这个查询

这篇文章介绍了一些roam的数据结构:https://sspai.com/post/65426 ,虽然roam和思源数据库完全不是一个类型,不过或许会有一些启发

这个通过深度是可以的呀, 不要通过 i 类型查询,而是通过 l 类型查询。我理解下面的 sql 应该就可A以满足你的需求

SELECT * FROM block WHERE markdown like '%[[foo1]]%[[foo2]]%' AND type = 'l' ORDER BY depth DESC limit 1

@fanglypro
Copy link

加入 limit 1 的话就只展示一个结果了?

大部分情况下有多个搜索结果:

image

@fanglypro
Copy link

fanglypro commented Sep 7, 2022

此外,在下面这种情况下,如果搜索列表块而不是列表项块,会发现内容很冗余,并不能精准定位到要查询的内容

image

上图roam中的query类似于搜索列表项块,如果在思源中搜索列表块,会把图片中的整个列表全搜出来,里面有五分之四的内容是冗余的,在搜索结果中起到干扰作用

@lengyu-lys
Copy link

lengyu-lys commented Sep 8, 2022

此外,在下面这种情况下,如果搜索列表块而不是列表项块,会发现内容很冗余,并不能精准定位到要查询的内容

image

上图roam中的query类似于搜索列表项块,如果在思源中搜索列表块,会把图片中的整个列表全搜出来,里面有五分之四的内容是冗余的,在搜索结果中起到干扰作用

按照您之前的说法,siyuan是可以实现类似的搜索,只不过比较复杂,这个是 sql 搜索语法和 roam 搜索语法的区别造成的,感觉和这次讨论的问题关系不大。引入 depth 并不会和您搜索的问题产生冲突,同时会解决很多其它的问题,因此我认为引入depth是有意义的。

关于您提出的问题,我觉得应该单独开一个 issue 提出对 搜索语法进行优化(比如可以效仿roam 语法之类的)

@fanglypro
Copy link

fanglypro commented Sep 8, 2022

roam/logseq 中的数据库

roam的数据库中,每个块的:block/parents属性存储了其父级id列表,即父块、祖父块、曾祖父块……的id,而思源中只存储了一个父块的id。

有了这个父级id列表后,在{{[[query]]: {and: [[foo1]] [[foo2]]}}}时,对于某个块,除了看自己的内容中有没有满足要求,还会同时看所有的父级id所对应的内容,可以等价于说某个block的content是自己的content加上所有父级块的content,但这个content是在query的过程中实时计算的,而思源可以理解成是预先存储这些content的(当然还是有不小区别的,roam中看某个块的所有父级,思源是存储某个块的所有子级,不过本质上差不多),某种程度上思源现有的技术方案是用空间换时间。只存储一个父块的id在理论上也可以搜,但在时间效率和实现复杂度上会差很多。(这段内容是我根据roam的数据库结构猜测的roam的query的实现方案,目前没找到相关资料,有可能roam在实现时和我说的有差异)

此外,roam的:block/children属性包含了一个块的所有一级子块的id列表,二级子块往下深度的都不包含在内,通过这个属性可以自顶向下地遍历,由roam的query无法查询到同级别的多关键词可推导出,roam的query在实现时没有用到:block/children属性,而是用到上面的:block/parents属性。

logseq中,每个块的:block/parent属性中只存储一个父块的id,但:block/path-refs属性存储了自身以及其所有父块的内容中所引用的页面id,例如{[[foo1]]的id,[[foo2]]的id}这样,存储这个之后,在{{[[query]]: {and: [[foo1]] [[foo2]]}}}中就可以直接通过这个属性进行查询。但这个设计相比roam的就局限很多,在query时只能以[[foo1]]这种页面引用为关键词,不能使用普通文本关键词。

logseq 中 query 的具体实现

我目前没有找到roam的query的实现方案,logseq的query原理有一些资料我研究了下,这里简单介绍下。

例如{{[[query]]: {and: [[foo1]] [[foo2]]}}}在logseq中会先转化为:

{:query
 [:find
  (pull
   ?b
   [:db/id
    :block/uuid
    :block/parent
    :block/left
    :block/collapsed?
    :block/format
    :block/refs
    :block/_refs
    :block/path-refs
    :block/tags
    :block/content
    :block/marker
    :block/priority
    :block/properties
    :block/pre-block?
    :block/scheduled
    :block/deadline
    :block/repeated?
    :block/created-at
    :block/updated-at
    :block/file
    :block/heading-level
    {:block/page
     [:db/id :block/name :block/original-name :block/journal-day]}
    {:block/_parent ...}])
  :in
  $
  %
  :where
  (page-ref ?b "foo1")
  (page-ref ?b "foo2")],
 :query-string "(and [[foo1]] [[foo2]])",
 :rules
 [[(page-ref ?b ?page-name)
   [?b :block/path-refs [:block/name ?page-name]]]]}

这一段代码的核心内容就是最后面对:block/path-refs属性进行匹配。

思源中的一种解决方案

一种解决方案是,类似roam,在数据库中加入每个块的父级id列表(不需要加入depth,这个列表本身其实也包含了depth信息),然后列表项块和列表块等容器块的content可以直接留空(存储第一个块级子节点内容这个我觉得没有必要了,直接一步到位吧)。 类似roam的:block/children属性的字段也可以加上,不过我还没找到它的实际应用场景。

但是只改一个数据库肯定是远远不够的,只改动数据库那就是自废武功了,原先很容易实现的查询现在变得麻烦很多,配套的需要增强sql功能,类似roam/logseq封装出query一样封装出一个新的sql语法,再进一步可以封装出可视化sql。 (我还没想出来在这种数据库设计下怎么用sql实现类似{{[[query]]: {and: [[foo1]] [[foo2]]}}}这种多关键字查询且关键字在不同层级上的情况,理论上可以,不过我sql功力有限,目前还不知道怎么实现以及实现复杂度如何)

但是改动sql的工作量肯定很大,所以说当下思源的技术方案可以说就是用空间换时间,这个时间包括查询时间(现有的思源数据库某种程度上相当于预先存储查询所需的content)和开发时间。

初心

回到D大当时提这个改进的初心,我想其中一个应该是从用户角度考虑为了提高新手的用户体验,但是,“列表块和列表项块上的 markdown 和 content 字段上仅存储第一个块级子节点内容”这个技术方案并没有提高新手的用户体验,因为用户想要真正解决“一炮三响”,还必须理解列表块、列表项块、段落块然后在sql中筛选,而估计有九成用户并不完全清楚这些块的准确含义,而且他们也没有动机去理解这些内容,理想情况思源也应当能让尚未准确理解思源块类型的用户轻松使用sql,而如果用户理解了思源的块类型,这是一个用户门槛,那么他理解去重的逻辑也很容易。

所以,想要提高用户体验,正确的路子应该是可视化sql或者像roam那样封装出query,改数据库并不能直接提高新手的用户体验。

D大提到“比如想搜索同时包含分散在列表项上的某些关键字的父级列表就比较困难,但实现复杂度应该低于之前去重子级的复杂度”,我认为恰好反过来,去重的复杂度低于搜索多关键字的复杂度,这复杂度不仅是针对用户的还是针对开发者的,而且我还可以选择不去重,但改进数据库后用户必须面对多重关键字搜索难度上升的问题,所以需要配合sql的改进才算完整的改进方案。

我觉得D大提这个改进还可能是从技术角度考虑觉得原先的技术方案不太优雅,因为数据库中有很多冗余数据。我认为这是一个用空间换时间的技术方案,没什么不好的。改动之后的技术方案肯定会更加优雅,有时间肯定还是要改进的,但要配合sql改进才完整,工作量应该不小,我觉得应该等到D大准备搞可视化sql之类的sql改进功能时再考虑这个问题。(顺带附上roam的可视化query插件链接,可能有参考价值:https://github.com/dvargas92495/roamjs-query-builder

@lengyu-lys
Copy link

roam/logseq 中的数据库

roam的数据库中,每个块的:block/parents属性存储了其父级id列表,即父块、祖父块、曾祖父块……的id,而思源中只存储了一个父块的id。

有了这个父级id列表后,在{{[[query]]: {and: [[foo1]] [[foo2]]}}}时,对于某个块,除了看自己的内容中有没有满足要求,还会同时看所有的父级id所对应的内容,可以等价于说某个block的content是自己的content加上所有父级块的content,但这个content是在query的过程中实时计算的,而思源可以理解成是预先存储这些content的(当然还是有不小区别的,roam中看某个块的所有父级,思源是存储某个块的所有子级,不过本质上差不多),某种程度上思源现有的技术方案是用空间换时间。只存储一个父块的id在理论上也可以搜,但在时间效率和实现复杂度上会差很多。(这段内容是我根据roam的数据库结构猜测的roam的query的实现方案,目前没找到相关资料,有可能roam在实现时和我说的有差异)

此外,roam的:block/children属性包含了一个块的所有一级子块的id列表,二级子块往下深度的都不包含在内,通过这个属性可以自顶向下地遍历,由roam的query无法查询到同级别的多关键词可推导出,roam的query在实现时没有用到:block/children属性,而是用到上面的:block/parents属性。

logseq中,每个块的:block/parent属性中只存储一个父块的id,但:block/path-refs属性存储了自身以及其所有父块的内容中所引用的页面id,例如{[[foo1]]的id,[[foo2]]的id}这样,存储这个之后,在{{[[query]]: {and: [[foo1]] [[foo2]]}}}中就可以直接通过这个属性进行查询。但这个设计相比roam的就局限很多,在query时只能以[[foo1]]这种页面引用为关键词,不能使用普通文本关键词。

logseq 中 query 的具体实现

我目前没有找到roam的query的实现方案,logseq的query原理有一些资料我研究了下,这里简单介绍下。

例如{{[[query]]: {and: [[foo1]] [[foo2]]}}}在logseq中会先转化为:

{:query
 [:find
  (pull
   ?b
   [:db/id
    :block/uuid
    :block/parent
    :block/left
    :block/collapsed?
    :block/format
    :block/refs
    :block/_refs
    :block/path-refs
    :block/tags
    :block/content
    :block/marker
    :block/priority
    :block/properties
    :block/pre-block?
    :block/scheduled
    :block/deadline
    :block/repeated?
    :block/created-at
    :block/updated-at
    :block/file
    :block/heading-level
    {:block/page
     [:db/id :block/name :block/original-name :block/journal-day]}
    {:block/_parent ...}])
  :in
  $
  %
  :where
  (page-ref ?b "foo1")
  (page-ref ?b "foo2")],
 :query-string "(and [[foo1]] [[foo2]])",
 :rules
 [[(page-ref ?b ?page-name)
   [?b :block/path-refs [:block/name ?page-name]]]]}

这一段的核心内容就是最后面对:block/path-refs属性进行匹配。

思源中的一种解决方案

一种解决方案是,类似roam,在数据库中加入每个块的父级id列表(不需要加入depth,这个列表本身其实也包含了depth信息),然后列表项块和列表块等容器块的content可以直接留空(存储第一个块级子节点内容这个我觉得没有必要了,直接一步到位吧)。 类似roam的:block/children属性的字段也可以加上,不过我还没找到它的实际应用场景。

但是只改一个数据库肯定是远远不够的,只改动数据库那就是自废武功了,原先很容易实现的查询现在变得麻烦很多,配套的需要增强sql功能,类似roam/logseq封装出query一样封装出一个新的sql语法,再进一步可以封装出可视化sql。 (我还没想出来在这种数据库设计下怎么用sql实现类似{{[[query]]: {and: [[foo1]] [[foo2]]}}}这种多关键字查询且关键字在不同层级上的情况,理论上可以,不过我sql功力有限,目前还不知道怎么实现以及实现复杂度如何)

但是改动sql的工作量肯定很大,所以说当下思源的技术方案可以说就是用空间换时间,这个时间包括查询时间(现有的思源数据库某种程度上相当于预先存储查询所需的content)和开发时间。

初心

回到D大当时提这个改进的初心,我想其中一个应该是从用户角度考虑为了提高新手的用户体验,但是,“列表块和列表项块上的 markdown 和 content 字段上仅存储第一个块级子节点内容”这个技术方案并没有提高新手的用户体验,因为用户想要真正解决“一炮三响”,还必须理解列表块、列表项块、段落块然后在sql中筛选,而估计有九成用户并不完全清楚这些块的准确含义,而且他们也没有动机去理解这些内容,理想情况思源也应当能让尚未准确理解思源块类型的用户轻松使用sql,而如果用户理解了思源的块类型,这是一个用户门槛,那么他理解去重的逻辑也很容易。

所以,想要提高用户体验,正确的路子应该是可视化sql或者像roam那样封装出query,改数据库并不能直接提高新手的用户体验。

D大提到“比如想搜索同时包含分散在列表项上的某些关键字的父级列表就比较困难,但实现复杂度应该低于之前去重子级的复杂度”,我认为恰好反过来,去重的复杂度低于搜索多关键字的复杂度,这复杂度不仅是针对用户的还是针对开发者的,而且我还可以选择不去重,但改进数据库后用户必须面对多重关键字搜索难度上升的问题,所以需要配合sql的改进才算完整的改进方案。

我觉得D大提这个改进还可能是从技术角度考虑觉得原先的技术方案不太优雅,因为数据库中有很多冗余数据。我认为这是一个用空间换时间的技术方案,没什么不好的。改动之后的技术方案肯定会更加优雅,有时间肯定还是要改进的,但要配合sql改进才完整,工作量应该不小,我觉得应该等到D大准备搞可视化sql之类的sql改进功能时再考虑这个问题。(顺带附上roam的可视化query插件链接,可能有参考价值:https://github.com/dvargas92495/roamjs-query-builder

很棒的分析,我觉得其实以 siyuan 的存储方式也可以实现类似的搜索,只不过是要通过 parent_id 逐步递归,效率会低很多。我也赞同你的观点:sql 语句不适合作为面向用户的搜索语言, 因为 sql 对于普通用户来说比较复杂非常难写。类似于 roam 那样的搜索语法确实更加简洁高效,不过要支持类似的语法的话成本就很高了,这个估计 D大 会需要更慎重的考虑。其实我觉得与其改变数据库,不如专门做一个搜索用的索引库,这样将元数据存储和搜索的索引分开维护,这样对现有系统影响更小,同时可以采用更加灵活的搜索方案或组件(比如 elastic search等)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants