【问题标题】:Autocomplete with Firebase使用 Firebase 自动完成
【发布时间】:2018-01-10 13:05:33
【问题描述】:

如何使用 Firebase 进行基本的自动完成/文本预览?

例如,假设有一个由 Firebase 支持的博客,博主可以在其中使用标签标记帖子。当博主正在标记新帖子时,如果他们能看到与他们输入的前几次击键相匹配的所有当前存在的标签,将会很有帮助。因此,如果“博客”、“黑色”、“炽热的马鞍”和“牛头犬”是标签,如果用户键入“bl”,他们会得到前三个,但不会得到“牛头犬”。

我最初的想法是,我们可以将标签设置为标签的优先级,并使用 startAt,这样我们的查询看起来像:

fb.child('tags').startAt('bl').limitToFirst(5).once('value', function(snap) {
  console.log(snap.val()) 
});

但这也会返回“斗牛犬”作为结果之一(不是世界末日,但也不是最好的)。使用 startAt('bl').endAt('bl') 不会返回任何结果。有没有其他方法可以做到这一点?

(我知道一种选择是我们可以使用搜索服务器,例如 ElasticSearch,用于 -- 参见 https://www.firebase.com/blog/2014-01-02-queries-part-two.html -- 但我希望尽可能多地保留在 Firebase 中。)

编辑

正如加藤建议的那样,这是一个具体的例子。我们有 20,000 名用户,他们的姓名是这样存储的:

/users/$userId/name

通常,用户会按名称查找其他用户。当用户查找他们的好友时,我们想要一个下拉列表来填充名称以搜索者输入的字母开头的用户列表。因此,如果我输入“Ja”,我希望在下拉列表中看到“Jake Heller”、“jake gyllenhaal”、“Jack Donaghy”等。

【问题讨论】:

  • 我们在这里讨论了多少条记录?我们是在实施 Google 搜索还是现实的客户端自动完成?
  • 好问题。对于我们正在考虑进行自动完成的某些事情,有 200 万多条记录。对于其他人,可能只有 100(网站上的不同标签)到 20,000(网站上注册用户的名称)的数量级。
  • 我想我知道你可能会用这个去哪里。我们绝对可以做一个 startAt() 和 limit() 然后在客户端过滤掉错误的结果(比如我的例子中的“斗牛犬”)。我认为对于 FB 中任何大小的对象,只要我们使用 limit(),这应该是高性能的(对吗?)。这不如让 Firebase 为我们处理所有事情好,但仍然是一种选择。
  • 对于这样一个通用集合,以 SO 格式回答这个问题非常困难。如果您提供一个具体的案例来首先解决,我可以帮助您处理细节。您提到的每一个对于“最高性能”来说可能都不同。我们可以排除 200 万+ 绝对是 ElasticSearch/Flashlight 项目。 500k 或更少,我只是抓住它们并过滤客户端。 20k 个名字是什么,400k 也许?在这里,我可能只是按名称的第一个字母进行索引或优先级排序,然后从那里过滤客户端,根据需要进行优化。选择一个,我们可以解决它。
  • 另外,请注意,您可以使用Flashlight 获取 ElasticSearch 结果,并且仍然只与 Firebase 交互,让您保持简单。

标签: autocomplete firebase


【解决方案1】:

我知道这是一个老话题,但它仍然具有相关性。根据上面 Neil 的回答,您可以更轻松地进行以下搜索:

fb.child('tags').startAt(queryString).endAt(queryString + '\uf8ff').limit(5)

Firebase Retrieving Data

上面查询中使用的 \uf8ff 字符是一个非常高的代码点 在 Unicode 范围内。因为它在大多数常规字符之后 Unicode,查询匹配所有以 queryString 开头的值。

【讨论】:

  • 虽然这个示例(和文档链接)专门针对 Web 上的 Firebase,但该技术也适用于 Android。您将执行以下操作:FirebaseDatabase.getInstance().getReference("child").startAt("string").endAt("string" + "\uf8ff").limitToFirst(5).addListenerForSingleValueEvent( [...]);
【解决方案2】:

受 Kato 的 cmets 启发,解决此问题的一种方法是将优先级设置为您要搜索自动完成的字段,并使用 startAt()、limit() 和客户端过滤仅返回你想要的结果。您需要确保优先级和搜索字词是小写的,因为 Firebase 区分大小写。

这是一个粗略的示例,使用我在问题中列出的用户示例来演示这一点:

对于“ja”的搜索,假设所有用户的优先级都设置为用户名的小写版本:

fb.child('users').
  startAt('ja'). // The user-inputted search
  limitToFirst(20).
  once('value', function(snap) {
    for(key in snap.val()){
      if(snap.val()[key].indexOf('ja') === 0) {
        console.log(snap.val()[key];
      }
    }
});

这应该只返回实际以“ja”开头的名称(即使 Firebase 实际上在“ja”之后按字母顺序返回名称)。

我选择使用 limitToFirst(20) 来保持较小的响应大小,因为实际上,自动完成下拉菜单永远不需要超过 20。可能有更好的方法来进行过滤,但这至少应该展示这个概念。

希望这对某人有所帮助! Firebase 的人很可能有更好的答案。

(请注意,这是非常有限的——如果有人搜索姓氏,它不会返回他们正在寻找的内容。因此,“最佳”答案可能是使用像 Kato 的 @ 这样的搜索后端987654321@.)

【讨论】:

  • 我同意你在这里提出的大部分内容。对于这种直接内容搜索,ElasticSearch 是最简单也可能是最干净的答案。随着您的应用程序的成熟,它也会随着您的需求而增长。如果我们仅限于严格的客户端答案,我会像您一样从 startAt() 和 limit() 开始。包含样式搜索可以通过简单地对服务器数据进行分页并手动搜索来实现——对于名称这样的小数据集来说,这是一个缓慢但合理的过程。
  • 这对 10 万条记录有效吗?有人试过这么多数据吗?
【解决方案3】:

令我震惊的是,有一种比客户端过滤或破解 Elastic 更简单、更优雅的方法来实现这一点。

通过将搜索键转换为它的 Unicode 值并将其存储为优先级,您可以通过 startAt() 和 endAt() 将值加一进行搜索。

var start = "ABA";

var pad = "AAAAAAAAAA";
start += pad.substring(0, pad.length - start.length);

var blob = new Blob([start]);

var reader = new FileReader();
reader.onload = function(e) {
    var typedArray = new Uint8Array(e.target.result);
    var array = Array.prototype.slice.call(typedArray);
    var priority = parseInt(array.join(""));
    console.log("Priority of", start, "is:", priority);
}
reader.readAsArrayBuffer(blob);

然后,您可以通过将最后一个 charCode 加一并执行相同的转换来将搜索优先级限制为键“ABB”:

var limit = String.fromCharCode(start.charCodeAt(start.length -1) +1);
limit = start.substring(0, start.length -1) +limit;

“ABA...”到“ABB...”的优先级为:

开始:65666565656565650000

结束:65666665656565650000

简单!

【讨论】:

    【解决方案4】:

    根据 Jake 和 Matt 的回答,sdk 3.1 的更新版本。 '.limit' 不再有效:

    firebaseDb.ref('users')
        .orderByChild('name')
        .startAt(query)
        .endAt(`${query}\uf8ff`)
        .limitToFirst(5)
        .on('child_added', (child) => {
          console.log(
              {
                id: child.key,
                name: child.val().name
              }
          )
        })
    

    【讨论】:

    • 这里的关键是 .endAt(${query}\uf8ff)
    猜你喜欢
    • 1970-01-01
    • 2014-05-31
    • 2018-06-30
    • 1970-01-01
    • 2016-02-27
    • 2014-12-12
    • 2011-06-28
    • 2014-05-07
    • 2017-08-14
    相关资源
    最近更新 更多