faunadb中如何进行多条件查询?

2023-12-26

我尝试提高对 FaunaDB 的理解。

我有一个包含以下记录的集合:

{
  "ref": Ref(Collection("regions"), "261442015390073344"),
  "ts": 1587576285055000,
  "data": {
    "name": "italy",
    "attributes": {
      "amenities": {
        "camping": 1,
        "swimming": 7,
        "hiking": 3,
        "culture": 7,
        "nightlife": 10,
        "budget": 6
      }
    }
  }
}

我想通过不同的属性以灵活的方式查询,例如:

  • 数据.属性.设施.露营 > 5
  • data.attributes.amenities.camping > 5 AND data.attributes.amenities.hiking > 6
  • data.attributes.amenities.camping 6 AND 远足 > 5 AND ...

我创建了一个包含所有属性的索引,但我不知道如何在包含多个术语的索引中进行更大等于过滤。

我的后备方案是为每个属性创建一个索引,并使用 Intersection 来获取我想要检查的所有子查询中的记录,但这感觉有些错误:

查询:预算 >= 6 AND 露营 >=8 将是:

Index:
{
  name: "all_regions_by_all_attributes",
  unique: false,
  serialized: true,
  source: "regions",
  terms: [],
  values: [
    {
      field: ["data", "attributes", "amenities", "culture"]
    },
    {
      field: ["data", "attributes", "amenities", "hiking"]
    },
    {
      field: ["data", "attributes", "amenities", "swimming"]
    },
    {
      field: ["data", "attributes", "amenities", "budget"]
    },
    {
      field: ["data", "attributes", "amenities", "nightlife"]
    },
    {
      field: ["data", "attributes", "amenities", "camping"]
    },
    {
      field: ["ref"]
    }
  ]
}

Query:

Map(
  Paginate(
    Intersection(
      Range(Match(Index("all_regions_by_all_attributes")), [0, 0, 0, 6, 0, 8], [10, 10, 10, 10, 10, 10]),
    )

  ),
  Lambda(
    ["culture", "hiking", "swimming", "budget", "nightlife", "camping", "ref"],
    Get(Var("ref"))
  )
)

这种方法有以下缺点:

  • 它不像预期的那样工作,例如,如果第一个(文化)属性在此范围内,但第二个(徒步旅行)不在这个范围内,那么我仍然会得到返回值
  • 由于我需要为每个结果遵循参考,因此它会导致大量读取。

是否可以将所有值存储在包含所有数据的这种索引中?我知道我可以向索引添加更多值并访问它们。但这意味着一旦我们向实体添加更多字段,我就必须创建一个新索引。但也许这是一个常见的事情。

提前致谢


谢谢你的提问。 Ben 已经写出了一个完整的示例,展示了您可以做什么,我将根据他的建议并尝试进一步澄清。

FaunaDB 的 FQL 非常强大,这意味着有多种方法可以做到这一点,但如此强大的功能带来的学习曲线很小,所以我很乐意提供帮助:)。花了一段时间来回答这个问题的原因是,如此详尽的答案实际上值得一篇完整的博客文章。好吧,我从来没有在 Stack Overflow 上写过博客文章,凡事都有第一次!

有以下三种方法可以做到“复合范围查询”但是有一种方法对于您的用例来说是最有效的,我们会看到第一种方法实际上并不完全是您所需要的。剧透,我们在这里描述的第三个选项正是您所需要的。

准备 - 让我们像 Ben 一样输入一些数据

我将把它保存在一个集合中以使其更简单,并且在这里使用 JavaScript 风格的动物群查询语言。尽管这与您的第二个地图/获取问题相关,但有充分的理由将第二个集合中的数据分开(请参阅此答案的末尾)

创建集合

 CreateCollection({ name: 'place' })

输入一些数据

    Do(
      Select(
        ['ref'],
        Create(Collection('place'), {
          data: {
            name: 'mullion',
            focus: 'team-building',
            camping: 1,
            swimming: 7,
            hiking: 3,
            culture: 7,
            nightlife: 10,
            budget: 6
          }
        })
      ),
      Select(
        ['ref'],
        Create(Collection('place'), {
          data: {
            name: 'church covet',
            focus: 'private',
            camping: 1,
            swimming: 7,
            hiking: 9,
            culture: 7,
            nightlife: 10,
            budget: 6
          }
        })
      ),
      Select(
        ['ref'],
        Create(Collection('place'), {
          data: {
            name: 'the great outdoors',
            focus: 'private',
            camping: 5,
            swimming: 3,
            hiking: 2,
            culture: 1,
            nightlife: 9,
            budget: 3
          }
        })
      )
    )

