многие ко многим выберите запрос - PullRequest
5 голосов
/ 21 сентября 2010

Я пытаюсь написать код для извлечения списка элементов продукта из базы данных SQL Server и отображения результатов на веб-странице.

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

Вот где оно становится немного волосатым.

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

Product table
[product_id](PK),[product_name],[product_price],[isEnabled],etc...

Category table
[CategoryID](PK),[CategoryName]

ProductCagetory table

[id](PK),[CategoryID](FK),[ProductID](FK)

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

Идентификаторы категории передаются в proc как разделенный запятыми varchar, то есть (3,5,8,12)

SQL разбивает это значение varchar на набор результатов во временной таблице для обработки.

Как мне написать этот запрос?

Ответы [ 5 ]

2 голосов
/ 21 сентября 2010

Одной из проблем является передача массива или списка выбранных категорий на сервер. Эланд Соммарског широко освещал эту тему в серии статей Массивы и списки в SQL Server . Передача списка в виде строки, разделенной запятыми, и создание временной таблицы - один из вариантов. Существуют альтернативы, такие как использование XML или Table-Valued-Parameter (в SQL Server 2008) или использование таблицы @variable вместо таблицы #temp. Плюсы и минусы каждого раскрыты в статьях, на которые я ссылаюсь.

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

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

select ...
from Products p
where p.IsEnabled = 1
and exists (
  select 1  
  from ProductCategories pc
  join #selectedCategories sc on sc.CategoryID = pc.CategoryID
  where pc.ProductID = p.ProductID);

У ProductsCategoriestable индекс должен быть (ProductID, CategoryID), а другой - (CategoryID, ProductID) (один из них кластеризованный, другой - NC). Это верно для каждого решения между прочим. Этот запрос будет работать, если всегда выбраны большинство категорий, а результат в любом случае содержит большинство продуктов. Но если список выбранных категорий ограничен, то лучше избегать сканирования в потенциально большой таблице «Продукты» и начинать с выбранных категорий:

with distinctProducts as (
select distinct pc.ProductID
from ProductCategories pc
join #selectedCategories sc on pc.CategoryID = sc.CategoryID)
select p.*
from Products p
join distinctProducts dc on p.ProductID = dc.ProductID;

Опять же, лучшее решение во многом зависит от формы ваших данных. Например, если у вас есть очень искаженная категория (одна категория покрывает 99% продуктов), тогда наилучшее решение должно учитывать этот перекос.

1 голос
/ 21 сентября 2010

Получаются все продукты, которые не менее во всех требуемых категориях (не менее):

select * from product p1 join (
  select p.product_id from product p 
  join ProductCategory pc on pc.product_id = p.product_id
  where pc.category_id in (3,5,8,12)
  group by p.product_id having count(p.product_id) = 4
) p2 on p1.product_id = p2.product_id

4 - это количество категорий в наборе.

Это получает все продукты, которые точно во всех желаемых категориях (не больше, не меньше):

select * from product p1 join (
  select product_id from product p1 
  where not exists (
    select * from product p2 
    join ProductCategory pc on pc.product_id = p2.product_id
    where p1.product_id = p2.product_id
    and pc.category_id not in (3,5,8,12)
  )
  group by product_id having count(product_id) = 4
) p2 on p1.product_id = p2.product_id

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

Для продуктов в любых желаемых категорий, это так просто, как:

select * from product p1 where exists (
  select * from product p2 
  join ProductCategory pc on pc.product_id = p2.product_id
  where 
    p1.product_id = p2.product_id and
    pc.category_id in (3,5,8,12)
)
0 голосов
/ 21 сентября 2010

Это должно быть достаточно близко к тому, что вы ищете

SELECT product.*
FROM   product
JOIN   ProductCategory ON ProductCategory.ProductID = Product.product_id
JOIN   #my_temp ON #my_temp.category_id = ProductCategory.CategoryID

EDIT

Как отмечено в комментариях, это приведет к дублированию для тех продуктов, которые отображаются в нескольких категориях. Чтобы исправить это, укажите DISTINCT перед списком столбцов. Я включил все столбцы продукта в список product.*, так как я не знаю, какие столбцы вы ищете, но вам, вероятно, следует изменить это на конкретные столбцы, которые вы хотите

0 голосов
/ 21 сентября 2010

Если вам нужно что-то еще, кроме product_id из продуктов, вы можете написать что-то вроде этого (и добавить дополнительные поля, которые вам нужны):

SELECT distinct(p.product_id)
FROM product_table p
JOIN productcategory_table pc
ON p.product_id=pc.product_id
WHERE pc.category_id in (3,5,8,12);

с другой стороны, если вам действительно нужен просто product_id, вы можете просто выбрать их из productcategory_table:

SELECT distinct(product_id)
FROM productcategory_table
WHERE category_id in (3,5,8,12);
0 голосов
/ 21 сентября 2010

Это должно сделать. Не нужно ломать идентификаторы категории, разделенные запятыми.

select distinct p.* 
from product p, productcategory pc
where p.product_id = pc.productid
and pc.categoryid in ( place your comma delimited category ids here)

Это даст продуктам, которые находятся в любом из переданных в категории идентификаторов, т.е. согласно комментарию JNK, это ИЛИ НЕ ВСЕ. Укажите, хотите ли вы AND, т. Е. Продукт нужно выбирать, только если он находится во ВСЕХ категориях, указанных в списке через запятую.

...