Подтвердить что ты не робот

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

Скажите, что у таблицы есть имя, имя, возраст, пол, образование и т.д. Идентификатор - это ключ, а таблица также индексируется для имени, возраста и пола. Мне нужны все студенты-мужчины старше 25 лет, отсортированные по их именам.

Это легко в mySQL:

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

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

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

4b9b3361

Ответ 1

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

Это абсолютно не связано с использованием флага multi-entry при создании индекса. Флаг с несколькими входами настраивает, как 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);

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

Кроме того: использование параметра range для openCursor или get является необязательным. Если вы не укажете диапазон, то IDBKeyRange.only неявно используется для вас. Другими словами, вам нужно использовать IDBKeyRange для ограниченных курсоров.

Фундаментальные понятия понятия

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

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

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

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

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

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

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

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

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

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

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

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

Вы должны указать каждое свойство пути ключа массива при создании IDBKeyRange

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

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

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

Сортировка с коротким замыканием

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

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

  • Пусть A - первое значение массива, а B - второе значение массива.
  • Пусть длина будет меньше длины 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 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(array) или index.openCursor(array), тогда не существует проблемы с коротким замыканием. Существует либо полный матч, либо не весь матч. В этом случае функция сравнения оценивает только то, являются ли два значения одинаковыми, а не один ли больше или меньше другого.

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

  • Перегруппируйте элементы ключевого тракта от самого узкого до самого широкого. В основном обеспечивают ранние зажимы на диапазонах, которые отсекают некоторые из нежелательных результатов короткого замыкания.
  • Храните обернутый объект в хранилище, который использует специально настроенные свойства, чтобы его можно было отсортировать с использованием не-массива keypath (не-составной индекс) или, возможно, использовать составной индекс, на который не влияет короткое замыкание.
  • Используйте несколько индексов. Это приводит к проблеме взрывающегося индекса. Обратите внимание, что эта ссылка касается другой базы данных no-sql, но те же понятия и объяснения относятся к индексированномуDB, и ссылка является разумным (и длинным и сложным) объяснением, поэтому я не повторяю его здесь.
  • Недавно один из создателей 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 является синхронным, поэтому ваш тестовый код не требует привлечения асинхронных обратных вызовов/ promises.

Ответ 2

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

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

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

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

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

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

Ответ 3

Существует библиотека 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. Надеюсь, это поможет!

Ответ 4

Попробуйте использовать Linq2indexedDB, эта библиотека позволяет вам использовать несколько филей, несколько сортировок и даже выбирать данные из ваших объектов. Он также работает кросс-браузер (IE10, Firefox и Chrome)

Ответ 5

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