Метод intersect() в Dexie.js предназначен для
пересечения результатов нескольких запросов. Он позволяет оставить
только те записи, которые одновременно удовлетворяют нескольким условиям
выборки.
В реляционных базах данных подобная логика часто реализуется через
комбинации операторов AND, сложные индексы или вложенные
запросы. В IndexedDB такие операции могут быть затруднены, особенно
когда условия относятся к разным индексам. Dexie.js предоставляет
механизм пересечения наборов данных через метод
intersect(), упрощающий решение подобных задач.
Основная идея метода заключается в следующем:
Таким образом реализуется математическая операция пересечения множеств.
Предположим, существует таблица пользователей:
db.version(1).stores({
users: '++id, name, city, age, role'
});
Содержимое:
[
{ id: 1, name: 'Анна', city: 'Москва', age: 25, role: 'admin' },
{ id: 2, name: 'Иван', city: 'Москва', age: 30, role: 'user' },
{ id: 3, name: 'Олег', city: 'Казань', age: 30, role: 'admin' },
{ id: 4, name: 'Мария', city: 'Москва', age: 35, role: 'admin' }
]
Необходимо получить пользователей:
Первое множество:
const moscowUsers =
db.users.where('city').equals('Москва');
Второе множество:
const admins =
db.users.where('role').equals('admin');
Требуется найти общие элементы этих двух выборок.
Общий вид метода:
collection.intersect(otherCollection)
где:
collection — основная коллекция;otherCollection — коллекция для пересечения.Пример:
const result = await db.users
.where('city')
.equals('Москва')
.intersect(
db.users
.where('role')
.equals('admin')
)
.toArray();
Результат:
[
{ id: 1, name: 'Анна', city: 'Москва', age: 25, role: 'admin' },
{ id: 4, name: 'Мария', city: 'Москва', age: 35, role: 'admin' }
]
В итоговый набор попали только записи, присутствующие одновременно в обеих выборках.
Внутренне Dexie выполняет следующие действия:
Условно это можно представить следующим псевдокодом:
result = collectionA.filter(item =>
collectionB.includes(item)
);
На практике механизм оптимизирован и работает через первичные ключи объектов.
Важно понимать, что сравнение производится не по содержимому объектов, а по их первичным ключам.
Например:
db.users.where('city')
.equals('Москва')
.intersect(
db.users.where('age').above(28)
);
Первая выборка:
[
{ id: 1, ... },
{ id: 2, ... },
{ id: 4, ... }
]
Вторая выборка:
[
{ id: 2, ... },
{ id: 3, ... },
{ id: 4, ... }
]
Пересечение:
[
{ id: 2, ... },
{ id: 4, ... }
]
Совпадение определяется по значениям id.
Метод особенно полезен при комбинировании диапазонных выборок.
Пример:
const result = await db.users
.where('age')
.between(25, 40)
.intersect(
db.users
.where('city')
.equals('Москва')
)
.toArray();
Логика запроса:
Результат содержит записи, удовлетворяющие обоим критериям.
Пересечения могут строиться цепочкой.
Пример:
const result = await db.users
.where('city')
.equals('Москва')
.intersect(
db.users.where('role')
.equals('admin')
)
.intersect(
db.users.where('age')
.above(30)
)
.toArray();
Логика:
Москва
AND администратор
AND возраст > 30
Результат:
[
{
id: 4,
name: 'Мария',
city: 'Москва',
age: 35,
role: 'admin'
}
]
Каждое новое пересечение дополнительно сокращает набор данных.
Очень распространённый сценарий — объединение anyOf() и
intersect().
Например:
const result = await db.users
.where('city')
.anyOf(['Москва', 'Казань'])
.intersect(
db.users
.where('role')
.equals('admin')
)
.toArray();
Первый набор:
Москва ИЛИ Казань
Второй набор:
Только администраторы
Итог:
(Москва OR Казань)
AND admin
Такой подход позволяет создавать достаточно сложные логические конструкции.
Одним из важнейших применений метода является реализация логического
оператора AND между различными индексами.
IndexedDB обычно эффективно работает с одним индексом за запрос.
Например:
.where('city').equals('Москва')
или
.where('role').equals('admin')
Но запрос вида:
city = Москва
AND role = admin
по двум независимым индексам напрямую выполнить сложнее.
Метод intersect() решает эту проблему:
await db.users
.where('city')
.equals('Москва')
.intersect(
db.users
.where('role')
.equals('admin')
)
.toArray();
Фактически происходит пересечение результатов двух индексных запросов.
Тот же результат можно получить через фильтрацию:
const result = await db.users
.where('city')
.equals('Москва')
.filter(user => user.role === 'admin')
.toArray();
Однако между подходами существует существенная разница.
.where('city')
.equals('Москва')
.filter(...)
Преимущества:
Недостатки:
.where('city')
.equals('Москва')
.intersect(...)
Преимущества:
Недостатки:
Рассмотрим схему:
db.version(1).stores({
users: '++id, [city+role], age'
});
Можно выполнять запросы по составному индексу:
db.users
.where('[city+role]')
.equals(['Москва', 'admin']);
Но иногда составной индекс отсутствует либо условия слишком разнообразны.
Тогда применяется пересечение:
db.users
.where('city')
.equals('Москва')
.intersect(
db.users
.where('role')
.equals('admin')
);
Такой подход обеспечивает гибкость без необходимости создавать большое количество составных индексов.
Следует учитывать, что intersect() требует обработки
двух коллекций.
Например:
db.orders
.where('status')
.equals('completed')
.intersect(
db.orders
.where('country')
.equals('Germany')
);
Если каждая выборка содержит десятки тысяч записей, потребуется:
Поэтому эффективность зависит от:
Чем уже исходные коллекции, тем быстрее выполняется операция.
Структура таблицы:
db.version(1).stores({
products: '++id, category, brand, price'
});
Необходимо найти:
Решение:
const products = await db.products
.where('category')
.equals('electronics')
.intersect(
db.products
.where('brand')
.equals('Samsung')
)
.toArray();
Логическая модель:
category = electronics
AND brand = Samsung
Таблица:
db.version(1).stores({
orders: '++id, status, country, customerId'
});
Запрос:
const orders = await db.orders
.where('status')
.equals('completed')
.intersect(
db.orders
.where('country')
.equals('Germany')
)
.intersect(
db.orders
.where('customerId')
.equals(15)
)
.toArray();
Эквивалентная логика:
status = completed
AND country = Germany
AND customerId = 15
Корректный вариант:
db.users
.where('city')
.equals('Москва')
.intersect(
db.users
.where('role')
.equals('admin')
);
Некорректный вариант:
db.users
.where('city')
.equals('Москва')
.intersect(
db.orders
.where('status')
.equals('completed')
);
Пересечение объектов разных таблиц не имеет смысла, поскольку первичные ключи принадлежат различным наборам данных.
Если запрос постоянно использует одинаковый набор условий:
city + role
то составной индекс обычно будет эффективнее:
'[city+role]'
Метод intersect() особенно полезен тогда, когда
комбинации условий динамически меняются и заранее определить все
составные индексы невозможно.
Наиболее распространённые случаи использования:
AND между разными
индексами;equals(),
between(), above(), below(),
anyOf() и других методов выборки.Метод intersect() является важным инструментом
построения сложных запросов в Dexie.js, позволяя выполнять пересечение
нескольких индексных выборок и получать только те записи, которые
одновременно присутствуют во всех участвующих множествах данных.