为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

米琪卡哇伊 2019-11-15 12:38:29

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


这在mySQL中很简单:


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

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


那么,在使用IndexedDB时,是否有一种方法可以进行排序的复合查询?


查看完整描述

3 回答

?
SMILET

在此答案中使用的术语“ 复合查询”指的是在其WHERE子句中涉及多个条件的SQL SELECT语句。尽管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类型的其他值进行比较:


设A为第一个数组值,B为第二个数组值。

令长度为A的长度和B的长度中的较小者。

让我为0。

如果A的第i个值小于B的第i个值,则A小于B。跳过其余步骤。

如果A的第i个值大于B的第i个值,则A大于B。跳过其余步骤。

将我增加1。

如果i不等于长度,请返回到步骤4。否则,请继续执行下一步。

如果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:https ://gist.github.com/inexorabletash/704e9688f99ac12dd336

使用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是同步的,因此您的测试代码不需要涉及异步回调/承诺。


查看完整回答
反对 回复 2019-11-15
?
一只甜甜圈

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


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


无论如何,我建议您检查一下BakedGoods,是否可以将查询表示为布尔表达式。


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


bakedGoods.getAll({

    filter: "keyObj > 5 && valueObj.someProperty !== 'someValue'",

    storageTypes: ["indexedDB"],

    complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}

});

为了完全透明起见,BaikeGoods由moi维护。


查看完整回答
反对 回复 2019-11-15
?
45度呼吸

有一个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中编写即可。希望这可以帮助!


查看完整回答
反对 回复 2019-11-15

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信