我有一个产品表,其中包含一个类别的FK,Categories表以每个类别可以拥有父类别的方式创建,例如:
Computers
Processors
Intel
Pentium
Core 2 Duo
AMD
Athlon
我需要进行一个选择查询,如果所选类别是处理器,它将返回Intel,Pentium,Core 2 Duo,Amd等产品......
我考虑过创建某种“缓存”,它将为数据库中的每个类别存储层次结构中的所有类别,并在where子句中包含“IN”。这是最好的解决方案吗?
答案 0 :(得分:6)
最佳解决方案是在数据库设计阶段。您的类别表必须是嵌套集。文章Managing Hierarchical Data in MySQL不是特定于MySQL的(尽管标题),并且非常概述了在数据库表中存储层次结构的不同方法。
因此,根据您的示例,如果您的层次结构表是嵌套集,则查询将如下所示:
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”这些类别的产品。获取类别是很难的,你有几个选择:
如果您不想执行此应用程序,存储过程可以提供帮助。
答案 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
本书中有一些方法可以很好地涵盖你的情况。