2017-02-17 9 views
1

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

Структура данных (Simple много-ко-многим):

ProductAttribute Table 
------ 
ProductId 
AttributeId 

Attribute Table 
------- 
AttributeId 
AttributeGroupId 

AttributeGroup Table 
------- 
AttributeGroupId 

Основная проблема здесь состоит в том, что на стороне клиента, они могут выбрать столько фильтров, сколько они хотят. Однако для каждого атрибута в группе это своего рода «или» выбор, в то время как атрибуты в других группах - «и».

Пример: AttributeGroup Цвет: красный, зеленый AttributeGroup Длина: Длинные AttributeGroup Материал: шелк, лен

Поэтому в основном я должен вернуть все продукты, которые соответствуют Красный/Long/шелк, красный/Long/Linen , Зеленый/длинный/шелковый, зеленый/длинный/льняной.

В настоящее время хранимые процедуры анализируют каждую из этих групп индивидуально в советском стиле, сверхдолгий процесс, который выполняет свою работу. Сначала создайте временную таблицу productIds, которая соответствует первой группе (цвет), а затем удалите идентификаторы, которые не соответствуют последовательным фильтрам AttributeGroup. Пример: Дайте мне все красные и зеленые продукты. Затем удалите любые продукты, которые также не являются «длинными». Теперь удалите оставшиеся продукты, которые также не являются Linen или Silk.

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

Данные могут передаваться любым способом, но в настоящее время это строка с несколькими разделителями, которая анализируется в таблице с использованием функции sql. AttributeGroupId-AttributeId, AttributeId | AttributeGroupId-AttributeId, AttributeId Пример: 1-104,114 | 2-125,140 | 3-215,317

EDIT: Пример того, как входные данные разобраны

AttributeGroupId| Attribute Id 
------------------------------ 
1    | 104 
1    | 114 
2    | 125 
2    | 140 
3    | 215 
3    | 317 

Не вдаваясь глубоко в текущие процедуры, любые рекомендации о том, как лучше всего решить эту проблему?

+0

без деталей о реализации, как может кто-нибудь предложить любую помощь, чтобы улучшить производительность? –

+0

Если атрибут AttributeId уникален, атрибут AttributeGroupID бессмыслен в поисковой программе. Похоже, вам просто нужен список AttributeId, так как это то, что связано с продуктом. «Дайте мне все продукты, включенные в этот список атрибутов». –

+0

Я бы хотел перепроектировать его, так как его реализация в настоящее время не имеет большого значения. – citizenkraft

ответ

0

В зависимости от количества имеющихся у вас атрибутов вы можете использовать стратегию junk dimension. Будут дубликаты в пределах одного столбца, но комбинация всех столбцов будет уникальной. Другими словами, у вас будет несколько записей для красного цвета, но у вас будет только один красный/длинный/шелковый.

Число записей - это количество уникальных комбинаций во всех столбцах.
Все фильтры будут против этой же таблицы, с одним соединением с таблицей ProductAttribute. В вашем примере вы можете сделать каждую группу столбцом, например цветом. В пределах одного столбца они могут использовать список значений со значением для «ИЛИ». По колонкам будет выбран «И».

Select AttributesID, Color, Length, Material From Attributes 
Where Color in (@Color) and Length in (@Length) AND Material in (@Material) 
+0

Спасибо, Уэс: Я рассмотрю значение нежелательной почты. Текущий proc использует второй подход стиля, который требует обслуживания каждый раз, когда добавляется новая группа атрибутов. Это также громоздкая 500-строчная процедура дублированного кода, которую я пытаюсь реорганизовать и уничтожить. – citizenkraft

+0

Не уверен, что вы подразумеваете под вторым стилем. Этот подход требует обслуживания каждый раз, когда добавляется новая группа атрибутов, но не при добавлении нового атрибута в группу. Как часто вы добавляете новые группы? –

+0

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

0

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

ПРИМЕЧАНИЕ. Подобный запрос, скорее всего, отправит оптимизатор запросов на орбиту, и произойдут ужасные вещи. Если вы реализуете что-то подобное, вам нужно установить опцию или тест WITH RECOMPILE с помощью OPTION (OPTIMIZE FOR UNKNOWN)

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

DECLARE @Attributes TABLE(AttributeGroupID INT,AttributeID INT) 
INSERT @Attributes SELECT 1,104--COLOR 
INSERT @Attributes SELECT 1,114--COLOR 
INSERT @Attributes SELECT 2,125--LENGTH 
INSERT @Attributes SELECT 2,140--LENGTH 
INSERT @Attributes SELECT 3,215--MATERIAL 
INSERT @Attributes SELECT 3,317--MATERIAL 

