Pull to refresh

Comments 21

Странно было во всей статье так и не найти термина "фасетный поиск". Как, впрочем, и аббревиатуры EAV.

Странно видеть утверждение "мы сосредоточимся только на базах данных SQL", хотя практически весь код - это .Net, причём даже не собственно какой-то из языков, а фреймворк.

Фраза "а значения-списки перемещены в отдельную таблицу ProductAttributeItem" просто изумила.

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

Совсем не понял обоснования того, что "оператор IN должен быть заменен подзапросом EXISTS" - если не считать специальных случаев (NULL), то WHERE IN и коррелированный WHERE EXISTS по результату эквивалентны (но не всегда - по плану выполнения). Или это опять именно потому, что всё происходит во фреймворке, который не умеет?

В общем, вопросов осталось больше, чем было получено ответов.

Да! ещё одно. Очень бы хотелось видеть текст SQL-запроса, который будет сгенерирован фреймворком на основании предложенного кода. Чтобы сравнить с тем, что напишет SQL-программист...

Идея мягко сказать не пахнет инновационностью.

В общем случае, если у нас не чистый SQL, а все равно есть бэк, то ничто не мешает нам:

1) Сделать отдельные таблицы для каждого существенного типа данных вида ObjectId, PropertyId, Value где value может быть например интом, валютой, датой или даблом.

2) Фильтры по пропертям возвращают ObjectId который джойнится или объеденяется в зависимости от операции

3) С итоговым списком ObjectId ведется работа, например возвращается простыня значений полей которая мапится на объекты уже на бэке.

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

о опять статья о том как создать свой EAV с блэк джэком и перфомансом.

рассказываю способ к которому можно придти от души потанцевав на граблях EAV.
учитывая что новые атрибуты добавляются значительно реже чем читаются продукты, можно сделать хранение атрибутов товара ВНЕЗАПНО в КОЛОНКАХ таблицы продуктов.

Тогда когда добавляется атрибут release date можно создать запрос на добавление колонки release date с типом int в таблицу product, таким образом можно добавлять сколько угодно атрибутов товарам мобильные телефоны, мы получим решение кучи проблем EAV.

Внимательные люди могут заметить, а что если у нас не только мобильные телефоны в продуктах, но и например телевизоры ? тогда надо в продуктах добавить type_id и для каждого тайпа хранить свою таблицу product_phones (product_id, ... тут самые расширенные атрибуты) product_tvs(product_id, тут дополнительные атрибуты), например если нужно добавить пользовательский атрибут всем телевизорам, то внезапно добавим его в product_tvs

Когда нужно будет получить список пользовательских атрибутов у типов товаров, можно сделать внезапно посмотреть структуру таблицы (в разных бд там будут разные запросы, но везде можно увидеть имена колонок и их типы и из этого списка убрать product_id).

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

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

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

А что делать если потребуется новый тип товара? Или отобрать товары разных типов по какому-то одному атрибуту? Цвет скажем или высота?

А что делать если потребуется новый тип товара?

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

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

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

Или отобрать товары разных типов по какому-то одному атрибуту?

Если есть такие требования, то такие атрибуты можно навешивать на таблицу product (общие для всех), а те атрибуты что относятся только к какой то группе товаров их навешивать на группу, тут уже структуру надо допиливать напильником.

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

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

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

Можно просто привязать атрибуты к категории товара. Это, собственно, и сделано в демонстрационном приложении SqGoods:

Можно просто привязать атрибуты к категории товара

тогда атрибуты будут не у товара, а у категории, а значения мы где будем хранить ?

в предложенном мной варианте категория товара воспринималась как тип и атрибуты я предложил добавлял типу товара, но только в виде колонок (product_tvs, product_phones), просто в магазинах может быть типа товара, он обычно один у товара, а могут быть категории это различные элементы меню где показывается товар и тут уже у товара будет связь не 1хN как у типов товаров, а NxM с категориями, поэтому не надо путать что является типом товара, а что категорией, у некоторых бывают и другие способы именования и структуры, речь тут совершенно не про категории или типы.

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

