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

时间:2012-08-23 03:04:46

标签: html5 sqlite web-applications client-side indexeddb

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

这在mySQL中很简单:

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

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

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

5 个答案:

答案 0 :(得分:70)

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

这与在创建索引时使用多条目标志完全无关。多条目标志调整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','male',200];
var range = IDBKeyRange.bound(lowerBound, upperBound);
var request = index.openCursor(range);

然而,由于我要解释的原因,这不会一直有效。

除此之外:使用范围参数openCursor或get是可选的。如果您未指定范围,则隐式使用IDBKeyRange.only。换句话说,您只需要使用IDBKeyRange作为有界游标。

基本指数概念

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

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

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

字符串按字典顺序进行比较

这意味着,例如,' Z'不到' a'并且字符串' 10'大于字符串' 020'。

使用规范定义的顺序

比较不同类型的值

例如,规范指定字符串类型值在日期类型值之前或之后的方式。值包含什么并不重要,只有类型。

IndexedDB不会为您强制类型。你可以在这里射击自己。你通常不想比较不同的类型。

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

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

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

还要注意null和空字符串之间的细微差别。空字符串缺少值。具有属性的空字符串的对象仍可以基于该属性显示在索引中,但如果属性存在但未定义或不存在,则不会出现在索引中。如果它不在索引中,那么在将光标复制到索引上时,您将无法看到它。

创建IDBKeyRange

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

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

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

这个一般规则有一个例外:你可以比较不同长度的数组。因此,从技术上讲,您可以省略该范围中的属性,前提是您从阵列的 end 执行此操作,并且适当地截断该数组。例如,您可以使用IDBKeyRange.only(['josh','male'])

短路阵列排序

indexedDB specification提供了一种排序数组的显式方法:

将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是平等的。
  9. 第4步和第5步:跳过其余步骤。这基本上意味着如果我们比较两个数组的顺序,例如[1,' Z']和[0,' A'],该方法只考虑第一个元素因为在那一点上1是> 0.由于评估短路(规范中的第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。注意这个链接是关于另一个无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.filterArray.prototype.sort的函数参数相同。您可以轻松地从控制台测试值,而无需处理连接/模式/索引以及所有这些。此外,indexedDB.cmp是同步的,因此您的测试代码不需要涉及异步回调/承诺。

答案 1 :(得分:2)

我迟到了几年,但我只想指出Josh的回答只考虑&#34;列&#34;&#34;&#34;&#34;在查询中是索引的keyPath

的一部分

如果有任何&#34;列&#34;如果存在于索引keyPath之外,则必须在示例中创建的光标迭代的每个条目上测试涉及它们的条件。因此,如果您正在处理此类查询,或者您的索引不是unique,请准备好编写一些迭代代码!

在任何情况下,如果您可以将查询表示为布尔表达式,我建议您查看BakedGoods

对于这些类型的操作,除非您执行严格的相等查询(x ===? y,给定x是objectStore或索引键),否则它将始终在焦点对象库上打开游标,但它将会省去编写自己的游标迭代代码的麻烦:

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

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

答案 2 :(得分:1)

尝试使用Linq2indexedDB此库允许您使用多个过滤器,多种排序,甚至可以从对象中选择数据。它也适用于跨浏览器(IE10,Firefox和Chrome)

答案 3 :(得分:1)

有一个库 JsStore 可用于查询来自IndexedDB的数据,它非常易于使用并节省大量代码和时间。 您可以从here

了解更多信息

这是使用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中编写即可。希望这有帮助!

答案 4 :(得分:0)

您只能在indexedDB中打开open one key range query。因此,使用最有效的指数,在这种情况下,“年龄”。只需在光标迭代中过滤掉性别。您可以稍后使用Array迭代方法进行排序。除了预先安排索引条目之外,IndexedDB API对订购不感兴趣。