选项 1:具有多个值的复合索引

我们可以在索引中放入与值一样多的术语并使用Match and Range来查询那些。然而!如果您使用多个值,范围可能会给您带来与预期不同的结果。 Range 准确地为您提供了索引的功能,并且索引按词法对值进行排序。如果我们看一下这个例子Range https://docs.fauna.com/fauna/current/api/fql/functions/range在文档中,我们看到一个示例,我们可以扩展多个值。

想象一下,我们有一个包含两个值的索引,我们这样写:

    Range(Match(Index('people_by_age_first')), [80, 'Leslie'], [92, 'Marvin'])

那么结果将是您在左侧看到的而不是您在右侧看到的。这是一种非常可扩展的行为,并且在没有底层索引开销的情况下公开了原始功能,但这并不是您正在寻找的!

那么让我们转向另一个解决方案!

选项 2:首先选择范围,然后选择筛选

另一个相当灵活的解决方案是使用 Range,然后使用 Filter。然而,如果您使用过滤器过滤掉很多内容,那么这并不是一个好主意,因为您的页面会变得更加空。想象一下,在“范围”之后,页面中有 10 个项目,并使用过滤器,那么最终会得到包含 2、5、4 个元素的页面,具体取决于过滤掉的内容。这是一个好主意,但是如果这些属性之一具有如此高的基数,它将过滤掉大多数实体。例如。假设所有内容都带有时间戳,您希望首先获取日期范围,然后继续过滤只会消除结果集中一小部分的内容。我相信在您的情况下,所有这些值都相当相等,因此第三个解决方案(见下文)将是最适合您的。

在这种情况下,我们可以将所有值放入其中,以便它们allget 返回,避免了 Get。例如,假设“露营”是我们最重要的过滤器。

    CreateIndex({
      name: 'all_camping_first',
      source: Collection('place'),
      values: [
        { field: ['data', 'camping'] },
        // and the rest will not be used for filter
        // but we want to return them to avoid Map/Get
        { field: ['data', 'swimming'] },
        { field: ['data', 'hiking'] },
        { field: ['data', 'culture'] },
        { field: ['data', 'nightlife'] },
        { field: ['data', 'budget'] },
        { field: ['data', 'name'] },
        { field: ['data', 'focus'] },
      ]
    })

您现在可以编写一个查询,仅根据露营值获取范围:

    Paginate(Range(Match('all_camping_first'), [1], [3]))

它应该返回两个元素(第三个元素有露营 === 5) 现在想象一下,我们想要过滤这些内容,并将页面设置得较小以避免不必要的工作

    Filter(
      Paginate(Range(Match('all_camping_first'), [1], [3]), { size: 2 }),
      Lambda(
        ['camping', 'swimming', 'hiking', 'culture', 'nightlife', 'budget', 'name', 'focus'],
        And(GTE(Var('hiking'), 0), GTE(7, Var('hiking')))
      )
    )

由于我想清楚地了解每种方法的优点和缺点,因此让我们通过添加另一个具有与我们的查询匹配的属性的过滤器来准确展示过滤器的工作原理。

    Create(Collection('place'), {
      data: {
        name: 'the safari',
        focus: 'team-building',
        camping: 1,
        swimming: 9,
        hiking: 2,
        culture: 4,
        nightlife: 3,
        budget: 10
      }
    })

运行相同的查询:

    Filter(
      Paginate(Range(Match('all_camping_first'), [1], [3]), { size: 2 }),
      Lambda(
        ['camping', 'swimming', 'hiking', 'culture', 'nightlife', 'budget', 'name', 'focus'],
        And(GTE(Var('hiking'), 0), GTE(7, Var('hiking')))
      )
    )

