В 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, вы можете аппроксимировать поведение составного запроса, создав индекс с ключом, который состоит из массива имен свойств.
Это совершенно не связано с использованием флага многократной записи при создании индекса. Флаг множественных записей регулирует, как 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);
Однако по причинам, которые я собираюсь объяснить, это не всегда будет работать.
В сторону: использование параметра диапазона для openCursor или get не является обязательным. Если вы не укажете диапазон, то IDBKeyRange.only
неявно используется для вас. Другими словами, вам нужно только использовать IDBKeyRange
для ограниченных курсоров.
Основные понятия индекса
Индексы похожи на хранилища объектов, но не могут быть изменяемыми напрямую. Вместо этого вы используете операции CRUD (создание и удаление обновления чтения) в указанном хранилище объектов, а затем indexedDB автоматически каскадно обновляет индекс.
Понимание сортировки имеет основополагающее значение для понимания индексов. Индекс - это просто специально отсортированная коллекция объектов. Технически это тоже фильтруется, но я коснусь этого чуть позже. Обычно, когда вы открываете курсор на индексе, вы выполняете итерацию в соответствии с порядком индекса. Этот порядок может быть и, вероятно, отличается от порядка объектов в указанном хранилище объектов. Порядок важен, потому что это позволяет итерации быть более эффективной и позволяет настраивать нижнюю и верхнюю границу, которая имеет смысл только в контексте порядка, определенного для индекса.
Объекты в индексе сортируются во время изменений в хранилище. Когда вы добавляете объект в хранилище, он добавляется в правильную позицию в индексе. Сортировка сводится к функции сравнения, аналогичной Array.prototype.sort, которая сравнивает два элемента и возвращает информацию о том, является ли один объект меньше другого, больше другого или равен. Таким образом, мы можем лучше понять поведение сортировки, углубившись в подробности функций сравнения.
Строки сравниваются лексикографически
Это означает, например, что "Z" меньше, чем "а", а строка "10" больше, чем "020".
Значения разных типов сравниваются с использованием заданного в спецификации порядка
Например, спецификация указывает, как значение типа строки приходит до или после значения типа даты. Неважно, что содержат значения, только типы.
IndexedDB не принуждает типы для вас. Вы можете выстрелить себе в ногу здесь. Как правило, вы никогда не хотите сравнивать разные типы.
Объекты с неопределенными свойствами не отображаются в индексах, путь к которым состоит из одного или нескольких из этих свойств.
Как я уже упоминал, индексы не всегда могут включать в себя все объекты из указанного хранилища объектов. Когда вы помещаете объект в хранилище объектов, он не будет отображаться в индексе, если в нем отсутствуют значения свойств, на которых основан индекс. Например, если у нас есть ученик, у которого мы не знаем возраст, и мы вставляем его в хранилище учеников, этот ученик не будет отображаться в индексе males25.
Помните об этом, когда вам интересно, почему объект не появляется при перемещении курсора по индексу.
Также обратите внимание на тонкую разницу между нулем и пустой строкой. Пустая строка не является пропущенным значением. Объект с пустой строкой для свойства все еще может появляться в индексе, основанном на этом свойстве, но не будет отображаться в индексе, если свойство присутствует, но не определено или отсутствует. И если его нет в индексе, вы не увидите его при наведении курсора на индекс.
Вы должны указать каждое свойство ключевого пути массива при создании IDBKeyRange
Вы должны указать допустимое значение для каждого свойства в ключевом пути массива при создании нижней или верхней границы, чтобы использовать ее в диапазоне при открытии курсора над этим диапазоном. В противном случае вы получите ошибку типа Javascript (зависит от браузера). Например, вы не можете создать диапазон, такой как IDBKeyRange.only([undefined, 'male', 25])
потому что свойство name не определено.
Сбивает с толку, если вы указали неправильный тип значения, такие как IDBKeyRange.only(['male', 25])
где имя не определено, вы не получите ошибку в указанном выше смысле, но вы получите бессмысленные результаты.
Из этого общего правила есть исключение: вы можете сравнивать массивы различной длины. Следовательно, технически вы можете опустить свойства из диапазона, при условии, что вы делаете это с конца массива и что вы соответствующим образом обрезаете массив. Например, вы можете использовать IDBKeyRange.only(['josh','male'])
,
Сортировка короткозамкнутых массивов
Спецификация indexedDB предоставляет явный метод для сортировки массивов:
Значения типа Array сравниваются с другими значениями типа Array следующим образом:
- Пусть A будет первым значением Array, а B будет вторым значением Array.
- Пусть длина будет меньше длины A и длины B.
- Позвольте мне быть 0.
- Если i-ое значение A меньше i-го значения B, то A меньше B. Пропустите оставшиеся шаги.
- Если i-е значение A больше, чем i-е значение B, то A больше, чем B. Пропустите оставшиеся шаги.
- Увеличьте я на 1.
- Если я не равен длине, вернитесь к шагу 4. В противном случае перейдите к следующему шагу.
- Если длина A меньше длины B, то A меньше B. Если длина A больше длины B, то A больше B. В противном случае A и B равны.
Подвох в шагах 4 и 5: пропустите оставшиеся шаги. Это в основном означает, что если мы сравниваем два массива для порядка, таких как [1,'Z'] и [0,'A'], метод учитывает только первый элемент, потому что в этой точке 1 равен> 0. никогда не удосуживается проверить Z против 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(массив) или index.openCursor(массив), проблема короткого замыкания отсутствует. Существует либо весь матч, либо не весь матч. В этом случае функция сравнения только оценивает, являются ли два значения одинаковыми, а не является ли одно больше или меньше другого.
Другие методы для рассмотрения:
- Переставьте элементы траектории от самой узкой к самой широкой. В основном обеспечивают ранние зажимы на диапазонах, которые отключают некоторые нежелательные результаты короткого замыкания.
- Храните обернутый объект в хранилище, которое использует специально настроенные свойства, чтобы его можно было отсортировать с использованием пути к немассивному ключу (не составной индекс) или использовать составной индекс, который не подвержен короткому замыканию поведение.
- Используйте несколько индексов. Это приводит к проблеме взрыва индекса. Обратите внимание, что эта ссылка относится к другой базе данных 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 является синхронным, поэтому ваш тестовый код не должен включать асинхронные обратные вызовы / обещания.
Я опоздал на пару лет, но я хотел бы отметить, что ответ Джоша рассматривает только те сценарии, в которых "столбцы" в запросе являются частью индекса. keyPath
,
Если какой-либо из указанных "столбцов" существует вне индекса keyPath
вам нужно будет проверить условия, связанные с ними, для каждой записи, по которой перебирает курсор, созданный в примере. Так что, если вы имеете дело с такими запросами, или ваш индекс не unique
, будьте готовы написать некоторый итерационный код!
В любом случае, я предлагаю вам проверить BakedGoods, если вы можете представить свой запрос в виде логического выражения.
Для этих типов операций он всегда будет открывать курсор в фокусном объектном хранилище, если вы не выполняете запрос строгого равенства (x ===? y
, учитывая, что x является objectStore или индексным ключом), но это избавит вас от необходимости писать собственный код итерации курсора:
bakedGoods.getAll({
filter: "keyObj > 5 && valueObj.someProperty !== 'someValue'",
storageTypes: ["indexedDB"],
complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}
});
Просто ради полной прозрачности BakedGoods поддерживается moi.
Для запроса данных из IndexedDB имеется библиотека JsStore, которая очень проста в использовании и экономит много кода и времени. Вы можете узнать больше здесь
Это ваш эквивалентный 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. Так что используйте наиболее эффективный индекс, в данном случае, "возраст". Просто отфильтруйте секс по итерации курсора. Заказ вы можете сделать позже, используя методы итераций Array. IndexedDB API не заинтересован в упорядочивании, кроме предварительной организации записей индекса.