Při vývoji je často potřeba pracovat s dynamickými atributy pro určité entity, navíc entity samotné mohou být zcela dynamické. Myslím, že nejznámějším příkladem toho je JIRA, kde mohou administrátoři přidávat nebo odebírat atributy lístků, načež s nimi může potenciálně pracovat každý uživatel (prohlížet nebo měnit jejich hodnoty). JIRA zároveň poskytuje rozsáhlé možnosti pro filtrování a třídění tiketů podle dynamických atributů, což naznačuje, že práce s dynamickými atributy je hluboce integrována do datového skladu JIRA, jinak by bylo nepravděpodobné dosáhnout dobrého výkonu při práci s velkým množstvím dat. . Pokud tedy například existují tisíce nebo dokonce miliony uložených objektů (stejné tipy v JIRA) a pokud by filtrování nebylo implementováno v samotném datovém úložišti, pak by bylo nutné načíst každý objekt do paměti aplikace a zkontrolovat, zda odpovídá specifikovaným podmínkám filtrace. Je zřejmé, že tento přístup se nezdá být zvláště účinný.
Při práci na různých projektech jsem se s podobnými problémy několikrát setkal a nyní chci nabídnout řešení, které je podle mého názoru poměrně efektivní.
Poznámka. Dále se zaměříme pouze na SQL databáze. S největší pravděpodobností budou mnozí souhlasit s tvrzením, že databáze SQL jsou dnes stále nejoblíbenějším způsobem ukládání dat aplikací.
Než budete moci filtrovat data, musíte se nejprve rozhodnout, jak správně uspořádat data v tabulkách SQL. V nejjednodušším scénáři může struktura tabulky pro dynamické atributy vypadat takto:

  • Produktový — Seznam objektů, které budou rozšířeny o dynamické atributy;
  • Atribut — Seznam dynamických atributů (název a typ atributu);
  • ProductAtribute — hodnoty dynamických atributů pro objekty.

Podívejme se na příklad, kde máme seznam modelů mobilních telefonů a chceme dynamicky přidat některé další atributy, jako je „výrobce“, „interní paměť (GB)“, „celulární protokoly“, „datum vydání“ atd. d.

V tomto případě mohou data vypadat takto:

Na této jednoduché struktuře je jedna věc, která může způsobit určité problémy – všimněte si, že typ sloupce Hodnota je řetězec. Na první pohled to vypadá logicky, protože typ atributu může být různý, ale kterýkoli z typů může být vždy reprezentován jako řetězec. Z hlediska filtrování a řazení to však není nejlepší volba, protože obecně platí, že porovnávání řetězců není totéž jako porovnávání zakódovaných hodnot. Takže například při porovnání řetězců ‘2,11’ a ‘11,2’ a porovnání čísel 2.11 a 11.2 dostaneme přesně opačný výsledek – řetězec ‘2,11’ je “větší” než řetězec ‘11,2’, ale číslo 2,11 je menší než číslo 11,2.

ČTĚTE VÍCE
Co ukazuje indikátor oxidace manganistanu v analýze vody?

Dalším problémem při používání řetězců je to, že stejná data mohou být zakódována odlišně. Myslím, že mnoho vývojářů se setkalo s problémy způsobenými různými formáty data: 05, 29–2022–2022, 05 – stejné datum, ale různé řetězce.

Hodnoty atributů mohou být navíc složitější struktury, jako jsou seznamy, a pokud jsou zakódovány jako jeden řetězec, stanou se takové hodnoty na úrovni databáze téměř neovladatelné.

Vzhledem ke všemu výše uvedenému se podívejme na novou strukturu tabulky:

Nyní tabulka hodnot ProductAtribute má jeden sloupec pro každý typ atributu (odkaz, číslo, datum) a hodnoty seznamu se přesunou do samostatné tabulky ProductAttributeItem.

Pomocí této struktury tabulky se můžeme pokusit vybrat některé objekty, které splňují určitá daná kritéria.

Filtrování v SQL

Podívejme se na příklad „komplexního filtru“:

Ve formě SQL to může být vyjádřeno jako:

