Операторы сравнения: equals, above, below, between, startsWith

Фильтрация данных в Dexie.js строится вокруг индексированных запросов и API where(), которое предоставляет набор операторов сравнения для работы с IndexedDB. Эти операторы позволяют выполнять выборки по точному совпадению, диапазонам и префиксному поиску, опираясь на индексы, определённые в схеме базы данных.


where() как основа фильтрации

Метод where() работает только с индексированными полями. Индекс создаётся при объявлении схемы базы данных:

const db = new Dexie("AppDB");

db.version(1).stores({
  users: "++id, name, age, city"
});

В этом примере name, age и city являются индексируемыми полями, и к ним применимы операторы сравнения.

Запрос начинается с выбора индекса:

db.users.where("age")

Далее применяется оператор сравнения.


equals: точное совпадение

Оператор equals() (или сокращённая форма .equals(value)) используется для поиска записей, где значение индекса строго совпадает с заданным.

db.users.where("city").equals("Almaty").toArray();

Особенности:

  • выполняет точное сравнение
  • чувствителен к регистру для строк
  • требует индексированного поля
  • эквивалентно строгому равенству в терминах IndexedDB

Для чисел:

db.users.where("age").equals(25).toArray();

above: строго больше

Оператор above() возвращает записи, где значение индекса строго больше указанного.

db.users.where("age").above(30).toArray();

Логика:

  • value > x
  • граница не включается
  • применяется к числам, строкам и датам

Пример с датами:

db.orders.where("createdAt").above(new Date("2025-01-01")).toArray();

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

db.users.where("name").above("M").toArray();

below: строго меньше

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

db.users.where("age").below(18).toArray();

Поведение:

  • value < x
  • граница не включается
  • поддерживает числа, строки, даты

Пример с датами:

db.orders.where("createdAt").below(new Date("2024-01-01")).toArray();

Лексикографическое сравнение строк:

db.users.where("name").below("K").toArray();

between: диапазоны значений

Оператор between() позволяет задавать диапазон значений для индекса.

db.users.where("age").between(18, 30).toArray();

По умолчанию границы включаются:

  • нижняя граница включается
  • верхняя граница включается

Расширенная форма:

db.users.where("age").between(18, 30, true, true).toArray();

Параметры:

between(lower, upper, includeLower, includeUpper)

Варианты поведения:

  • true, true — включены обе границы
  • false, false — обе границы исключены
  • true, false — включена нижняя, исключена верхняя
  • false, true — исключена нижняя, включена верхняя

Пример исключающего диапазона:

db.users.where("age").between(18, 30, false, false).toArray();

Диапазоны особенно эффективны при работе с числовыми и датированными индексами, так как IndexedDB использует B-tree структуру.


startsWith: префиксный поиск

Оператор startsWith() применяется к строковым индексам и выполняет поиск по началу строки.

db.users.where("name").startsWith("Al").toArray();

Он возвращает все записи, где значение поля начинается с указанного префикса:

  • “Alex”
  • “Alice”
  • “Alina”

Особенности:

  • работает только со строками
  • использует индексный диапазон, а не полный перебор
  • чувствителен к регистру
  • быстрее, чем filter(), так как использует индекс

Пример для городов:

db.users.where("city").startsWith("New").toArray();

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

Операторы можно разделить по типу сравнения:

Точное совпадение:

equals(value)

Диапазонные:

above(value)
below(value)
between(lower, upper)

Строковый префикс:

startsWith(prefix)

Особенности работы с индексами

Все операторы where() опираются на IndexedDB индексы. Это означает:

  • отсутствие индекса делает оператор неприменимым
  • при отсутствии индекса требуется filter(), который значительно медленнее
  • индексы работают только по одному полю (если не используется compound index)

Пример ошибки архитектуры:

db.users.where("nonIndexedField").equals("test")

Такой запрос невозможен без индекса.


Лексикографическое сравнение строк

Для above, below, between и startsWith строки сравниваются не по смыслу, а по Unicode-порядку.

Пример:

"A" < "B"
"Z" < "a"

Это влияет на:

  • сортировку
  • диапазоны
  • префиксные выборки

Включение границ в between и влияние на результат

Различие включённых и исключённых границ критично при построении диапазонов:

db.users.where("age").between(18, 18).toArray();

Вернёт только записи с age = 18, если границы включены.

При исключении:

db.users.where("age").between(18, 18, false, false).toArray();

результат всегда пуст.


Комбинирование с другими методами запроса

Операторы сравнения часто используются в цепочках:

db.users
  .where("age")
  .between(18, 30)
  .and(user => user.city === "Almaty")
  .toArray();

Или с сортировкой:

db.users
  .where("age")
  .above(20)
  .sortBy("name");

Ограничения операторов сравнения

  • работают только с индексированными полями
  • не поддерживают сложные выражения внутри where
  • startsWith применим только к строкам
  • диапазоны зависят от типа данных
  • compound queries требуют отдельного индексирования

Производительность диапазонных запросов

Диапазонные операторы (above, below, between, startsWith) используют B-tree индексирование, что обеспечивает:

  • логарифмическую сложность поиска
  • отсутствие полного сканирования таблицы
  • масштабируемость при больших объёмах данных

Особенно эффективны:

  • фильтрация по датам
  • числовые диапазоны
  • автодополнение строк через startsWith

Поведение при пустых результатах

Все операторы возвращают пустой массив без ошибок, если совпадений нет:

db.users.where("age").above(200).toArray(); // []

Влияние типов данных

Поведение операторов зависит от типа индекса:

  • числа сравниваются арифметически
  • строки — лексикографически
  • даты — по timestamp
  • бинарные данные — по байтовому порядку

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