现在仍然只返回一个值但为您提供了一个指向下一页的“之后”光标。您可能会想:“嗯?我的页面大小是 2?”。那是因为 Filter 起作用了after分页和您的页面最初有两​​个实体,其中一个被过滤掉。因此,您剩下一个值为 1 的页面和一个指向下一页的指针。


{
  "after": [
    ... 
  ],
  "data": [
    [
      1,
      7,
      3,
      7,
      10,
      6,
      "mullion",
      "team-building"
    ]
  ]

您还可以选择直接在 SetRef 上过滤,然后才分页。在这种情况下,页面的大小将包含所需的大小。但是,请记住,这是对从 Range 返回的元素数量进行 O(n) 操作。 Range 使用索引,但从使用 Filter 的那一刻起,它将循环遍历每个元素。

选项 3:一个值的索引 + 交集!

这是适合您的用例的最佳解决方案,但它需要更多的理解和中间索引。

当我们查看文档示例时路口 https://docs.fauna.com/fauna/current/api/fql/functions/intersection我们看到这个例子:

    Paginate(
       Intersection(
          Match(q.Index('spells_by_element'), 'fire'),
          Match(q.Index('spells_by_element'), 'water'),
       )
    ) 

这是有效的,因为它是相同索引的两倍,这意味着**结果是相似的值**(本例中为引用)。 假设我们添加了一些索引。

    CreateIndex({
      name: 'by_camping',
      source: Collection('place'),
      values: [
        { field: ['data', 'camping']}, {field:  ['ref']}
      ]
    })

    CreateIndex({
      name: 'by_swimming',
      source: Collection('place'),
      values: [
        { field: ['data', 'swimming']}, {field:  ['ref']} 
      ]
    })

    CreateIndex({
      name: 'by_hiking',
      source: Collection('place'),
      values: [
        { field: ['data', 'hiking']}, {field:  ['ref']} 
      ]
    })

我们现在可以与它们相交但它不会给我们正确的结果。例如...让我们这样称呼:

    Paginate(
      Intersection(
        Range(Match(Index("by_camping")), [3], []),
        Range(Match(Index("by_swimming")), [3], [])
      )
    )

结果是空的。虽然我们有一个游泳 3 和露营 5。 这正是问题所在。如果游泳和露营的值相同,我们就会得到结果。所以重要的是要注意 Intersection 与values,因此包括露营/游泳值以及参考值。这意味着我们必须删除该值,因为我们只需要引用。这样做的方法before分页是通过连接进行的,本质上我们将与另一个索引连接,该索引将只是..返回引用(不指定值默认仅返回引用)

CreateIndex({
  name: 'ref_by_ref',
  source: Collection('place'),
  terms: [{field:  ['ref']}]
})

该连接如下所示

    Paginate(Join(
      Range(Match(Index('by_camping')), [4], [9]),
      Lambda(['value', 'ref'], Match(Index('ref_by_ref'), Var('ref'))
    )))

在这里,我们仅获取 Match(Index('by_camping')) 的结果,并通过连接仅返回引用的索引来删除该值。现在让我们将其结合起来并执行 AND 类型的范围查询;)

    Paginate(Intersection(
      Join(
        Range(Match(Index('by_camping')), [1], [3]),
        Lambda(['value', 'ref'], Match(Index('ref_by_ref'), Var('ref'))
      )),
      Join(
        Range(Match(Index('by_hiking')), [0], [7]),
        Lambda(['value', 'ref'], Match(Index('ref_by_ref'), Var('ref'))
      ))
    ))

结果是两个值,并且都在同一页面中!

请注意,您可以轻松地extend or composeFQL 通过仅​​使用本机语言(在本例中为 JS)来使其看起来更好(注意我没有测试这段代码)

    const DropAllButRef = function(RangeMatch) {
      return Join(
        RangeMatch,
        Lambda(['value', 'ref'], Match(Index('ref_by_ref'), Var('ref'))
      ))
    }
    
    Paginate(Intersection(
      DropAllButRef (Range(Match(Index('by_camping')), [1], [3])),
      DropAllButRef (Range(Match(Index('by_hiking')), [0], [7]))
    ))

