列不喜欢不适用于递归cte

时间:2017-05-27 16:08:12

标签: sql sql-server recursive-cte

这是SQL Server代码。

我们假设您有一个包含三列的表格。第1列命名为Monster,第2列命名为Level,第3列命名为BodyType。等级表示怪物的强大程度,BodyType表示它具有什么类型的身体。

我的架构:

CREATE TABLE YourTable
    ([Monster] nvarchar(max), [Level] int, [BodyType] nvarchar(max))
;

INSERT INTO YourTable
    ([Monster], [Level], [BodyType])
VALUES
    ('Small Beast', 300, 'Scaly'),
    ('Large Beast', 700, 'Slimy'),
    ('Small Dragon', 350, 'Fiery'),
    ('Large Dragon', 800, 'Slimy')
;

我有一个sql命令来查找怪物的所有可能组合。它使用递归cte,因为表中的怪物数量可能会波动(因此我可以在以后添加更多的怪物)。该命令还获取正在组合的怪物等级的总和值。该命令还仅输出属于某个总和值的怪物组合。在这个例子中,总和值是1500.到目前为止,一切都按预期工作。

我的sql命令:

;WITH cte AS (
SELECT  Monster, 
        [Level],
        BodyType,
        1 as l
FROM YourTable
UNION ALL


SELECT  c1.Monster+','+c2.Monster,
        c1.[Level]+c2.[Level],
        c1.BodyType+','+c2.BodyType,
        c1.l+1
FROM cte c1
CROSS JOIN YourTable c2
WHERE c1.Monster NOT LIKE '%'+c2.Monster+'%'
)


SELECT *
FROM cte
WHERE cte.Level < 1500
ORDER BY l
OPTION (MAXRECURSION 0)

正确的输出:

1   Small Beast 300 Scaly   1
2   Large Beast 700 Slimy   1
3   Small Dragon    350 Fiery   1
4   Large Dragon    800 Slimy   1
5   Large Dragon,Small Beast    1100    Slimy,Scaly 2
6   Large Dragon,Small Dragon   1150    Slimy,Fiery 2
7   Small Dragon,Small Beast    650 Fiery,Scaly 2
8   Small Dragon,Large Beast    1050    Fiery,Slimy 2
9   Small Dragon,Large Dragon   1150    Fiery,Slimy 2
10  Large Beast,Small Beast 1000    Slimy,Scaly 2
11  Large Beast,Small Dragon    1050    Slimy,Fiery 2
12  Small Beast,Large Beast 1000    Scaly,Slimy 2
13  Small Beast,Small Dragon    650 Scaly,Fiery 2
14  Small Beast,Large Dragon    1100    Scaly,Slimy 2
15  Small Beast,Large Dragon,Small Dragon   1450    Scaly,Slimy,Fiery   3
16  Small Beast,Small Dragon,Large Beast    1350    Scaly,Fiery,Slimy   3
17  Small Beast,Small Dragon,Large Dragon   1450    Scaly,Fiery,Slimy   3
18  Small Beast,Large Beast,Small Dragon    1350    Scaly,Slimy,Fiery   3
19  Large Beast,Small Dragon,Small Beast    1350    Slimy,Fiery,Scaly   3
20  Large Beast,Small Beast,Small Dragon    1350    Slimy,Scaly,Fiery   3
21  Small Dragon,Large Dragon,Small Beast   1450    Fiery,Slimy,Scaly   3
22  Small Dragon,Large Beast,Small Beast    1350    Fiery,Slimy,Scaly   3
23  Small Dragon,Small Beast,Large Beast    1350    Fiery,Scaly,Slimy   3
24  Small Dragon,Small Beast,Large Dragon   1450    Fiery,Scaly,Slimy   3
25  Large Dragon,Small Dragon,Small Beast   1450    Slimy,Fiery,Scaly   3
26  Large Dragon,Small Beast,Small Dragon   1450    Slimy,Scaly,Fiery   3

我遇到的问题是当我添加一个Where子句时,只返回不属于某种体型(BodyType列)的怪物。修改后的上述代码部分是:

