选择类别属于层次结构中任何类别的产品

时间:2008-10-13 12:10:41

标签: sql sql-server select hierarchy

我有一个产品表,其中包含一个类别的FK,Categories表以每个类别可以拥有父类别的方式创建,例如:

Computers
    Processors
        Intel
            Pentium
            Core 2 Duo
        AMD
            Athlon

我需要进行一个选择查询,如果所选类别是处理器,它将返回Intel,Pentium,Core 2 Duo,Amd等产品......

我考虑过创建某种“缓存”,它将为数据库中的每个类别存储层次结构中的所有类别,并在where子句中包含“IN”。这是最好的解决方案吗?

9 个答案:

答案 0 :(得分:6)

最佳解决方案是在数据库设计阶段。您的类别表必须是嵌套集。文章Managing Hierarchical Data in MySQL不是特定于MySQL的(尽管标题),并且非常概述了在数据库表中存储层次结构的不同方法。

执行摘要:

嵌套集

  • 选择对任何深度都很容易
  • 插入和删除很难

基于标准parent_id的层次结构

  • 选择基于内连接(因此快速生长)
  • 插入和删除很容易

因此,根据您的示例,如果您的层次结构表是嵌套集,则查询将如下所示:

SELECT * FROM products 
   INNER JOIN categories ON categories.id = products.category_id 
WHERE categories.lft > 2 and categories.rgt < 11

2和11分别是Processors记录的左右两种。

答案 1 :(得分:3)

看起来像是一个Common Table Expression的工作......类似于:

with catCTE (catid, parentid)
as
(
select cat.catid, cat.catparentid from cat where cat.name = 'Processors'
UNION ALL
select cat.catid, cat.catparentid from cat inner join catCTE on cat.catparentid=catcte.catid
)
select distinct * from catCTE

那应该选择名称为'Processors'的类别及其任何后代,应该能够在IN子句中使用它来撤回产品。

答案 2 :(得分:0)

我过去做过类似的事情,首先查询类别ID,然后查询“IN”这些类别的产品。获取类别是很难的,你有几个选择:

  • 如果已知类别的嵌套级别,或者您可以找到上限:使用大量JOIN构建一个看起来很糟糕的SELECT。这很快,但很难看,你需要对层次结构的级别设置限制。
  • 如果您的总类别数量相对较少,请查询所有类别(仅限ID,父母),收集您关注的ID的ID,并为产品执行SELECT .... IN。这对我来说是合适的选择。
  • 使用一系列SELECT查询/缩小层次结构。简单,但相对较慢。
  • 我相信SQLServer的最新版本对递归查询有一些支持,但我自己没有使用它们。

如果您不想执行此应用程序,存储过程可以提供帮助。

答案 3 :(得分:0)

您想要找到的是“父”关系类别的传递闭包。我认为类别层次结构深度没有限制,因此您无法制定查找所有类别的单个SQL查询。我会做什么(伪代码)是这样的:

categoriesSet = empty set
while new.size > 0:
  new = select * from categories where parent in categoriesSet
  categoriesSet = categoriesSet+new

所以请继续查询孩子,直到找不到为止。除非您具有退化的层次结构(例如,1000个类别,每个类别都是另一个类别的孩子)或大量总类别,否则这在速度方面表现良好。在第二种情况下,您始终可以使用临时表来保持应用程序和数据库之间的数据传输较小。

答案 4 :(得分:0)

可能是这样的:

select *
from products
where products.category_id IN
  (select c2.category_id 
   from categories c1 inner join categories c2 on c1.category_id = c2.parent_id
   where c1.category = 'Processors'
   group by c2.category_id)

[编辑]如果类别深度大于1,这将构成您最内层的查询。我怀疑您可以设计一个存储过程,该存储过程将在表中向下钻取,直到内部查询返回的ID没有子节点 - 可能最好有一个属性将类别标记为层次结构中的终端节点 - 然后对这些ID执行外部查询。

答案 5 :(得分:0)

CREATE TABLE #categories (id INT NOT NULL, parentId INT, [name] NVARCHAR(100))
INSERT INTO #categories
    SELECT 1, NULL, 'Computers'
    UNION
SELECT 2, 1, 'Processors'
    UNION
SELECT 3, 2, 'Intel'
    UNION
SELECT 4, 2, 'AMD'
    UNION
SELECT 5, 3, 'Pentium'
    UNION
SELECT 6, 3, 'Core 2 Duo'
    UNION
SELECT 7, 4, 'Athlon'
SELECT * 
    FROM #categories
DECLARE @id INT
    SET @id = 2
            ; WITH r(id, parentid, [name]) AS (
    SELECT id, parentid, [name] 
        FROM #categories c 
        WHERE id = @id
        UNION ALL
    SELECT c.id, c.parentid, c.[name] 
        FROM #categories c  JOIN r ON c.parentid=r.id
    )
SELECT * 
    FROM products 
    WHERE p.productd IN
(SELECT id 
    FROM r)
DROP TABLE #categories   

如果您像这样直接运行,示例的最后一部分实际上并不起作用。只需从产品中删除选择,然后使用简单的SELECT * FROM r

替换

答案 6 :(得分:0)

这应该从给定类别开始递归所有“子”类别。

答案 7 :(得分:0)

我喜欢使用堆栈临时表来分层数据。 这是一个粗略的例子 -

-- create a categories table and fill it with 10 rows (with random parentIds)
CREATE TABLE Categories ( Id uniqueidentifier, ParentId uniqueidentifier )
GO

INSERT
INTO   Categories
SELECT NEWID(),
       NULL 
GO

INSERT
INTO   Categories
SELECT   TOP(1)NEWID(),
         Id
FROM     Categories
ORDER BY Id
GO 9


DECLARE  @lvl INT,            -- holds onto the level as we move throught the hierarchy
         @Id Uniqueidentifier -- the id of the current item in the stack

SET @lvl = 1

CREATE TABLE #stack (item UNIQUEIDENTIFIER, [lvl] INT)
-- we fill fill this table with the ids we want
CREATE TABLE #tmpCategories (Id UNIQUEIDENTIFIER)

-- for this example we’ll just select all the ids 
-- if we want all the children of a specific parent we would include it’s id in
-- this where clause
INSERT INTO #stack SELECT Id, @lvl FROM Categories WHERE ParentId IS NULL

WHILE @lvl > 0
BEGIN -- begin 1

      IF EXISTS ( SELECT * FROM #stack WHERE lvl = @lvl )
      BEGIN -- begin 2

      SELECT @Id = [item]
      FROM #stack
      WHERE lvl = @lvl

      INSERT INTO #tmpCategories
      SELECT @Id

      DELETE FROM #stack
      WHERE lvl = @lvl
      AND item = @Id

      INSERT INTO #stack
      SELECT Id, @lvl + 1
      FROM   Categories
      WHERE  ParentId = @Id

      IF @@ROWCOUNT > 0
      BEGIN -- begin 3
         SELECT @lvl = @lvl + 1
      END -- end 3
   END -- end 2
   ELSE
   SELECT @lvl = @lvl - 1

END -- end 1

DROP TABLE #stack

SELECT * FROM #tmpCategories
DROP TABLE #tmpCategories
DROP TABLE Categories

这里有一个很好的解释link text

答案 8 :(得分:0)

我几天前对另一个问题的回答适用于此...... recursion in SQL

本书中有一些方法可以很好地涵盖你的情况。