多对多选择查询

时间:2010-09-21 17:11:58

标签: sql sql-server database join

我正在尝试编写代码以从SQL Server数据库中提取产品项列表,并在网页上显示结果。

项目的要求是在页面的右侧显示类别列表作为复选框列表(默认选择所有类别),用户可以取消选中类别并重新查询数据库以查看产品只是他们想要的类别。

Heres开始变得有点毛茸茸。

使用以下产品类别表可以将每个产品分成多个类别......

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

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

ProductCagetory table

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

我需要选择一个产品列表,这些产品与传递给我的存储过程的一组类别ID相匹配,其中产品有多个已分配的类别。

将类别ID作为逗号分隔的varchar传递给proc,即(3,5,8,12)

SQL将此varchar值分解为临时表中的结果集以进行处理。

我如何写这个查询?

5 个答案:

答案 0 :(得分:2)

一个问题是将所选类别的数组或列表传递到服务器中。 Eland Sommarskog在一系列文章Arrays and Lists in SQL Server中对该主题进行了全面介绍。将列表作为逗号分隔的字符串传递并构建临时表是一种选择。还有其他选择,例如使用XML或Table-Valued-Parameter(在SQL Server 2008中)或使用表@variable而不是#temp表。我所链接的文章涵盖了每种方法的优缺点。

现在讨论如何检索产品。首先要做的事情是:如果选择了所有类别,那么使用一个不同的查询,只需检索所有产品,而无需根据类别进行操作。这样可以节省 lot 的性能,并且考虑到所有用户可能会首先看到没有选择任何类别的页面,节省的费用可能很高。

当选择 类别时,构建连接产品,类别和所选类别的查询相当容易。使其扩展和执行是一个不同的主题,完全取决于您的数据模式和所选类别的实际模式。一个天真的方法是这样的:

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)。这对每个解决方案都是如此。如果始终选择大多数类别且结果包含大多数产品,则此查询将起作用。但是,如果所选类别的列表具有限制性,那么最好避免对可能较大的Products表进行扫描,并从所选类别开始:

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 :(得分:1)

这样可以获得所有所需类别中至少的所有产品(不低于):

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)
)

答案 2 :(得分:-1)

这应该做。哟不必打破逗号分隔的类别ID。

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)

这将给出任何传递类别ID的产品,即根据JNK的评论,它是OR而不是ALL。请指定是否需要AND,即只有在逗号分隔列表中指定的所有类别中才需要选择产品。

答案 3 :(得分:-1)

如果您需要产品中的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);

答案 4 :(得分:-2)

这应该与您正在寻找的相当接近

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

修改

如评论中所述,这将为出现在多个类别中的产品产生重复。要更正此问题,请在列列表前指定DISTINCT。我在列表product.*中包含了所有产品列,因为我不知道您要查找哪些列,但您应该将其更改为您想要的特定列