在IndexedDB中,有没有办法进行排序复合查询?

jas*_*son 31 sqlite html5 web-applications client-side indexeddb

说一张桌子有,名字,身份证,年龄,性别,教育等.ID是关键,表格也是姓名,年龄和性别的索引.我需要所有25岁以上的男学生按名字排序.

这在mySQL中很简单:

    SELECT * FROM table WHERE age > 25 AND sex = "M" ORDER BY name
Run Code Online (Sandbox Code Playgroud)

IndexDB允许创建索引并根据该索引对查询进行排序.但它不允许多次查询,如年龄和性别.我发现了一个名为queryIndexedDB(https://github.com/philikon/queryIndexedDB)的小型库,它允许复合查询,但不提供排序结果.

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

Jos*_*osh 72

本回答中使用的术语复合查询是指在其WHERE子句中涉及多个条件的SQL SELECT语句.尽管indexedDB规范中未提及此类查询,但您可以通过创建具有由属性名称数组组成的键路径的索引来近似复合查询的行为.

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

创建索引

在此示例中,"name","gender"和"age"对应于学生对象库中存储的学生对象的属性名称.

// 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);
}
Run Code Online (Sandbox Code Playgroud)

在索引上打开游标

然后,您可以在索引上打开一个光标:

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);
Run Code Online (Sandbox Code Playgroud)

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

另外:使用范围参数openCursor或get是可选的.如果您未指定范围,则会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. 将我增加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 vs A.

所以,早期的例子不会起作用.它实际上更像以下工作:

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)
Run Code Online (Sandbox Code Playgroud)

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

处理短路问题

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

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

例如,如果您使用的是index.get(array)或index.openCursor(array),那么就没有短路问题.有整场比赛或不是整场比赛.在这种情况下,比较函数仅评估两个值是否相同,而不是一个值是否大于或小于另一个.

其他需要考虑的技巧:

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

使用indexedDB.cmp进行测试

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

var a = ['Hello',1];
var b = ['World',2];
alert(indexedDB.cmp(a,b));
Run Code Online (Sandbox Code Playgroud)

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