最后一个扩展,它仅返回索引,因此您需要映射 get。如果你真的想的话,当然有办法解决这个问题..只需使用另一个索引:)

    const index = CreateIndex({
      name: 'all_values_by_ref',
      source: Collection('place'),
      values: [
        { field: ['data', 'camping'] },
        { field: ['data', 'swimming'] },
        { field: ['data', 'hiking'] },
        { field: ['data', 'culture'] },
        { field: ['data', 'nightlife'] },
        { field: ['data', 'budget'] },
        { field: ['data', 'name'] },
        { field: ['data', 'focus'] }
      ],
      terms: [
        { field: ['ref'] }
      ]
    }) 

现在您有了范围查询,无需地图/获取即可获取所有内容:

  Paginate(
    Intersection(
      Join(
        Range(Match(Index('by_camping')), [1], [3]),
        Lambda(['value', 'ref'], Match(Index('all_values_by_ref'), Var('ref'))
      )),
      Join(
        Range(Match(Index('by_hiking')), [0], [7]),
        Lambda(['value', 'ref'], Match(Index('all_values_by_ref'), Var('ref'))
      ))
    )
  )

通过这种连接方法,您甚至可以对不同的集合进行范围索引,只要您在相交之前将它们连接到相同的引用即可!很酷吧?

我可以在索引中存储更多值吗?

是的,你可以,FaunaDB 中的索引是视图,所以我们称它们为独立视图。这是一种权衡,本质上是用计算来交换存储。通过创建包含许多值的视图,您可以非常快速地访问数据的某个子集。但还有另一个权衡,那就是灵活性。你可以not只需添加元素,因为这需要您重写整个索引。在这种情况下,如果您有大量数据(是的,这很常见),您将必须创建一个新索引并等待它构建,并确保您执行的查询(查看映射过滤器中的 lambda 参数)匹配你的新索引。之后您可以随时删除其他索引。仅使用 Map/Get 会更加灵活,数据库中的所有内容都是一种权衡,而 FaunaDB 为您提供了两种选择:)。我建议从数据模型固定并且您在应用程序中看到要优化的特定部分开始就使用这种方法。

避免 MapGet

关于 Map/Get 的第二个问题需要一些解释。如果您想使用 Join 来获取实际值,那么将要搜索的值与位置分开(就像 Ben 所做的那样)是一个好主意。places更有效率。这不需要 Map Get,因此读取的成本要少得多,但请注意,Join 更像是一种遍历(它将用它连接到的目标引用替换当前引用),因此如果您需要值和实际位置查询结束时一个对象中的数据比您需要 Map/Get 的数据要多。从这个角度来看,索引在读取方面非常便宜,你可以走得很远,但对于某些操作来说,没有办法绕过 Map/Get,Get 仍然只有 1 次读取。考虑到你每天免费获得 100 000 个,这仍然不贵:)。您可以将页面保持相对较小(分页中的大小参数),以确保您不会进行不必要的获取,除非您的用户或应用程序需要更多页面。 对于那些还不知道这一点的阅读本文的人:

  • 1 个索引页 === 1 次阅读
  • 1 获取 === 1 阅读

最后的笔记

我们可以而且将来会让这变得更容易。但是,请注意,您正在使用可扩展的分布式数据库,并且通常这些事情在其他解决方案中甚至是不可能的,或者效率非常低。 FaunaDB 为您提供了非常强大的结构和对索引如何工作的原始访问,并为您提供了许多选项。它不会试图在幕后为您聪明,因为如果我们出错,这可能会导致非常低效的查询(这在可扩展的即用即付系统中将是一个无赖)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