DECLARE @AttributeGroup TABLE(AttributeGroupID INT) 
INSERT @AttributeGroup SELECT 1--COLOR 
INSERT @AttributeGroup SELECT 2--LENGTH 
INSERT @AttributeGroup SELECT 3--MATERIAL 

DECLARE @Product TABLE(ProductID INT) 
INSERT @Product SELECT 1 
INSERT @Product SELECT 2 


DECLARE @ProductAttribute TABLE(ProductID INT,AttributeID INT) 
INSERT @ProductAttribute SELECT 1,104 
INSERT @ProductAttribute SELECT 1,125 
INSERT @ProductAttribute SELECT 1,317 
INSERT @ProductAttribute SELECT 2,114 
INSERT @ProductAttribute SELECT 2,125 
INSERT @ProductAttribute SELECT 2,215 


DECLARE @Filter TABLE(AttributeGroupID INT,AttributeID INT) 
INSERT @Filter SELECT 1,104 
INSERT @Filter SELECT 2,125 
--INSERT @Filter SELECT 3,317 

SELECT 
    P.ProductID, 
    MatchingAttrinuteCount=COUNT(*) 
FROM 
    @Product P 
    INNER JOIN @ProductAttribute PA ON PA.ProductID=P.ProductID 
    INNER JOIN @Attributes A ON A.AttributeID=PA.AttributeID 
WHERE 
    (A.AttributeGroupID<>1 OR(A.AttributeGroupID=1 AND ((NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=1))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=1)))) 
    AND 
    (A.AttributeGroupID<>2 OR(A.AttributeGroupID=2 AND ((NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=2))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=2)))) 
    AND 
    (A.AttributeGroupID<>3 OR(A.AttributeGroupID=3 AND ((NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=3))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=3)))) 
GROUP BY 
    P.ProductID 

Запроса выше будет выход:

ProductID MatchingAttrinuteCount 
1   3 
2   2 

Вторым и, возможно, более производительный метод ...

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

DECLARE @Filter TABLE(AttributeGroupID INT,AttributeID INT) 
INSERT @Filter SELECT 1,104 
INSERT @Filter SELECT 2,125 
INSERT @Filter SELECT 1,114 
SET NOCOUNT ON 

DECLARE @Matches TABLE(ProductID INT,AttributeID INT) 
DECLARE @AttributeID INT, @AttributeGroupID INT, @HasLooped BIT=0 
--GROUPS TO LOOP FOR 
DECLARE ATTRIBUTE_GROUP_CURSOR CURSOR FOR SELECT DISTINCT AttributeGroupID FROM @Filter 
OPEN ATTRIBUTE_GROUP_CURSOR 
FETCH NEXT FROM ATTRIBUTE_GROUP_CURSOR INTO @AttributeGroupID 

--FOR EACH GROUP [COLOR..MATERIAL] IN THE FILTER 
WHILE(@@FETCH_STATUS=0)BEGIN 

    INSERT @Matches 
    --INSERT ALL PRODUCTS WITH MATCHING ATTRIBUTES IN THIS GROUP 
    --IF FIRST LOOP EXAMINE ALL PRODUCTS IF NOT EXAMINE ONLY PRODUCTS ALREADY IN MATCH SET 
    SELECT P.ProductID, PA.AttributeID 
    FROM @Product P 
    INNER JOIN @ProductAttribute PA ON PA.ProductID=P.ProductID 
    WHERE 
     AttributeID IN (SELECT AttributeID FROM @Filter F WHERE [email protected]) 
     AND 
     (@HasLooped=0 OR P.ProductID IN (SELECT ProductID FROM @Matches)) --Only match products if one filter has been applied 

    SET @HasLooped=1 

    FETCH NEXT FROM ATTRIBUTE_GROUP_CURSOR INTO @AttributeGroupID 

END 

CLOSE ATTRIBUTE_GROUP_CURSOR 
DEALLOCATE ATTRIBUTE_GROUP_CURSOR 

SELECT * FROM @Matches 

Уступая ...

ProductID AttributeID 
1   104 
2   114 
1   125 
2   125 
+0

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

+0

Метод курсора был примерно на 500 мс медленнее, чем существующий proc. 300мс против 800 мс. Хотя код был гораздо более кратким. = \ – citizenkraft

+0

citizenkraft - вам понадобится N соединений или N подзапросов в набор фильтров. В Приложении выше используется первое. Wes H корректно работает с курсором. Его следует избегать, поскольку он обойдутся оптимизациями, которые могут быть установлены на основе операций на основе, однако, если ваша таблица продуктов состоит из тысяч записей, а не ОГРОМНЫХ, потеря производительности может быть незначительной. Это для вас, чтобы проверить. Вы не указали, насколько велики таблицы. Мне было бы интересно узнать, как работает решение where where. –