;WITH cte AS (
SELECT  Monster, 
        [Level],
        BodyType,
        1 as l
FROM YourTable
WHERE BodyType NOT LIKE 'Fiery' AND BodyType NOT LIKE 'Slimy'
UNION ALL

输出变为以下不正确,因为它仍然包含Slimy和Fiery的主体类型:

    Monster Level   BodyType    l
1   Small Beast 300 Scaly   1
2   Small Beast,Large Beast 1000    Scaly,Slimy 2
3   Small Beast,Small Dragon    650 Scaly,Fiery 2
4   Small Beast,Large Dragon    1100    Scaly,Slimy 2
5   Small Beast,Large Dragon,Small Dragon   1450    Scaly,Slimy,Fiery   3
6   Small Beast,Small Dragon,Large Beast    1350    Scaly,Fiery,Slimy   3
7   Small Beast,Small Dragon,Large Dragon   1450    Scaly,Fiery,Slimy   3
8   Small Beast,Large Beast,Small Dragon    1350    Scaly,Slimy,Fiery   3

输出似乎部分有效,因为Large Beast是Slimy并且它第一次忽略它但我怀疑它在移动BodyType的级别时忽略了NOT LIKE子句,这就是为什么它没有&#39 ; t忽略后续发现的大型野兽。

3 个答案:

答案 0 :(得分:2)

如果我理解正确,您可以尝试以下两种方法:

1. recursive cte

后过滤血型
;WITH cte AS (
SELECT  Monster, 
        [Level],
        BodyType,
        1 as l
FROM YourTable
UNION ALL

SELECT  c1.Monster+','+c2.Monster,
        c1.[Level]+c2.[Level],
        c1.BodyType+','+c2.BodyType,
        c1.l+1
FROM cte c1
CROSS JOIN YourTable c2
WHERE c1.Monster NOT LIKE '%'+c2.Monster+'%'
)
SELECT *
FROM cte
WHERE cte.Level < 1500
AND ',' + cte.BodyType ',' + NOT LIKE '%,Fiery,%' 
AND ',' + cte.BodyType ','  + NOT LIKE '%,Slimy,%'
ORDER BY l
OPTION (MAXRECURSION 0)

2.在递归cte

之前使用第二个cte进行过滤
;WITH temp AS
(
    SELECT * 
    FROM YourTable
    WHERE   BodyType != 'Fiery' 
            AND BodyType != 'Slimy'
)
,cte AS (
SELECT  Monster, 
        [Level],
        BodyType,
        1 as l
FROM temp
UNION ALL

SELECT  c1.Monster+','+c2.Monster,
        c1.[Level]+c2.[Level],
        c1.BodyType+','+c2.BodyType,
        c1.l+1
FROM cte c1
CROSS JOIN temp c2
WHERE c1.Monster NOT LIKE '%'+c2.Monster+'%'
)
SELECT *
FROM cte
WHERE cte.Level < 1500   
ORDER BY l
OPTION (MAXRECURSION 0)

答案 1 :(得分:0)

您的逻辑需要AND,而不是OR

WHERE BodyType NOT LIKE 'Fiery' AND BodyType NOT LIKE 'Slimy'

BodyType&#34; Fiery&#34;不像&#34; Slimy&#34;。因此,它符合第二个条件。请注意,如果您使用LIKE,则需要AND

答案 2 :(得分:0)

我想我找到了解决方案。输出数据似乎是正确的:

SELECT  c1.Monster+','+c2.Monster,
        c1.[Level]+c2.[Level],
        c1.BodyType+','+c2.BodyType,
        c1.l+1
FROM cte c1
CROSS JOIN YourTable c2
WHERE (c1.Monster NOT LIKE '%'+c2.Monster+'%') AND (c1.BodyType NOT LIKE 'Scaly' AND c2.BodyType NOT LIKE 'Scaly') AND (c1.BodyType NOT LIKE 'Fiery' AND c2.BodyType NOT LIKE 'Fiery') 
)

我把破坏的Where子句拿出来,只是在原始代码的CROSS JOIN之后向Where子句添加了NOT LIKE&#39。

不确定这不是最佳做法,还是可能会破坏某些东西,任何人都想要加入?谢谢。

编辑:我的解决方案有一个问题,即第一次迭代将包含忽略&#39; BodyTypes&#39;