Рекомендуемый способ поиска иерархических данных MSSQL2008 - PullRequest
0 голосов
/ 11 августа 2011

У меня есть таблица со следующим содержимым:

  • CategoryID
  • ParentID
  • Имя

Я хотел бы иметьфункция поиска, которая будет выполнять поиск по всей иерархии, например, это крошка категории:

Мотоциклы / Япония / Kawasaki / 600cc до 800cc / 1998-2004

Если кто-то ищет "600cc Kawasaki "Я хотел бы, чтобы вышеупомянутая категория была возвращена.Таким образом, путь к категории, который имеет наибольшее количество совпадений, должен вернуться.

На данный момент я пришел к следующему:

IF ISNULL(@searchTerm, '') = ''
    SET @searchTerm = '""'
DECLARE @Result TABLE (CategoryId int) 

DECLARE CategoryCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT CategoryId, ParentId, Name
FROM Category
WHERE FREETEXT([Name], @searchTerm)
OPEN CategoryCursor
DECLARE @CategoryId int
DECLARE @ParentId int
DECLARE @Name nvarchar(100)

FETCH NEXT FROM CategoryCursor INTO @CategoryId, @ParentId, @Name
WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @FullPath nvarchar(1000)
    SET @FullPath = @Name

    WHILE @ParentId <> 0
    BEGIN
        SELECT @ParentId = ParentId, @Name = [Name]
        FROM Category 
        WHERE CategoryId = @ParentId

        SET @FullPath = @Name + '\' + @FullPath     
    END

    -- Check if @FullPath contains all of the searchterms
    DECLARE @found bit
    DECLARE @searchWords NVARCHAR(100)
    DECLARE @searchText NVARCHAR(255)
    DECLARE @pos int

    SET @found = 1
    SET @searchWords = @searchTerm + ' '
    SET @pos = CHARINDEX(' ', @searchWords)
    WHILE @pos <> 0
        BEGIN
        SET @searchText = LEFT(@searchWords, @pos - 1)
        SET @searchWords = STUFF(@searchWords, 1, @pos, '')
        SET @pos = CHARINDEX(' ', @searchWords)
        IF @searchText = '' CONTINUE
        IF @FullPath NOT LIKE '%' + @searchText + '%' 
            BEGIN
                SET @found = 0
            BREAK
            END
        END

    IF @found = 1
        INSERT INTO @Result VALUES(@CategoryId)

    FETCH NEXT FROM CategoryCursor INTO @CategoryId, @ParentId, @Name
END

CLOSE CategoryCursor
DEALLOCATE CategoryCursor

SELECT * 
FROM Category 
WHERE categoryID IN (SELECT categoryId FROM @Result)

Сначала будут найдены все имена катагорий, которые содержат любое из поисковых слов.Проблема в том, что я не хочу, чтобы "600cc" возвращался другим брендам, только тот, который связан с "Kawasaki".Итак, затем я строю хлебные крошки для текущей категории и проверяю, содержит ли она все поисковые слова.

Это работает, но я думаю, что это неэффективно, поэтому я ищу лучший метод.

Возможно, сохранение полного пути в виде текста в новом столбце и поиск по нему?

1 Ответ

0 голосов
/ 11 августа 2011

Я бы предложил использовать иерархию , что в 2008 году. Вы бы по существу установили свою иерархию следующим образом

/ 1 / - Root Node / 1/1 / - Мотоциклы / 1/ 1/1 / - Япония / 1/1/1/1 / - Kawasaki / 1/1/1/2 / - Honda / 1/1/2 / - US / 1/1/2/1 / - Harley.

Затем вы можете использовать иерархию, чтобы получить все дерево от вашего кавасаки 600 куб. См 1984 года вплоть до мотоциклов.

Вот пример кода из программирования Microsoft SQL Server 2008

CREATE FUNCTION dbo.fnGetFullDisplayPath(@EntityNodeId hierarchyid)  RETURNS varchar(max) AS  
BEGIN    
    DECLARE @EntityLevelDepth smallint    
    DECLARE @LevelCounter smallint    
    DECLARE @DisplayPath varchar(max)    
    DECLARE @ParentEmployeeName varchar(max)    

    -- Start with the specified node    
    SELECT @EntityLevelDepth = NodeId.GetLevel(), 
    @DisplayPath = EmployeeName     
    FROM  Employee     
    WHERE NodeId = @EntityNodeId    

    -- Loop through all its ancestors    
    SET @LevelCounter = 0    
    WHILE @LevelCounter < @EntityLevelDepth 
    BEGIN       
        SET @LevelCounter = @LevelCounter + 1       
        SELECT @ParentEmployeeName = EmployeeName        
        FROM  Employee        WHERE NodeId = (SELECT NodeId.GetAncestor(@LevelCounter)               
            FROM Employee 
            WHERE NodeId = @EntityNodeId)       

        -- Prepend the ancestor name to the display path       
        SET @DisplayPath = @ParentEmployeeName + ' > ' + @DisplayPath    
    END    

    RETURN(@DisplayPath)   
END 

My / 1/1/2 представление является строковым представлением.В базе данных вы фактически увидите шестнадцатеричное представление (например, 0x79).

В иерархии есть несколько ключевых функций.

declare @motorcycleAncestor hieararchyid
select @motorcycleAncestor = nodeId.GetAncestor(1)
from parts 
where Label = 'motorcycle'

select * from Parts
where Node.GetAncestor(1) = @motorcyleAncestor;

Этот запрос делает несколько вещей.Во-первых, он получает идентификатор иерархии для узла, который содержит «Мотоцикл» в качестве метки.(Я предполагаю, что поле hiearchy называется 'nodeid', но вы, очевидно, можете называть его как угодно.)

Далее, оно принимает значение этого узла и находит всех непосредственных потомков мотоциклов (чей предок, 1 уровень вверх, естьузел мотоцикла. На самом деле вы можете указать любое значение, например, GetAncestor (3) будет на 3 уровня выше предка).Таким образом, в этом случае он найдет Японию, США, Германию и т. Д.

Существует еще один метод, называемый IsDescendantOf ( node ).Вы можете использовать его так:

declare @motorcycleAncestor hieararchyid
select @motorcycleAncestor = nodeId.GetAncestor(1)
from parts 
where Label = 'motorcycle'

select * from Parts
where Node.IsDescendantOf(@motorcycleAncestor) = 1

Это вернет все предметы, которые являются детьми (любого уровня) под мотоциклами.На самом деле это также включает мотоциклы.

Вы можете комбинировать их по-разному.Например, мы используем их в своего рода оргструктуре.У нас есть возможность показать результаты для одного пользователя или для пользователя и его братьев и сестер (все на одном уровне), а также для пользователя и всех его потомков.

Чтобы я мог показать вашу информацию, или ямог бы показать всех в вашем отделе, или я мог бы показать всех в вашей компании.

...