这在MysqL中很简单:
SELECT * FROM table WHERE age > 25 AND sex = "M" ORDER BY name
IndexDB允许创建索引,并根据该索引对查询进行排序。但它不允许多个查询,如年龄和性别。我找到一个名为queryIndexedDB(https://github.com/philikon/queryIndexedDB)的小库,它允许复合查询,但不提供排序结果。
那么有没有办法使用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); }
在索引上打开一个光标
然后可以在索引上打开一个光标:
var students = transaction.objectStore('students'); var index = students.index('males25'); var lowerBound = ['AAAAA','male',26]; var upperBound = ['ZZZZZ',200]; var range = IDBKeyRange.bound(lowerBound,upperBound); var request = index.openCursor(range);
然而,由于我要解释的原因,这并不总是奏效。
另外:使用rangeCursor或get的range参数是可选的。如果不指定范围,则IDBKeyRange.only将被隐式使用。换句话说,您只需要为有界游标使用IDBKeyRange。
基本指标概念
指数就像对象商店,但不是直接可变的。而是在引用的对象存储上使用CRUD(创建读取更新删除)操作,然后,indexedDB会自动将更新级联到索引。
了解排序是理解指标的基础。索引基本上只是一个特别排序的对象集合。在技术上,它也被过滤,但我会在一会儿触及。通常,当您在索引上打开游标时,您将根据索引的顺序进行迭代。此顺序可能并且可能与引用对象存储中的对象的顺序不同。该顺序很重要,因为这样可以使迭代更有效率,并允许自定义的下限和上限在索引特定顺序的上下文中是有意义的。
索引中的对象在商店发生更改时排序。将对象添加到商店时,将其添加到索引中的正确位置。排序归结为一个比较函数,类似于Array.prototype.sort,它比较两个项目,并返回一个对象是否小于另一个,大于另一个对象或相等。因此,我们可以通过深入了解比较功能的更多细节来更好地理解排序行为。
字符串按字典顺序比较
这意味着,例如,’Z’小于’a’,字符串’10’大于字符串’020’。
使用规范定义的顺序比较不同类型的值
例如,该规范指定字符串类型值在日期类型值之前或之后。值不包含什么,只是类型。
IndexedDB不会为您强制类型。你可以在脚下射击自己。你通常不想比较不同的类型。
具有未定义属性的对象不会出现在其关键字由一个或多个这些属性组成的索引中
正如我所提到的,索引可能并不总是包括引用对象存储中的所有对象。将对象放入对象存储中时,如果索引所基于的属性缺少值,则对象将不会出现在索引中。例如,如果我们有一个我们不知道年龄的学生,并且我们将它插入学生商店,那么特定的学生不会出现在男性25的索引中。
记住这一点,当你想知道为什么在索引上迭代游标时不会出现一个对象。
还要注意null和空字符串之间的细微差别。空字符串不是缺少的值。具有空字符串的属性的对象仍然可以基于该属性出现在索引中,但如果属性存在但未定义或不存在,则该索引中不会出现该索引。如果它不在索引中,则在索引上迭代游标时,不会看到它。
创建IDBKeyRange时,必须指定数组keypath的每个属性
创建一个下限或上限时,必须为数组关键字中的每个属性指定一个有效值,以便在该范围上打开光标时在范围内使用。否则,您会收到一些类型的Javascript错误(因浏览器而异)。例如,您不能创建一个范围,例如IDBKeyRange.only([undefined,’male’,25]),因为name属性未定义。
令人困惑的是,如果你指定错误类型的值,例如IDBKeyRange.only([‘male’,25]),其中name是未定义的,你不会在上面的意义上得到错误,但是你会得到无意义的结果。
这个一般规则有一个例外:您可以比较不同长度的数组。因此,从技术上讲,您可以从范围中省略属性,前提是您从数组的末尾执行此操作,并适当地截断数组。例如,您可以使用IDBKeyRange.only([‘josh’,’male’])。
短路数组排序
indexedDB specification提供了排列数组的显式方法:
将Array类型的值与Array的其他值进行比较,如下所示:
>令A为第一个数组值,B为第二个数组值。
>把长度作为A长度和B长度的较小者。
让我成为0。
>如果A的第i个值小于B的第i个值,则A较少
比B.跳过剩下的步骤。
>如果A的第i个值大于B的第i个值,则A大于B.跳过其余步骤。
>增加我1。
>如果我不等于长度,请返回到步骤4.否则继续下一步。
>如果A的长度小于B的长度,则A小于B.如果A的长度大于B的长度,则A大于B.否则A和B相等。
捕获在步骤4和5:跳过剩余的步骤。这基本上意味着如果我们比较两个数组用于顺序,例如[1,’Z’]和[0,’A’],则该方法仅考虑第一个元素,因为在该点1是>由于短路评估(规格中的步骤4和5),它永远不会检查Z对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)
如果您在sql或通用编程中有这样的布尔子句的任何经验,那么您已经应该认识到整套条件不一定涉及。这意味着您将无法获取所需的对象列表,这就是为什么您无法真正获得与sql复合查询相同的行为。
处理短路
在当前的实现中,您不能轻易避免这种短路行为。在最坏的情况下,您必须将存储/索引中的所有对象加载到内存中,然后使用自己的自定义排序功能对集合进行排序。
有办法尽量减少或避免一些短路问题:
例如,如果您使用的是index.get(array)或index.openCursor(array),那么就没有任何短路问题。整场比赛还是整场比赛。在这种情况下,比较功能只是评估两个值是否相同,而不是一个是大于还是小于另一个。
其他技术要考虑:
>将关键字的元素从最窄到最大重新排列。基本上提供早期的夹具,可以切断一些不需要的短路结果。
>将包装的对象存储在使用特殊定制属性的商店中,以便可以使用非数组关键字(非复合索引)对其进行排序,或者可以使用不受短组合索引影响的复合索引,电路行为。
>使用多个索引。这导致了exploding index problem.注意这个链接是关于另一个no-sql数据库,但是相同的概念和解释适用于indexedDB,并且链接是一个合理(冗长和复杂的)解释,所以我不在这里重复。
> indexedDB(规范和Chrome实现)的创建者之一最近建议使用cursor.continue:https://gist.github.com/inexorabletash/704e9688f99ac12dd336
使用indexedDB.cmp进行测试
cmp function提供了一种快速简单的方法来检查排序是如何工作的。例如:
var a = ['Hello',1]; var b = ['World',2]; alert(indexedDB.cmp(a,b));
indexedDB.cmp函数的一个不错的属性是它的签名与Array.prototype.filter和Array.prototype.sort的函数参数相同。您可以轻松地从控制台测试值,而无需处理连接/ schemas /索引和所有这些。此外,indexedDB.cmp是同步的,所以您的测试代码不需要涉及async回调/承诺。