在IndexedDB中,是否有一种方法可以进行sorting的复合查询?

说一个表有姓名,身份证,年龄,性别,教育程度等。身份证是关键,表格也是按姓名,年龄和性别进行索引的。 我需要所有年龄大于25岁的男学生,按他们的名字sorting。

这在mySQL中很简单:

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

IndexDB允许创build一个索引,并根据该索引对查询进行sorting。 但是它不允许像年龄和性别这样的多重查询。 我发现一个名为queryIndexedDB(https://github.com/philikon/queryIndexedDB)的小型库,它允许复合查询,但不提供sorting结果。

那么有没有办法做一个sorting的复合查询,而使用IndexedDB?

此答案中使用的术语复合查询是指在其WHERE子句中涉及多个条件的SQL SELECT语句。 尽pipe索引数据库规范中未提及这样的查询,但可以通过使用由属性名称数组组成的键path创build索引来近似复合查询的行为。

这与创build索引时使用多条logging标志完全无关。 多项标志调整indexedDB如何通过单个数组属性创build索引。 我们索引一个对象属性的数组,而不是一个对象的单个数组属性的值。

创build索引

在这个例子中,“名字”,“性别”和“年龄”对应于存储在学生对象库内的学生对象的属性名称。

 // 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); 

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

另外:使用范围参数openCursor或get是可选的。 如果你没有指定范围,那么隐式地使用IDBKeyRange.only 。 换句话说,你只需要为有界游标使用IDBKeyRange

基本的索引概念

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

理解sorting是理解指数的基础。 索引基本上只是一个特别sorting的对象集合。 从技术上讲,它也被过滤了,但是我马上就会谈到这一点。 一般来说,当你在一个索引上打开一个游标时,你正在按照索引的顺序进行迭代。 这个顺序可能是,也可能是不同于被引用的对象存储中对象的顺序。 顺序非常重要,因为这样可以使迭代效率更高,并且允许自定义的上下限在索引特定顺序的上下文中才有意义。

索引中的对象在商店发生更改时进行sorting。 将对象添加到商店时,会将其添加到索引中的适当位置。 sorting归结为比较函数,类似于Array.prototype.sort,比较两个项目并返回一个对象是否小于另一个,大于另一个或相等。 所以我们可以通过深入比较函数的细节来更好地理解sorting行为。

string按字典顺序进行比较

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

不同types的值使用规范定义的顺序进行比较

例如,规范指定了stringtypes值在datetypes值之前或之后的含义。 这些值包含什么并不重要,只是types而已。

IndexedDB不强制你的types。 你可以在这里打脚 你通常不想比较不同的types。

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

正如我所提到的,索引可能并不总是包含引用对象存储中的所有对象。 将对象放入对象存储中时,如果索引所基于的属性缺less值,则该对象将不会显示在索引中。 例如,如果我们有一个我们不知道年龄的学生,并且将其插入学生商店,那么这个特定的学生就不会出现在男性的指数中。

记住这一点,当你想知道为什么一个对象不会出现在索引上迭代游标时。

还要注意null和空string之间的细微区别。 一个空string不是缺less的值。 属性为空的对象仍然可以出现在基于该属性的索引中,但是如果该属性存在但未定义或不存在,则不会出现在索引中。 如果它不在索引中,则在索引上迭代游标时不会看到它。

创buildIDBKeyRange时,必须指定数组keypath的每个属性

创build一个在范围内使用的下限或上限,以便在该范围内打开光标时,必须为数组的keypath中的每个属性指定一个有效值。 否则,你会得到一些types的Javascript错误(因浏览器而异)。 例如,您不能创build一个范围,如IDBKeyRange.only([undefined, 'male', 25])因为name属性是未定义的。

令人困惑的是,如果你指定了错误的值types ,比如IDBKeyRange.only(['male', 25]) ,其中name是未定义的,你将不会在上述意义上得到一个错误,但是你会得到无意义的结果。

这个通用规则有个例外:你可以比较不同长度的数组。 因此,从技术angular度而言,您可以省略范围内的属性,前提是您从数组的末尾进行操作,并且适当地截断数组。 例如,你可以使用IDBKeyRange.only(['josh','male'])

短路arrayssorting

indexedDB规范提供了一个显式的数组sorting方法:

Arraytypes的值与Arraytypes的其他值进行比较,如下所示:

  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. 如果我不等于长度,则返回步骤4.否则继续下一步。
  8. 如果A的长度小于B的长度,那么A小于B.如果A的长度大于B的长度,则A大于B,否则A和B相等。

捕获是在步骤4和5: 跳过其余的步骤 。 这基本上意味着,如果我们比较两个数组的顺序,比如[1,'Z']和[0,'A'],那么该方法只考虑第一个元素,因为在那个点1> 0。绝不会因为短路评估而检查Z vs A(规范中的步骤4和5)。

所以,前面的例子是不会工作的。 它实际上更像以下内容:

 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复合查询那样真正得到相同的行为。

处理短路

在当前的实施中,你不能轻易避免这种短路行为。 在最糟糕的情况下,您必须将存储/索引中的所有对象加载到内存中,然后使用您自己的自定义sortingfunction对集合进行sorting。

有一些方法可以减less或避免一些短路问题:

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

其他技术要考虑:

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

使用indexedDB.cmp进行testing

cmp函数提供了一种快速简单的方法来检查sorting如何工作。 例如:

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

indexedDB.cmp函数的一个很好的属性是它的签名与Array.prototype.filter和Array.prototype.sort的函数参数相同。 您可以轻松地从控制台testing值,而无需处理连接/模式/索引等等。 此外,indexedDB.cmp是同步的,因此您的testing代码不需要涉及asynchronouscallback/承诺。

我迟了几年,但我只想指出,乔希的答案只考虑查询中的“列”是索引keyPath一部分的keyPath

如果任何上述“列”存在于索引的keyPath ,则必须在每个条目上testing涉及它们的条件,这些条目在该示例中创build的迭代中。 所以,如果你正在处理这样的查询,或者你的索引不是unique ,准备写一些迭代代码!

无论如何,我build议你检查BakedGoods,如果你可以表示你的查询作为布尔expression式。

对于这些types的操作,除非正在执行严格的相等查询( x ===? y ,因为x是一个objectStore或索引键),否则它总是会在焦点对象存储上打开一个游标,但它会为您节省麻烦写你自己的游标迭代代码:

 bakedGoods.getAll({ filter: "keyObj > 5 && valueObj.someProperty !== 'someValue'", storageTypes: ["indexedDB"], complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){} }); 

为了完全透明,BakedGoods由moi维护。

有一个库JsStore可用于查询来自IndexedDB的数据,这是非常容易使用,并节省了大量的代码和时间。 你可以从这里探索更多

这是使用JsStore的等效sql查询。

 var connection = new JsStore.Instance("DbName"); connection.select({ From: "TableName", Where: { age : {'>':'25'}, sex : 'M' }, Order: { By: 'Name' }, OnSuccess:function (results){ console.log(results); }, OnError:function (error) { console.log(error); } }); 

只是想在Sql和写在JS 。 希望这可以帮助!

尝试使用Linq2indexedDB这个库允许你使用多个filter,多种sorting,甚至从你的对象中select数据。 它也可以跨浏览器(IE10,Firefox和Chrome)

您只能在indexedDB中打开一个键范围查询 。 因此,使用最有效的指标,在​​这种情况下,“年龄”。 只需在游标迭代中过滤掉性别即可。 sorting你可以稍后使用数组迭代方法。 除了预先安排索引条目之外,IndexedDB API对sorting没有兴趣。