8

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

Computers
    Processors
        Intel
            Pentium
            Core 2 Duo
        AMD
            Athlon

我需要进行选择查询,如果选择的类别是处理器,它将返回英特尔、奔腾、酷睿 2 双核、AMD 等的产品...

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

4

9 回答 9

6

最好的解决方案是在数据库设计阶段。您的类别表需要是一个嵌套集。这篇文章在 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记录的左侧和右侧。

于 2008-10-13T13:35:55.573 回答
4

看起来像是公共表表达式的工作.. 类似于:

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

这应该选择名称为“处理器”的类别及其任何后代,应该能够在 IN 子句中使用它来拉回产品。

于 2008-10-13T13:35:16.120 回答
0

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

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

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

于 2008-10-13T12:20:13.137 回答
0

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

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

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

于 2008-10-13T12:24:08.900 回答
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)

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

于 2008-10-13T12:28:13.620 回答
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 替换

于 2008-10-13T12:56:45.620 回答
0

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

DECLARE @startingCatagoryId int
DECLARE @current int
SET @startingCatagoryId = 13813 -- or whatever the CatagoryId is for 'Processors'

CREATE TABLE #CatagoriesToFindChildrenFor
(CatagoryId int)

CREATE TABLE #CatagoryTree
(CatagoryId int)

INSERT INTO #CatagoriesToFindChildrenFor VALUES (@startingCatagoryId)

WHILE (SELECT count(*) FROM #CatagoriesToFindChildrenFor) > 0
BEGIN
    SET @current = (SELECT TOP 1 * FROM #CatagoriesToFindChildrenFor)

    INSERT INTO #CatagoriesToFindChildrenFor
    SELECT ID FROM Catagory WHERE ParentCatagoryId = @current AND Deleted = 0

    INSERT INTO #CatagoryTree VALUES (@current)
    DELETE #CatagoriesToFindChildrenFor WHERE CatagoryId = @current
END

SELECT * FROM #CatagoryTree ORDER BY CatagoryId

DROP TABLE #CatagoriesToFindChildrenFor
DROP TABLE #CatagoryTree
于 2008-10-13T13:40:41.867 回答
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

这里有一个很好的解释链接文本

于 2008-10-15T21:56:23.707 回答
0

几天前我对另一个问题的回答在这里适用... SQL 中的递归

我已经链接了书中的一些方法,它们应该很好地涵盖您的情况。

于 2008-10-15T22:08:24.103 回答