Но нет лучше товары хранить в одном месте, категории в другом, к категориям привязать атрибуты, а потом эти атрибуты связать с товарами и хранить это(значения) в другой сущности (буква V из EAV) и потом удивлятся на больших данных как это все работает не очень быстро и запросы какие то не интуитивные.

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

как я уже выше писал серебряной пули нет мы уже ушли в обсуждения категорий, вместо того чтобы признать определенные факты:
1. EAV далеко не лучший способ хранения информации в РСУБД
2. В некоторых случаях пользовательские атрибуты можно задавать колонками в таблице, а значение хранить в этой самой колонке у сущности (товаре), получается существенный выйгрышь в скорости чтения и удобстве фильтрации, цена этому возможные блокировки(таблицы) на этапе создание или удаление атрибутов - это бывает не во всех базах.

Не думали тупо в JSONB хранить аттрибуты? Это дает максимальную гибкость (можно замиксить любые типы товаров, сделать сложные аттрибуты и тд).

Нормально с JSON могут работать далеко не все SQL базы данных, и не понятно, что с производительностью (если делать индексы на JSON, то теряется динамичность).

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

И вы, конечно, можете подсказать подходящую базу?

postgres с типом jsonb

я просто сталкивался на практике с EAV.  Чтобы вытащить хоть что-нибудь из базы, нужно 3-4 джойна. Если же надо написать какую-то выборку посложнее, то тушите свет. Всё умирает: и база, и программист

и в итоге это везде выпиливалось в итоге после долгих мучений

Хм... а сталкивался на практике с прямо противоположной ситуацией, когда JSON выпиливался и внедрялся EAV как раз из-за проблем с производительностью - с EAV всё стало в тысячи раз быстрее (в буквальном смысле).

29.05.2022, 2022–05–22, 29.05.2022 — одна и та же дата, но разные строки.

Даты-то тоже разные, так-то. Во всяком случае вторая из них

Спасибо! Поправил. Это сработала авто-замена. Должно быть " 05/29/2022, 2022–05–22, 29.05.2022 " (MM/dd/yyyy, yyyy-MM-dd, dd.MM.yyyy)

Глубоко не втыкал, но, похоже, автора можно поздравить с изобретением EAV. Сейчас работаю с одним проектом где он повсюду - адово дрочево. Особенно учитывая, что все нынешние БД уже давно умеют работать с semistructured data в виде того же JSON.

я бы не стал так категорично критиковать EAV, он не плох и не хорош. Это просто один из способов хранить разнородные данные. У него есть свои преимущества относительно того же blob поля, например, скорость поиска.

@0x1000000. у тебя 5 таблиц.

  • Product — Список объектов, которые будут расширены динамическими атрибутами;

  • Attribute — Cписок динамических атрибутов (имя атрибута и его тип);

  • ProductAttribute — значения

  • таблица значений AttributeItem

  • значения-списки перемещены в отдельную таблицу ProductAttributeItem

.а что если я скажу что одна таблица лишняя?

  • Product товары

  • Attribute атрибуты товаров

  • AttributeItem значения атрибутов, как уникальные так и списочные. у тебя это ProductAttribute

  • ProductAttributeItem - m2m through таблица от продукта к значениям атрибутов.

Список для каждого списочного атрибута. Храни пометку типа "список" делай выборку Attribute - AttributeItem, по типу атрибута "список".

Выборка параметров каждого товара Product-ProductAttributeItem-AttributeItem

Ну и я не понял в статье, почему нельзя все сделать c filter без вложенных селектов только на where and, or. Похоже, что не ту таблицу спрашиваешь. ты спрашиваешь from Product, а надо from ProductAttributeItem.

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

filter без вложенных селектов

Идея в том, что фильтр приходит "извне", где не знают о структуре хранения данных, и предикат по полю "списку" (например [Protocol] IN ('4G', '5G')) может быть вложен внутрь сложного булевского выражения. Замена такого предиката на EXISTS(SELECT ...) позволит сохранить логику исходного фильтра.

Кто в проде работал с EAV, тот в цирке не смеется. Врагу не пожелаю такой подход.

Лучше уж в json хранить

Если важна производительность, то для данных с низкой cardinality (как у вас) стоит попробовать обратные индексы на битах. Тогда вся фильтрация сводится к простым операциям над массивами битов.

Sign up to leave a comment.

Articles