faunadb中如何进行多条件查询? 的相关文章

  • 我们应该使用 Nexus 还是 Artifactory 来构建 Maven 存储库? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我们使用 Maven 进行大型构建过程 gt 100 个模块 我们一直将外部依赖项存储在源代码管理中 并使用它来更新本地存储库 然而 我们已经准
  • 如何为初学者设置 SSH 配置文件 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 首先 我对 SSH 相当陌生 From 这个问题 https stackoverflow com questions 56285972 c
  • 坏元素的映射

    我正在实施k means我想创建新的质心 但映射遗漏了一个元素 然而 当K值较小 例如 15 效果会很好 基于此code http www cs berkeley edu rxin ampcamp ecnu machine learning
  • 返回 n 个布尔值的所有组合的函数?

    我正在尝试实现一个函数 该函数接受数字 n 并返回布尔值列表的列表 其中包含 n 布尔值的所有可能组合 例如的输出 make bools 3 应该看起来像 false false false false false true false t
  • 生成更新脚本 MySQl 表

    我的 MySql 表有 250 个字段 我需要进行更新 手动编写整个更新可能需要几个小时 In SQL服务器 我可以选择 生成更新脚本 我正在寻找类似的选项 我尝试了 Phpmyadmin 和 Navicat 但没有找到这样的选项 Use
  • 实体框架自定义 SQL 查询返回泛型类型

    我正在尝试制作一个通用报告系统 以便我可以动态地将报告添加到我的程序中 而不是每次必须添加报告时都发布新版本的程序 目前 我已经部分工作了 我的自定义报告 SQL 与报告名称一起存储在表中 报表名称用于用户单击的按钮 当他们单击按钮时 我希
  • 找不到aspectj-maven-plugin的依赖项

    我在使用aspectj maven plugin 时遇到CTW 方面的问题 我收到以下错误 执行条目突出显示 Multiple annotations found at this line Execution default of goal
  • 如何将UIScrollview与UIPagecontrol结合起来显示不同的视图?

    我已经搜索并搜索了这方面的教程 但没有一个是我正在寻找的 我尝试过苹果的示例 但它只是颜色 我不知道如何使其成为视图 我所寻找的只是一个可以在显示页面控件的同时进行分页的屏幕 每次滚动视图页面时 我希望它显示带有按钮的完全不同的视图 很像
  • anaconda python 导入 theano 时出错

    我对 python 很陌生 当然我对 Theano 也很陌生 我试图在 Windows 下与 anaconda python 一起使用它 我已经安装了所有强制要求 除了 CUDA 因为在这台笔记本电脑上我没有 NVIDIA GPU 我安装了
  • NSString 常量可以弱链接吗?

    NSString 常量可以弱链接吗 AVCaptureSessionPresetiFrame960x540在 iOS 5 之前没有定义 我避免实际引用它 它在 gdb 中加载得很好 但是当我加载 ipa 时 它似乎在调用 main 之前在
  • 合并列表中的数据框[重复]

    这个问题在这里已经有答案了 这是早期的一个分支post https stackoverflow com questions 29981195 find top 10 and 10 20 decile entries from datafra
  • 如何在变异后将 t.test() 应用于多对列

    这个问题与此相关跨多列进行 T 检验或整理数据 https stackoverflow com questions 69951627 t tests across multiple columns or tidy the data 6995
  • MySQL GROUP BY NULL 和 EMPTY

    在 MySQL 查询中我正在执行GROUP BY带有文本字段 由于原始数据的性质 某些行包含该字段的空字符串 而其他行则为 truenull 分组时 如何将空字符串和 null 分组在一起 将两者视为null 这可以通过 SELECT CA
  • 从“控制数组”创建控制?

    我有一系列图片框 如下所示 Dim pieces 500 As PictureBox pieces 1 New PictureBox With pieces 1 CreateControl Visible True BackColor Co
  • 具有相同 CIDR 块的多个 VPC 和子网

    我意识到我可以使用相同的 CIDR 块创建多个 AWS VPC 和子网 我不确定其背后的原理是什么以及它是如何实现的 AWS VPC 可以存在于私有 RFC 1918 https www rfc editor org rfc rfc1918
  • 使用嵌入式 Jetty 对 servlet 进行单元测试

    我们如何使用嵌入式 Jetty 服务器对 servlet 进行单元测试 比如下面的servlet方法如何测试 protected void doGet HttpServletRequest request HttpServletRespon
  • .NET:如何跨平台设置“扩展文件属性”?

    我需要阅读并修改扩展文件属性 https en wikipedia org wiki Extended file attributes在必须在 Windows 和 Linux 上运行的 NET 5 控制台应用程序中 假设文件系统支持这些属性
  • Bootstrap 3.0 - 包含固定列大小的流体网格

    我正在学习如何使用 Bootstrap 目前 我正在艰难地进行布局 虽然 Bootstrap 非常酷 但我看到的一切似乎都过时了 对于我的生活 我有一个我认为是我无法弄清楚的基本布局 我的布局如下所示 240px 160px All Rem

随机推荐