( [1]/*Vendor*/ = 1/*Apple*/ AND [2]/*Internal Memory*/ >= 64 AND [2]/*Internal Memory*/ 

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

-Проанализировать то какие атрибуты используются в фильтре
-Присоединить таблицу значений ProductAttribute столько раз, сколько уникальных атрибутов присутствует в фильтре.
Фильтр из примера содержит два атрибута:

[1] Vendor — ссылка на перечисление;
[2] Internal Memory — число.

Таким образом, окончательный запрос должен содержать под-запрос, в котором ProductAttribute соединяется два раза с правильным псевдонимами столбцов:

SELECT [ATTRIBUTES].ProductId FROM ( SELECT [P].ProductId, [AT_1].ValueItem [1], [AT_2].ValueInt [2] FROM Product [P] LEFT JOIN ProductAttribute [AT_1] ON [AT_1].AttributeId = 1/*Vendor*/ AND [AT_1].ProductId = [P].ProductId LEFT JOIN ProductAttribute [AT_2] ON [AT_2].AttributeId = 2/*Internal Memory*/ AND [AT_2].ProductId = [P].ProductId ) [ATTRIBUTES] WHERE ( [1]/*Vendor*/ = 1/*Apple*/ AND [2]/*Internal Memory*/ >= 64 AND [2]/*Internal Memory*/ 

Как мы видим, исходное выражение фильтра (как и любое другое с такими же атрибутами) можно внедрить в созданный запрос, и фильтрация будет выполнена непосредственно в базе данных. Помимо того, что эта фильтрация будет достаточно эффективной (там же будут индексы, да?), мы также получаем возможность выделения диапазона в отсортированном наборе данных (пагинация с FETCH OFFSET), что потребовало бы наличия всех данных в памяти приложения в том случае, если бы фильтрация не производилась бы на уровне базы данных.

ČTĚTE VÍCE
Jak by mělo vypadat normální oko?

Построение динамического SQL

В теории это выглядит нормально, но не совсем понятно, как использовать этот подход в реальном приложении, так как сначала нужно каким-то образом решить следующие задачи:

-Преобразовать критерии фильтрации в булевское SQL выражение;
-Подготовить правильный подзапрос со всеми необходимыми соединениями таблиц.

Вряд ли используемая вами ORM достаточно гибкая, чтобы выполнить все эти манипуляции, и первая идея, которая приходит в голову, — это создавать динамические SQL-запросы в виде текста, что, конечно, возможно, но я бы рекомендовал использовать какие-нибудь SQL компоновщики, которые позволяют работать с синтаксическими деревьями— это было бы гораздо более безопасным и гибким решением.

Далее в качестве примера я буду использовать библиотеку SqExpress для платформы .Net.
Конечно, вы можете использовать и другие подобные библиотеки — принципы останутся прежними.

Итак, начнем с построения логического выражения:
Примечание: здесь фильтр “захардкожен”, но ничто не мешает создать его динамически из какой-либо модели (фильтра).

var vendor = SqQueryBuilder.Column("1"); var internalMemory = SqQueryBuilder.Column("2"); ExprBoolean filter = vendor == 1; filter = filter & internalMemory >= 64 & internalMemory 

Небольшая проверка, что это то, что мы хотим:

Console.WriteLine(TSqlExporter.Default.ToSql(filter)); //[1]=1 AND [2]>=64 AND [2]

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

List filterAttributes = filter .SyntaxTree() .DescendantsAndSelf() .OfType() .Select(c => int.Parse(c.ColumnName.Name)) .Distinct() .ToList(); foreach(var filterAttribute in filterAttributes) < Console.WriteLine(filterAttribute); >//1 //2

Теперь когда мы знаем все идентификаторы динамических атрибутов в фильтре, мы можем получить их типы:

var tblAttribute = AllTables.GetAttribute(); Dictionary typesDict = await SqQueryBuilder .Select(tblAttribute.AttributeId, tblAttribute.AttributeType) .From(tblAttribute) .Where(tblAttribute.AttributeId.In(filterAttributes)) .QueryDictionary( database, r => tblAttribute.AttributeId.Read(r), r => (AttributeType)tblAttribute.AttributeType.Read(r));

Используя информацию об атрибутах, мы можем построить подзапрос, в котором таблица ProductAttribute будет присоединена один раз для каждого уникального атрибута:

var tblProduct = AllTables.GetProduct(); var subQueryColumns = new List < tblProduct.ProductId >; var subQuerySelect = SqQueryBuilder .Select(subQueryColumns) .From(tblProduct); foreach (var filterAttributeId in filterAttributes) < var tblProductAttribute = AllTables.GetProductAttribute(); subQuerySelect = subQuerySelect.LeftJoin( tblProductAttribute, on: tblProductAttribute.ProductId == tblProduct.ProductId & tblProductAttribute.AttributeId == filterAttributeId); switch (typesDict[filterAttributeId]) < case AttributeType.Integer: subQueryColumns.Add(tblProductAttribute.ValueInt.As(filterAttributeId.ToString())); break; case AttributeType.Set: subQueryColumns.Add(tblProductAttribute.ValueItem.As(filterAttributeId.ToString())); break; case AttributeType.Date: subQueryColumns.Add(tblProductAttribute.ValueDate.As(filterAttributeId.ToString())); break; >>

Собирая всё вместе:

var subQuery = subQuerySelect.As(SqQueryBuilder.TableAlias("ATTRIBUTES")); var query = SqQueryBuilder .Select(tblProduct.ProductName) .From(tblProduct) .InnerJoin( subQuery, on: tblProduct.ProductId == tblProduct.ProductId.WithSource(subQuery.Alias)) .Where(filter) .Done(); await query.Query(database, r => Console.WriteLine(tblProduct.ProductName.Read(r))); //iPhone 11 128 //Galaxy Note 20 Ultra

Это всё прекрасно работает с простыми атрибутами, но если тип атрибута это «Set» (значение представляет собой набор элементов), то потребуется чуть больше усилий. Например, мы хотим фильтровать по сотовым протоколам:

var cProtocols = SqQueryBuilder.Column("3"); filter = cProtocols.In(3, 5); Console.WriteLine(TSqlExporter.Default.ToSql(filter)); //[3] IN(3,5)

Для выполнения этой фильтрации в базе данных, этот фильтра должен быть изменен следующим образом: оператор IN должен быть заменен подзапросом EXISTS для того, чтобы использовалась таблица ProductAttributeItem. Это можно сделать, изменив синтаксическое дерево исходного фильтра:

filter = (ExprBoolean)filter.SyntaxTree().Modify(exprInValues => < var column = (ExprColumn)exprInValues.TestExpression; var columId = int.Parse(column.ColumnName.Name); var t = AllTables.GetProductAttributeItem(); return SqQueryBuilder.Exists( SqQueryBuilder .SelectOne() .From(t) .Where( t.AttributeId == columId & t.ProductId == tblProduct.ProductId & t.AttributeItemId.In(exprInValues.Items))); >)!; Console.WriteLine(TSqlExporter.Default.ToSql(filter));
EXISTS ( SELECT 1 FROM [dbo].[ProductAttributeItem] [A0] WHERE [A0].[AttributeId]=3 AND [A0].[ProductId]=[A1].[ProductId] AND [A0].[AttributeItemId] IN(3,5) )

После модификации фильтр можно использовать в запросе:

await SqQueryBuilder .Select(tblProduct.ProductName) .From(tblProduct) .Where(filter) .Query(database, r => Console.WriteLine(tblProduct.ProductName.Read(r)));

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

  • SqGoods — это демо веб-приложение, демонстрирующее динамическую фильтрацию динамических сущностей;
  • Исходный код для этой статьи
  • SqExpress — библиотека для создания динамического SQL, которая была использована в примерах в этой статье.
ČTĚTE VÍCE
Jak se jmenuje ještěrka, která vypadá jako drak?

Образование: Мастер делового администрирования – Master of Bussines Administration (MBA) по специальности Инновационно-инвестиционный менеджмент.

Опыт работы: лет

Краткая биография:

Рекламные кампании Ивана опубликованы в библиотеках лучших кейсов Google и Яндекс. Имеет опыт преподавания интернет-маркетинга в МГИМО, Moscow business school, BrainWashing, РАГС, ВШЭ, МИСиС, IHSBM. Обладатель степени MBA. Сертифицированный специалист Яндекс.Директ, Google Ads, Яндекс.Метрика, Google Analytics, К50: Генератор, Youtube, DBM. Спикер РИФ+КИБ, Оборот.ру, СПИК, MIXX.

В портфолио эксперта увеличение прибыли Gett, ЛитРес, Амедиатека, Hansa и других проектов. Владеет несколькими собственными e-commerce проектами. Является сертифицированным партнером по обучению Яндекс.

Автор книги «Библия интернет-маркетолога» ISBN: 978-5-04-165202-9

Редко у кого найдется время для просмотра всех 2563 моделей женских платьев, представленных на сайте.

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

Что это?

Это техническая функция, которая позволяет пользователям сузить результаты поиска с помощью заранее определенной группы категорий. Благодаря динамическим фильтрам пользователи могут настраивать результаты поиска в соответствии с их предпочтениями. Посетители сайта могут сузить область поиска всего за несколько кликов и выбрать из 15 товаров, соответствующих их критериям, вместо тысячи случайных предметов.

Как работают динамические фильтры: пользователь вводит свой запрос в строку поиска. Затем фильтры формируются из характеристик релевантных товаров по этому запросу без привязки к каталогу. Товары при этом могут быть из разных категорий. Вы можете сортировать товарные позиции по:

  • категориям;
  • наличию;
  • ценам;
  • брендам;
  • и другим параметрам.

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

Фильтры VS. Динамические фильтры

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

Фильтрация – это процесс сужения поиска на основе заранее определенных категорий. Часто эти категории являются широкими. Фильтры – это общие категории, определяемые бизнесом, которые не меняются между поисками. Например, интернет-магазин одежды будет использовать «тип одежды» в качестве фильтра с четырьмя возможными категориями рубашек, брюк, обуви и аксессуаров. Когда посетитель сайта нажимает на «рубашки» в верхней части навигации, применяется фильтр по типу одежды, и посетитель видит страницу результатов только с рубашками.

ČTĚTE VÍCE
Co jí doma tarantule?

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

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

Для крупных интернет-магазинов динамические фильтры – мастхэв. Если в вашем каталоге сотни или тысячи страниц, динамические фильтры очень помогут в поиске. Пользователям нужны способы одновременного поиска по нескольким параметрам, чтобы они могли легко находить продукты и услуги. Но, к примеру, если ваш сайт предлагает всего несколько десятков товаров, нескольких широких фильтров может быть достаточно.

Мы в SearchBooster убедились в важности динамических фильтров: они делают сайты более удобными и обеспечивают динамичный и интуитивно понятный интерфейс, к которому пользователи хотят вернуться.

Динамические фильтры могут стать дополнительными затратами при разработке сайта, но справедливо сказать, что они того стоят. Если вы ищете комплексное решение, которое выведет ваш магазин на новый уровень, то можно обратить внимание на готовые решения. SearchBooster предлагает готовую функцию динамических фильтров в дополнение к расширенной поисковой системе по сайту – никаких дополнительных затрат. Благодаря интеллектуальным инструментам SearchBooster ваши клиенты смогут легко фильтровать результаты поиска и сэкономить много времени и усилий.

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

Какие возможности предлагает SearchBooster:

  • Неограниченное количество настраиваемых фильтров
  • Динамическая фильтрация по цене, типу продукта, коллекциям, тегам, продавцу, цвету, размеру, рейтингу продукта, доступности
  • Фильтры с множественным выбором
  • Быстрый просмотр, кнопки «Добавить в корзину»
  • Оптимизация для мобильных устройств
ČTĚTE VÍCE
Kolikrát denně byste měli krmit svou drápatou žábu?

Динамические фильтры помогают существенно повысить качества и удобство использования поиска и повышают конверсию сайта. Хотите узнать еще больше о функциях умного поиска?

Тогда регистрируйтесь, и получите 14-дневный пробный период, чтобы оценить новые возможности вашего магазина.