一尘不染

在IndexedDB中,是否可以进行排序的复合查询?

html

假设表格具有名称,ID,年龄,性别,学历等。ID是关键,并且该表也按名称,年龄和性别进行索引。我需要所有25岁以上的男学生,按其姓名排序。

这在mySQL中很简单:

    SELECT * FROM table WHERE age > 25 AND sex = "M" ORDER BY name

IndexDB允许创建索引并根据该索引对查询进行排序。但是它不允许多个查询,例如年龄和性别。我发现了一个名为queryIndexedDB的小型库,该库允许复合查询,但不提供排序结果。

那么,在使用IndexedDB时,有没有办法进行排序的复合查询?


阅读 785

收藏
2020-05-10

共1个答案

一尘不染

在此答案中使用的术语“ 复合查询” 指的是在其WHERE子句中涉及多个条件的SQLSELECT语句。尽管indexedDB规范中未提及此类查询,但是您可以通过使用包含由属性名数组组成的 键路径 的索引来创建复合查询的行为。

这与创建索引时使用多条目标志完全无关。多条目标志调整indexedDB如何在单个数组属性上创建索引。我们正在索引对象属性的数组,而不是对象的单个数组属性的值。

创建索引

在此示例中,“名称”,“性别”和“年龄”对应于存储在学生对象库中的学生对象的属性名称。

// An example student object in the students store
var foo = {
  'name': 'bar',
  'age': 15,
  'gender': 'M'
};

function myOnUpgradeNeeded(event) {
  var db = event.target.result;
  var students = db.createObjectStore('students');
  var name = 'males25';
  var keyPath = ['name', 'gender', 'age'];
  students.createIndex(name, keyPath);
}

在索引上打开光标

然后可以在索引上打开游标:

var students = transaction.objectStore('students');
var index = students.index('males25');
var lowerBound = ['AAAAA','male',26];
var upperBound = ['ZZZZZ','male',200];
var range = IDBKeyRange.bound(lowerBound, upperBound);
var request = index.openCursor(range);

但是 ,出于我要解释的原因,这并不总是有效。

撇开:使用范围参数来打开光标或获取是可选的。如果您未指定范围,则将IDBKeyRange.only为您隐式使用。换句话说,您只需要IDBKeyRange用于有界游标。

基本指数概念

索引就像对象存储,但不能直接可变。而是在引用的对象存储上使用CRUD(创建读取更新删除)操作,然后indexedDB自动将更新级联到索引。

了解排序是了解索引的基础。索引基本上只是对象的特殊排序集合。从技术上讲,它也是经过过滤的,但我会在稍后讨论。通常,当您在索引上打开游标时,您将根据索引的顺序进行迭代。该顺序可能并且可能与引用的对象存储中的对象顺序不同。该顺序很重要,因为这样可以使迭代更有效,并允许仅在特定于索引的顺序的上下文中有意义的自定义下限和上限。

发生存储更改时,索引中的对象将进行排序。将对象添加到存储中时,该对象将被添加到索引中的适当位置。排序归结为类似于Array.prototype.sort的比较函数,该函数比较两个项目并返回一个对象是否小于另一个对象,大于另一个对象或相等。因此,通过深入研究比较函数的更多细节,我们可以更好地理解排序行为。

按字典顺序比较字符串

例如,这意味着“ Z”小于“ a”, 字符串 “ 10”大于 字符串 “ 020”。

使用规范定义的顺序比较不同类型的值

例如,规范指定字符串类型值如何出现在日期类型值之前或之后。值包含的内容与类型无关紧要。

IndexedDB不会为您强制类型。您可以在这里射杀自己。通常,您永远都不想比较不同的类型。

具有未定义属性的对象不会出现在其键路径由一个或多个这些属性组成的索引中

如前所述,索引可能并不总是包含引用对象存储中的所有对象。当您将对象放入对象存储中时,如果该对象缺少索引所基于的属性的值,则该对象将不会出现在索引中。例如,如果我们有一个不知道年龄的学生,并将其插入学生商店,则该特定学生将不会出现在males25索引中。

当您想知道为什么在索引上迭代光标时为什么没有出现对象时,请记住这一点。

还要注意null和空字符串之间的细微差别。空字符串 不是
缺少的值。属性值为空字符串的对象仍可以出现在基于该属性的索引中,但是如果该属性存在但未定义或不存在,则该对象不会出现在索引中。而且,如果它不在索引中,则在将游标遍历索引时将看不到它。

