В IndexedDB есть ли способ сделать отсортированный составной запрос?

говорят, что таблица имеет имя, ID, возраст, пол, образование и т. д. ID является ключом, и таблица также индексируется для имени, возраста и пола. Мне нужно, чтобы все студенты мужского пола старше 25 лет были отсортированы по именам.

это легко в mySQL:

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

IndexDB позволяет создавать индекс и заказывает запрос на основе этого индекса. Но это не позволяет задавать несколько вопросов, таких как возраст и пол. Я нашел небольшую библиотеку под названием queryIndexedDB (https://github.com/philikon/queryIndexedDB) который разрешает сложные запросы, но не предоставляет отсортированных результатов.

Итак, есть ли способ сделать сортированный составной запрос при использовании IndexedDB?

5 ответов


термин сложный запрос как используется в этом ответе, ссылается на инструкцию SQL SELECT, включающую более одного условия в предложение WHERE. Хотя такие запросы не упоминаются в спецификации indexedDB, можно приблизить поведение составного запроса, создав индекс с keypath который состоит из массива имен свойств.

это совершенно не связано с использованием флага multi-entry при создании индекса. Этот флаг multi-entry регулирует, как indexedDB создает индекс над одним свойством массива. Мы индексируем массив свойств объекта, а не значения одного свойства массива объекта.

создание индекса

в этом примере "имя", " пол " и "возраст" соответствуют именам свойств объектов student, хранящихся в хранилище объектов students.

// 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 или сделать это опционально. Если не указать диапазон, то IDBKeyRange.only неявно используется для вас. Другими словами, вам нужно использовать IDBKeyRange для ограниченных курсоры.

Основные понятия

индексы как объект хранит, но не напрямую Мутабельный. Вместо этого вы используете CRUD (create чтение update delete) операции в хранилище объектов, на которые ссылается, а затем indexedDB автоматически каскадирует обновления индекса.

понимание сортировки имеет основополагающее значение для понимания индексов. Индекс-это в основном специально отсортированная коллекция объектов. Технически, он также фильтруется, но я коснусь этого через минуту. Как правило, при открытии курсора на индексе выполняется итерация в соответствии с порядком индекса. Этот порядок может быть и, вероятно, отличается от порядок объектов в указанном хранилище объектов. Порядок важен, потому что это позволяет итерации быть более эффективными и позволяет пользовательскую нижнюю и верхнюю границу, которая имеет смысл только в контексте индексного порядка.

объекты в индексе сортируются во время внесения изменений в хранилище. При добавлении объекта в хранилище он добавляется в соответствующую позицию индекса. Сортировка сводится к функции сравнения, аналогичной Array.прототип.род, это сравнивает два элемента и возвращает, является ли один объект меньше другого, больше другого или равен. Таким образом, мы можем лучше понять поведение сортировки, погрузившись в более подробную информацию о функциях сравнения.

строки сравниваются лексикографически

это означает, например, что " Z "меньше, чем "a", и что строка '10' больше строка '020'.

сравниваются значения различных типов использование определенного спецификацией порядка

например, спецификация указывает, как строковое значение появляется до или после значения типа даты. Не имеет значения, что содержат значения, только типы.

IndexedDB не принуждает типы для вас. Вы можете выстрелить себе в ногу. Обычно вы не хотите сравнивать разные типы.

объекты с неопределенными свойствами не отображаются в индексах, путь к которым состоит из одного или нескольких из этих свойств

как я уже упоминал, индексы не всегда включают все объекты из объекта хранения. Когда вы помещаете объект в хранилище объектов, объект не будет отображаться в индексе, если у него отсутствуют значения для свойств, на которых основан индекс. Например, если у нас есть студент, возраст которого мы не знаем, и мы вставляем это в магазин студентов, конкретный студент не будет отображаться в индексе males25.

помните об этом, когда вы задаетесь вопросом, почему объект не появляется при итерации курсора по индексу.

Также обратите внимание на тонкое различие между null и пустой строкой. Пустая строка не отсутствующее значение. Объект с пустой строкой для свойства может по-прежнему отображаться в индексе на основе этого свойства, но не будет отображаться в индексе, если свойство присутствует, но не определено или отсутствует. И если его нет в индексе, вы не увидите его при итерации курсора над индекс.

при создании IDBKeyRange

вы должны указать допустимое значение для каждого свойства в пути к ключу массива при создании нижней или верхней границы для использования в диапазоне при открытии курсора над этим диапазоном. В противном случае вы получите некоторую ошибку Javascript (зависит от браузера). Например, нельзя создать диапазон, например IDBKeyRange.only([undefined, 'male', 25]) потому что свойство name не определено.

смешения, если вы указываете неправильное тип ценности, такие как IDBKeyRange.only(['male', 25]), где имя не определено, вы не получите ошибку в вышеуказанном смысле, но вы получите бессмысленные результаты.

существует исключение из этого общего правила: вы можете сравнивать массивы разной длины. Таким образом, вы технически можете опустить свойства из диапазона, при условии, что вы делаете это из конец массива, и что вы надлежащим образом обрезать массив. Например, вы можете использовать IDBKeyRange.only(['josh','male']).

сортировка короткозамкнутых массивов

на спецификация indexedDB предоставляет явный метод сортировки массивов:

значения типа Array сравниваются с другими значениями типа Array следующим образом:

  1. пусть A-первое значение массива, А B-второе значение массива.
  2. пусть длина меньше длины A и длины B.
  3. пусть i 0.
  4. если значение ith A меньше I-го значения B, тогда A меньше чем Б. пропустите оставшиеся шаги.
  5. Если I-е значение A больше, чем I-е значение B, то A больше, чем B. пропустите оставшиеся шаги.
  6. увеличить i на 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.

дело с коротким замыканием

вы не можете легко избежать короткого замыкания поведение в текущей реализации. В худшем случае вам нужно загрузить все объекты из хранилища/индекса в память, а затем отсортировать коллекцию, используя собственную функцию сортировки.

есть способы минимизировать или избежать некоторых проблем с коротким замыканием:

например, если вы используете index.get (массив) или индекс.openCursor (массив), то нет короткого замыкания. Есть либо весь матч, либо не весь матч. В этом случае функция сравнения только оценивает, являются ли два значения одинаковыми, а не является ли одно больше или меньше другого.

другие методы для рассмотрения:

  • изменить элементы путь от узкого до широкого. В основном обеспечивают ранние зажимы на диапазонах, которые отрезают некоторые нежелательные результаты короткого замыкания.
  • хранить завернутый объект в хранилище, использующем специально настроенные свойства, чтобы его можно было отсортировать с помощью ключ без массива (несоставной индекс) или может использовать составной индекс, на который не влияет поведение короткого замыкания.
  • использовать несколько индексов. Это приводит к проблема взрывающегося индекса. Обратите внимание, что эта ссылка касается другой базы данных no-sql, но те же понятия и объяснение применяются к indexedDB, и ссылка является разумным (и длительным и сложным) объяснением, поэтому я не повторяю его здесь.
  • один из создателей indexedDB (спецификация и реализация Chrome) недавно предложили использовать курсор.продолжайте:https://gist.github.com/inexorabletash/704e9688f99ac12dd336

тестирование с помощью indexedDB.cmp

на функция cmp обеспечивает быстрый и простой способ изучить, как сортировка работает. Например:

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

одно хорошее свойство indexedDB.функция cmp заключается в том, что ее сигнатура совпадает с параметром функции для массив.прототип.фильтр и массив.прототип.вроде. Вы можете легко проверить значения из консоли, не имея дело с подключениями / схемами / индексами и всем этим. Кроме того, indexedDB.cmp является синхронным, поэтому ваш тестовый код не должен включать асинхронные обратные вызовы / обещания.


Я опоздал на пару лет, но я просто хотел бы отметить, что ответ Джоша учитывает только сценарии, в которых "столбцы" в запросе являются частью индекса keyPath.

если какой-либо из указанных "столбцов" существует вне индекса keyPath, вам нужно будет проверить условия, связанные с ними, на каждой записи, которую курсор, созданный в Примере, повторяет. Поэтому, если вы имеете дело с такими запросами, или ваш индекс не unique, будьте готовы написать некоторую итерацию код!

в любом случае, я предлагаю вам проверить BakedGoods если вы можете представить ваш запрос как логическое выражение.

для этих типов операций он всегда будет открывать курсор на фокальном objectStore, если вы не выполняете строгий запрос равенства (x ===? y, учитывая, что x-это ключ objectStore или index), но это избавит вас от необходимости писать собственный код итерации курсора:

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

просто ради полной прозрачности, BakedGoods поддерживается moi.


библиотека JsStore доступно для запроса данных из IndexedDB, который очень прост в использовании и экономит много кода и времени. вы можете исследовать больше от здесь

это ваш эквивалентный sql-запрос с использованием JsStore.

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 эта библиотека позволяет использовать несколько фильтров, несколько видов и даже выбирать данные из ваших объектов. Он также работает через браузер (IE10, Firefox и Chrome)


вы можете открыть только открыть один запрос диапазона ключей в indexedDB. Поэтому используйте наиболее эффективный индекс, в данном случае "возраст". Просто отфильтруйте секс на итерации курсора. Заказ вы можете сделать позже, используя методы итерации массива. IndexedDB API не заинтересован в заказе, кроме предварительной организации записей индекса.