Pergunta

Eu tenho uma tabela produtos que contém uma FK para uma categoria, a tabela Categorias é criado de uma forma que cada categoria pode ter uma categoria pai, exemplo:

Computers
    Processors
        Intel
            Pentium
            Core 2 Duo
        AMD
            Athlon

Eu preciso fazer uma consulta seleção que se a categoria selecionada é Processors, ele irá devolver os produtos que está em Intel, Pentium, Core 2 Duo, AMD, etc ...

Eu pensei sobre a criação de uma espécie de "cache" que irá armazenar todas as categorias na hierarquia para cada categoria no db e incluem o "IN" na cláusula onde. É esta a melhor solução?

Foi útil?

Solução

A melhor solução para isso é na fase de projeto de banco de dados. Suas categorias necessidades de mesa para ser um Nested Set . O artigo Gerenciamento de dados hierárquicos em MySQL não é que o MySQL específico ( apesar do título), e dá uma grande visão geral dos diferentes métodos de armazenar uma hierarquia em uma tabela de banco de dados.

Sumário Executivo:

conjuntos aninhados

  • Seleciona são fáceis para qualquer profundidade
  • Inserções e exclusões são difíceis

Padrão PARENT_ID baseado hierarquia

  • Seleciona são baseados em junções internas (de modo a obter rápido cabeludo)
  • Inserções e exclusões são fáceis

Assim, com base no seu exemplo, se sua tabela de hierarquia era um conjunto aninhado sua consulta seria algo parecido com isto:

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

a 2 e 11 são a esquerda e direita, respectivamente, do registro Processors.

Outras dicas

parece um trabalho para uma expressão de tabela comum .. algo ao longo das linhas de:

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

Isso deve selecionar a categoria cujo nome é 'Processors' e qualquer de descendentes, deve ser capaz de usar isso em uma cláusula IN para puxar para trás os produtos.

Eu tenho feito coisas semelhantes no passado, primeira consulta para as IDs de categoria, em seguida, consultando para os produtos "em" dessas categorias. Obtendo as categorias é o pouco difícil, e você tem algumas opções:

  • Se o nível de aninhamento de categorias é conhecido ou você pode encontrar um limite superior: Construir um SELECT de aparência horrível com lotes de junções. Isto é rápido, mas feio e você precisa definir um limite sobre os níveis da hierarquia.
  • Se você tem um número relativamente pequeno de categorias no total, consulta todos eles (apenas ids, pais), recolher os ids de aqueles que se preocupam, e fazer um SELECT .... IN para os produtos. Esta foi a opção adequada para mim.
  • Consulta para cima / baixo na hierarquia usando uma série de SELECTs. Simples, mas relativamente lento.
  • Eu acredito que as versões recentes do SQLServer tem algum suporte para consultas recursivas, mas não usei-los eu mesmo.

Os procedimentos armazenados podem ajudar se você não quer fazer isso do lado do aplicativo.

O que você quer encontrar é o fechamento transitivo da categoria relação "pai". Suponho que não há nenhuma limitação à categoria de profundidade hierarquia, então você não pode formular uma única consulta SQL, que encontra todas as categorias. O que eu faria (em pseudocódigo) é o seguinte:

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

Então, basta manter a consulta para crianças até não mais são encontrados. Este se comporta bem em termos de velocidade, a menos que você tem uma hierarquia degenerado (digamos, 1000 categorias, cada uma criança de outro), ou um grande número de categorias no total. No segundo caso, você poderia sempre trabalhar com tabelas temporárias para manter a transferência de dados entre seu aplicativo e o pequeno banco de dados.

Talvez algo como:

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)

[EDIT] Se a profundidade categoria é maior do que um presente formaria sua consulta mais interna. Eu suspeito que você poderia projetar um procedimento armazenado que iria detalhar na tabela até que os ids retornados pela consulta interna não ter filhos - provavelmente melhor ter um atributo que marca uma categoria como um nó terminal na hierarquia - em seguida, executar a consulta externa sobre os ids.

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   

A última parte do exemplo não é realmente a trabalhar se você estiver executando-o diretamente como esta. Basta remover a escolha dos produtos e substituto com um simples * SELECT FROM r

Isso deve recurse para baixo todas as categorias 'criança' a partir de uma determinada categoria.

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

Eu gosto de usar uma tabela temporária de pilha para dados hierárquicos. aqui está um exemplo áspero -

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

há uma boa explicação aqui link de texto

A minha resposta a outra pergunta de um par de dias atrás, se aplica aqui ... recursão em SQL

Existem alguns métodos no livro que eu ligado, que deve cobrir a sua situação muito bem.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top