父母

时间:2015-05-29 18:14:15

标签: sql-server tsql recursion

我的老板给了我一张桌子。

Related_Items_Table

Item        | Accessory 
---------------------
TV          | Antennae 
TV          | Power Cord 
TV          | Remote 
Laptop      | Power Cord 
Laptop      | Carrying Case 
Camera      | Carrying Case 
Camera      | Lens 
iPod        | Headphones

描述我的老板想要结果的最佳方式是完成整个过程。

  1. 用户搜索电视。

  2. 找到电视,电视配件是Antennae,Power Cord& 远程。

  3. 配件Antennae,Power Cord&远程现在用于查找     其他相关项目。电源线也是笔记本电脑的配件。     天线&遥控器不是任何其他物品的附件。

  4. 项目笔记本电脑现在用于查找该项目的配件,其中         是电源线和电源线。携带箱。

  5. 配件电源线&携带箱现在用于寻找     其他相关项目。电源线找不到新物品(我们已经知道了     电源线与电视和电视有关。笔记本电脑)。携带箱也是     相机配件。

  6. 项目相机现在用于查找该项目的附件,其中     是携带箱&透镜。

  7. 配件携带箱&镜头现在用于寻找其他镜头     相关项目。携带箱&镜头找不到新物品(我们已经     知道便携包与笔记本电脑有关。

  8. 未找到任何新项目以继续搜索链。最终名单     返回。

  9. Final List 
    
    Item        | Accessory 
    ---------------------
    TV          | Antennae 
    TV          | Power Cord 
    TV          | Remote 
    Laptop      | Power Cord 
    Laptop      | Carrying Case 
    Camera      | Carrying Case 
    Camera      | Lens 
    

    处理此问题的最佳方法是什么?我不确定这个术语的正确性是什么,所以也许我在搜索中错过了它。任何建议都表示赞赏。

5 个答案:

答案 0 :(得分:1)

您的表看起来像是一个无向图,您需要从搜索的Item用户开始遍历此图。

考虑使用breadth-first search (BFS) algorithm

每个访问过的节点都是您需要的结果列表。

答案 1 :(得分:0)

我会做这样的pesudocode:

insert into Final_List
all the records that match the item in Related_Items_Table

WHILE 1=1
BEGIN
    Insert into Final List
    select NextLevel.*
    from Related_Items_Table
    join Related_Items_Table NextLevel
    on Related_Items_Table.Accessory = NextLevel.Item
    where the nextlevel.item and nextlevel.accesory not already in Final List
    if @@Rowcount = 0
        break
END

答案 2 :(得分:0)

除了连接的几个问题之外,Brian Pressler的伪代码非常接近。这就是我认为的样子:

-- The sample data from the problem.
declare @SearchString varchar(32) = 'TV';
declare @RelatedItemsTable table
(
    [Item] varchar(32), 
    [Accessory] varchar(32)
);
insert @RelatedItemsTable values
    ('TV', 'Antennae'),
    ('TV', 'Power Cord'),
    ('TV', 'Remote'),
    ('Laptop', 'Power Cord'),
    ('Laptop', 'Carrying Case'),
    ('Camera', 'Carrying Case'),
    ('Camera', 'Lens'),
    ('iPod', 'Headphones');

-- This table will hold your results.
declare @SearchResults table
(
    [Item] varchar(32),
    [Accessory] varchar(32)
);

-- Base case: look for any item or accessory that matches the search string.
-- I'm not sure whether you want to search items only or accessories also;
-- adjust as needed.
insert @SearchResults 
select * 
from
    @RelatedItemsTable 
where 
    [Item] like @SearchString or 
    [Accessory] like @SearchString;

while @@rowcount > 0
begin
    -- The recursive case: look for new records where...
    insert @SearchResults
    select
        [New].[Item],
        [New].[Accessory]
    from
        @RelatedItemsTable [New]
        inner join @SearchResults [Old] on
            -- ... the new record is an item using the same kind of accessory as
            -- an existing item, or...
            [New].[Accessory] = [Old].[Accessory] or

            -- ... the new record is an accessory for the same kind of item as an
            -- existing accessory, and...
            [New].[Item] = [Old].[Item]
    where
        -- ... this record doesn't yet appear in the result set.
        not exists
        (
            select 1
            from
                @SearchResults [Existing]
            where
                [Existing].[Accessory] = [New].[Accessory] and
                [Existing].[Item] = [New].[Item]
        );
end;

select * from @SearchResults;

SQL Server确实有一种递归查询机制 - recursive CTE - 但我无法使用其中一种实现此示例,因为我无法实现上述NOT EXISTS部分查询。

答案 3 :(得分:0)

我不能像我说的那样用CTE做到这一点。这里解释了原因:Prevent recursive CTE visiting nodes multiple times

所以这是一种古老的时尚方式

DECLARE @MyTable TABLE(Item NVARCHAR(50), Accessory NVARCHAR(50))
DECLARE @Result TABLE(Item NVARCHAR(50), Accessory NVARCHAR(50), LinkedItem NVARCHAR(50), Done int)

INSERT INTO @MyTable
VALUES
('TV', 'Antennae'),
('TV', 'Power Cord'),
('TV', 'Remote'),
('Laptop', 'Power Cord'),
('Laptop', 'Carrying Case'),
('Camera', 'Carrying Case'),
('Camera', 'Lens')


DECLARE @NbIteration INT = 0

INSERT INTO @Result
SELECT  t.Item,
        t.Accessory,
        LinkedItem.Item,
        @NbIteration                
FROM @MyTable AS t
LEFT JOIN @MyTable AS LinkedItem ON t.Accessory = LinkedItem.Accessory
WHERE t.Item = 'TV'


WHILE(@@ROWCOUNT > 0)
BEGIN
    SELECT @NbIteration = @NbIteration + 1

    INSERT INTO @Result
    SELECT  t.Item,
            t.Accessory,
            LinkedItem.Item,
            @NbIteration        
    FROM @Result AS r
    INNER JOIN @MyTable AS t ON r.LinkedItem = t.Item
    LEFT JOIN @MyTable AS LinkedItem ON t.Accessory = LinkedItem.Accessory
    WHERE r.Done = @NbIteration - 1
    AND NOT EXISTS(SELECT TOP 1 1 FROM @Result AS Sub WHERE t.Item = Sub.Item) --don't go back to records already done

END

SELECT DISTINCT Item, Accessory
FROM @Result

答案 4 :(得分:0)

如果您不反对使用GOTO语句进行循环,那么您可以尝试实现,这是一个解决方案:

DECLARE @search VARCHAR(50) = 'TV'

--Get initial resultset
DECLARE @table TABLE (item VARCHAR(50), accessory VARCHAR(50))
INSERT INTO @table
SELECT
    items.*
FROM
    items
WHERE
    item LIKE @search

--declare the variables used for checking if we have any new results
DECLARE @intCount INT = (SELECT COUNT(*) FROM @table)
DECLARE @intNewCount INT = (SELECT COUNT(*) FROM @table)

--The infamous GOTO label
START:
    --Store the count of items
    SET @intCount = (SELECT COUNT(*) FROM @table)

    --Insert any matching rows for accessory = accessory, excluding ones already added
    INSERT INTO @table
        (item, accessory)
    SELECT
        item, accessory
    FROM
        items
    WHERE
        accessory IN (SELECT accessory FROM @table) 
    AND NOT EXISTS(SELECT TOP 1 1 FROM @table t WHERE t.item = items.item AND t.accessory = items.accessory)

    --Now Insert any matching rows for item = item, excluding ones already added
    INSERT INTO @table
        (item, accessory)
    SELECT
        item, accessory
    FROM
        items
    WHERE
        item IN (SELECT item FROM @table)
    AND NOT EXISTS(SELECT TOP 1 1 FROM @table t WHERE t.item = items.item AND t.accessory = items.accessory)

    --Set the new count
    SET @intNewCount = (SELECT COUNT(*) FROM @table)
--Check if there's been any added during this iteration, if there are, repeat!
IF @intCount <> @intNewCount GOTO START;

--Finished
SELECT * FROM @table

我看到大多数其他答案都有一个while循环,我想我只是把它混合起来:)在SQL2008中测试