创建IDBKeyRange时,必须指定数组键路径的每个属性。

当创建在该范围内打开游标的下限或上限时,必须为数组键路径中的每个属性指定一个有效值。否则,您将得到某种类型的Javascript错误(因浏览器而异)。例如,您不能创建一个范围,例如,IDBKeyRange.only([undefined, 'male', 25])因为name属性是未定义的。

令人困惑的是,如果您指定了错误的值 类型 (例如IDBKeyRange.only(['male', 25]),其中未定义名称),则在上述意义上不会出现错误,但会得到毫无意义的结果。

该一般规则有一个例外:您可以比较不同长度的数组。因此,从技术上讲,您可以从该范围中忽略属性,但前提是您要从数组 末尾开始
,并且可以适当地截断该数组。例如,您可以使用IDBKeyRange.only(['josh','male'])

短路数组排序

索引资料规范提供了一种用于分选阵列的显式方法:

如下将Array类型的值与Array类型的其他值进行比较:

  1. 设A为第一个数组值,B为第二个数组值。
  2. 令长度为A的长度和B的长度中的较小者。
  3. 让我为0。
  4. 如果A的第i个值小于B的第i个值,则A小于B。跳过其余步骤。
  5. 如果A的第i个值大于B的第i个值,则A大于B。跳过其余步骤。
  6. 将i加1。
  7. 如果i不等于长度,请返回到步骤4。否则,请继续执行下一步。
  8. 如果A的长度小于B的长度,则A小于B。如果A的长度大于B的长度,则A大于B。否则A和B相等。

捕获在步骤4和5中: 跳过其余步骤
。这基本上意味着,如果我们比较两个数组的顺序,例如[1,’Z’]和[0,’A’],则该方法仅考虑第一个元素,因为在那一点上1大于0。由于短路评估(规范中的第4步和第5步),因此永远不会检查Z
vsA。

因此,先前的示例将无法正常工作。实际上,它的工作方式类似于以下内容:

WHERE (students.name >= 'AAAAA' && students.name <= 'ZZZZZ') || 
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' && 
students.gender >= 'male' && students.gender <= 'male') || 
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' && 
students.gender >= 'male' && students.gender <= 'male' && 
students.age >= 26 && students.age <= 200)

如果您有在SQL或一般编程中使用此类布尔子句的经验,那么您已经应该认识到不一定要涉及全部条件。这意味着您将无法获得所需的对象列表,这就是为什么您无法真正获得与SQL复合查询相同的行为的原因。

处理短路

在当前的实现中,您无法轻易避免这种短路行为。在最坏的情况下,您必须将所有对象从存储/索引加载到内存中,然后使用自己的自定义排序功能对集合进行排序。

有一些方法可以最大程度地减少或避免某些短路问题:

例如,如果您正在使用index.get(array)或index.openCursor(array),则无需担心短路问题。有一个完整的比赛或没有一个完整的比赛。在这种情况下,比较功能仅评估两个值是否相同,而不评估一个值是否大于或小于另一个。

其他要考虑的技术:

  • 从最窄到最宽重新排列键路径的元素。基本上可以在一定范围内提供较早的钳位,以切断一些不必要的短路结果。
  • 将包装的对象存储在使用特殊自定义属性的商店中,以便可以使用非数组键路径(非复合索引)对其进行排序,或者可以使用不受短路影响的复合索引行为。
  • 使用多个索引。这导致指数爆炸问题。请注意,此链接是关于另一个no-sql数据库的,但是相同的概念和解释适用于indexedDB,并且该链接是一个合理的(冗长而复杂的)解释,因此在此不再赘述。
  • indexedDB的创建者之一(规范和Chrome实现)最近建议使用cursor.continue

使用indexedDB.cmp测试

该CMP功能提供了一种快速简单的方法来研究如何排序的作品。例如:

var a = ['Hello',1];
var b = ['World',2];
alert(indexedDB.cmp(a,b));

indexedDB.cmp函数的一个不错的特性是它的签名与Array.prototype.filter和Array.prototype.sort的函数参数相同。您可以轻松地从控制台测试值,而无需处理连接/方案/索引等。此外,indexedDB.cmp是同步的,因此您的测试代码不需要涉及异步回调/承诺。

2020-05-10