如何在 SQL Server 中无限地查询查询直到达到条件

2024-01-05

在 SQL Server 中

我有一个数据库,其中有一个名为食谱的表,该表包含食谱和食谱中的材料。我需要一个查询来显示食谱的所有基础材料。不幸的是,其中一些材料实际上是食谱中的其他食谱。例如:您有配方 AA01,其中包含 2 种基础材料和配方 BB01。您现在必须查找该配方中的材料,以找到 AA01 中的基础材料。诀窍是,你可以在食谱中拥有无限数量的食谱。这可以是一个搜索的阶梯。 你不知道你要往下看多远。

我提出了一个查询的想法,该查询搜索并找到下一个材料并循环,直到最终找到基础材料。有时可能需要循环一次才能找到材料,有时可能需要循环 5 次才能下降 5 个级别。

不幸的是,下面的代码我无法让它循环,所以它只找到第一层。我无法自行循环查询。

SELECT 
    Recipe.RecipeID, 
    Recipe_1.RecipeID, 
    Recipe_1.MaterialID  
FROM Recipe 
LEFT JOIN Recipe AS Recipe_1 ON Recipe.MaterialID = Recipe_1.RecipeID
ORDER BY Recipe.RecipeID;

解决方案是将此代码放入循环中或让它递归地调用自身,直到达到已找到所有基础材料的级别。我附上了 RECIPE 表示例的图片、代码生成的内容以及我需要它无限生成的内容(更多级别)。您可以看到突出显示的行是实际行中缺少的行,这些材料需要 2 层才能最终获得基础材料。我已将其硬编码为最多搜索 5 层,但显然,还可以更多。我怎样才能有 SQL 循环并分配层本身?

是否可以创建一个循环来不断地循环查询自身?


这看起来正在产生您正在寻找的结果......

SET NOCOUNT ON;

IF OBJECT_ID('tempdb..#Recipe', 'U') IS NOT NULL 
DROP TABLE #Recipe;

CREATE TABLE #Recipe (
    Recipe VARCHAR(5) NOT NULL,
    Material VARCHAR(5) NOT NULL 
    );

INSERT #Recipe (Recipe, Material) VALUES 
    ('aa01', 'B1'),
    ('aa01', 'B2'),
    ('aa01', 'bb01'),
    ('bb01', 'B1'),
    ('bb01', 'cc01'),
    ('cc01', 'B3'),
    ('cc01', 'B4'),
    ('B1', 'B1'),
    ('B2', 'B2'),
    ('B3', 'B3'),
    ('B4', 'B4');

--SELECT * FROM #Recipe r;

--====================================================================================

IF OBJECT_ID('tempdb..#RecursiveOutput', 'U') IS NOT NULL 
DROP TABLE #RecursiveOutput;

WITH 
    cte_Recursion AS (  
        SELECT 
            r.Recipe,
            x = r.Material,
            Material = CAST(r.Material AS VARCHAR(8000)),
            NodeLevel = 1,
            MaterialLevel = CAST('m1.Material' AS VARCHAR(8000))
        FROM
            #Recipe r
        UNION ALL
        SELECT 
            cr.Recipe, 
            x = r.Material,
            Material = CAST(CONCAT(cr.Material, '>', r.Material) AS VARCHAR(8000)),
            NodeLevel = cr.NodeLevel + 1,
            MaterialLevel = CAST(CONCAT('m', cr.NodeLevel + 1, '.Material, ', cr.MaterialLevel) AS VARCHAR(8000))
        FROM 
            cte_Recursion cr
            JOIN #Recipe r
                ON cr.x = r.Recipe
        WHERE 1 = 1
            AND cr.Recipe <> r.Recipe
            AND r.Recipe <> r.Material
        )
SELECT 
    cr.Recipe, 
    cr.Material, 
    cr.NodeLevel,
    cr.MaterialLevel
    INTO #RecursiveOutput
FROM
    cte_Recursion cr;

-------------------------------------

DECLARE 
    @Split VARCHAR(8000) = '',
    @Material VARCHAR(8000) = '',
    @Level VARCHAR(8000) = '',
    @SelectList VARCHAR(8000) = '', 
    @MaxNode INT = 0,
    @DeBug BIT = 0;     -- set to 0 to execute & set to 1 to print...


SELECT
    @Split = CONCAT(@Split, '
    CROSS APPLY ( VALUES (NULLIF(CHARINDEX(''>'', ro.Material, ', CASE WHEN ro.NodeLevel = 1 THEN '1' ELSE CONCAT('s', ro.NodeLevel - 1, '.Split + 1') END, '), 0)) ) s', ro.NodeLevel, ' (Split)'),
    @Material = CONCAT(@Material, '
    CROSS APPLY ( VALUES (SUBSTRING(ro.Material, ', CASE WHEN ro.NodeLevel = 1 THEN '1, ISNULL(s1.Split -1,' ELSE CONCAT('s', ro.NodeLevel - 1, '.Split + 1, ISNULL(s', ro.NodeLevel, '.Split - s', ro.NodeLevel - 1, '.Split - 1,') END, ' 1000))) ) m', ro.NodeLevel, ' (Material)'),
    @Level = CONCAT(@Level, CASE WHEN ro.NodeLevel = 1 THEN '' ELSE CONCAT('
    CROSS APPLY ( VALUES (CAST(COALESCE(', ro.MaterialLevel, ') AS VARCHAR(20))) ) L', ro.NodeLevel, ' ([Level ', ro.NodeLevel, ' Material])') END),
    @SelectList =  CONCAT(@SelectList, CASE WHEN ro.NodeLevel = 1 THEN '' ELSE CONCAT(',
    L', ro.NodeLevel, '.[Level ', ro.NodeLevel, ' Material]') END),
    @MaxNode = CASE WHEN ro.NodeLevel > @MaxNode THEN ro.NodeLevel ELSE @MaxNode END
FROM 
    #RecursiveOutput ro
GROUP BY 
    ro.NodeLevel,
    ro.MaterialLevel;

DECLARE @sql VARCHAR(MAX) = CONCAT('
SELECT DISTINCT
   ro.Recipe,
    [Level 1 Material] = CAST(m1.Material AS VARCHAR(20))', 
    @SelectList, '
FROM
    #RecursiveOutput ro', 
    @Split, 
    @Material,
    @Level, '
WHERE
    EXISTS (SELECT 1 FROM #Recipe r WHERE L', @MaxNode, '.[Level ', @MaxNode, ' Material] = r.Recipe AND r.Recipe = r.Material);')

IF @DeBug = 1
BEGIN 
    PRINT(@sql);
END;
ELSE 
BEGIN
    EXEC(@sql);
END; 

结果...

Recipe Level 1 Material     Level 2 Material     Level 3 Material
------ -------------------- -------------------- --------------------
aa01   B1                   B1                   B1
aa01   B2                   B2                   B2
aa01   bb01                 B1                   B1
aa01   bb01                 cc01                 B3
aa01   bb01                 cc01                 B4
B1     B1                   B1                   B1
B2     B2                   B2                   B2
B3     B3                   B3                   B3
B4     B4                   B4                   B4
bb01   B1                   B1                   B1
bb01   cc01                 B3                   B3
bb01   cc01                 B4                   B4
cc01   B3                   B3                   B3
cc01   B4                   B4                   B4

编辑:下面是与上面相同的解决方案,但编写的目的是为了消除对早期版本的 SQL Server 的 CONCAT 函数的使用...

SET NOCOUNT ON;

IF OBJECT_ID('tempdb..#Recipe', 'U') IS NOT NULL 
DROP TABLE #Recipe;

CREATE TABLE #Recipe (
    Recipe VARCHAR(5) NOT NULL,
    Material VARCHAR(5) NOT NULL 
    );

INSERT #Recipe (Recipe, Material) VALUES 
    ('aa01', 'B1'),
    ('aa01', 'B2'),
    ('aa01', 'bb01'),
    ('bb01', 'B1'),
    ('bb01', 'cc01'),
    ('cc01', 'B3'),
    ('cc01', 'B4'),
    ('B1', 'B1'),
    ('B2', 'B2'),
    ('B3', 'B3'),
    ('B4', 'B4');

--SELECT * FROM #Recipe r;

--====================================================================================

IF OBJECT_ID('tempdb..#RecursiveOutput', 'U') IS NOT NULL 
DROP TABLE #RecursiveOutput;

WITH 
    cte_Recursion AS (  
        SELECT 
            r.Recipe,
            x = r.Material,
            Material = CAST(r.Material AS VARCHAR(8000)),
            NodeLevel = 1,
            MaterialLevel = CAST('m1.Material' AS VARCHAR(8000))
        FROM
            #Recipe r
        UNION ALL
        SELECT 
            cr.Recipe, 
            x = r.Material,
            Material = CAST(cr.Material + '>' + r.Material AS VARCHAR(8000)),
            NodeLevel = cr.NodeLevel + 1,
            MaterialLevel = CAST('m' + CAST(cr.NodeLevel + 1 AS VARCHAR(10)) + '.Material, ' + cr.MaterialLevel AS VARCHAR(8000))
        FROM 
            cte_Recursion cr
            JOIN #Recipe r
                ON cr.x = r.Recipe
        WHERE 1 = 1
            AND cr.Recipe <> r.Recipe
            AND r.Recipe <> r.Material
        )
SELECT 
    cr.Recipe, 
    cr.Material, 
    cr.NodeLevel,
    cr.MaterialLevel
    INTO #RecursiveOutput
FROM
    cte_Recursion cr;

-------------------------------------

DECLARE 
    @Split VARCHAR(8000) = '',
    @Material VARCHAR(8000) = '',
    @Level VARCHAR(8000) = '',
    @SelectList VARCHAR(8000) = '', 
    @MaxNode INT = 0,
    @DeBug BIT = 0;     -- set to 0 to execute & set to 1 to print...


SELECT
    @Split = @Split + '
    CROSS APPLY ( VALUES (NULLIF(CHARINDEX(''>'', ro.Material, ' + CASE WHEN ro.NodeLevel = 1 THEN '1' ELSE 's' + CAST(ro.NodeLevel - 1 AS VARCHAR(10)) + '.Split + 1' END + '), 0)) ) s' + CAST(ro.NodeLevel AS VARCHAR(10)) + ' (Split)',

    @Material = @Material + '
    CROSS APPLY ( VALUES (SUBSTRING(ro.Material, ' + CASE WHEN ro.NodeLevel = 1 THEN '1, ISNULL(s1.Split -1,' ELSE 's' + CAST(ro.NodeLevel - 1 AS VARCHAR(10)) + '.Split + 1, ISNULL(s' + CAST(ro.NodeLevel AS VARCHAR(10)) + '.Split - s' 
            + CAST(ro.NodeLevel - 1 AS VARCHAR(10)) + '.Split - 1,' END + ' 1000))) ) m' + CAST(ro.NodeLevel as VARCHAR(10)) + ' (Material)',

    @Level = @Level + CASE WHEN ro.NodeLevel = 1 THEN '' ELSE '
    CROSS APPLY ( VALUES (CAST(COALESCE(' + ro.MaterialLevel + ') AS VARCHAR(20))) ) L' + CAST(ro.NodeLevel AS VARCHAR(10)) + ' ([Level ' + CAST(ro.NodeLevel as VARCHAR(10)) + ' Material])' END,

    @SelectList =  @SelectList + CASE WHEN CAST(ro.NodeLevel as VARCHAR(10)) = 1 THEN '' ELSE ',
    L' + CAST(ro.NodeLevel AS VARCHAR(10)) + '.[Level ' + CAST(ro.NodeLevel AS VARCHAR(10)) + ' Material]' END,

    @MaxNode = CASE WHEN ro.NodeLevel > @MaxNode THEN ro.NodeLevel ELSE @MaxNode END
FROM 
    #RecursiveOutput ro
GROUP BY 
    ro.NodeLevel,
    ro.MaterialLevel;

DECLARE @sql VARCHAR(MAX) = '
SELECT DISTINCT
   ro.Recipe,
    [Level 1 Material] = CAST(m1.Material AS VARCHAR(20))' +
    @SelectList + '
FROM
    #RecursiveOutput ro' +
    @Split +
    @Material +
    @Level + '
WHERE
    EXISTS (SELECT 1 FROM #Recipe r WHERE L' + CAST(@MaxNode AS VARCHAR(10)) + '.[Level ' + CAST(@MaxNode AS VARCHAR(10)) + ' Material] = r.Recipe AND r.Recipe = r.Material);'

IF @DeBug = 1
BEGIN 
    PRINT(@sql);
END;
ELSE 
BEGIN
    EXEC(@sql);
END;

哈特哈, 贾森

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 SQL Server 中无限地查询查询直到达到条件 的相关文章

  • 删除前导零

    给定列中的数据 如下所示 00001 00 00026 00 我需要使用 SQL 删除空格后面的所有内容以及值中的所有前导零 以便最终输出为 1 26 我怎样才能最好地做到这一点 顺便说一句 我正在使用 DB2 这已在 DB2 for Li
  • 如何显示 RSpec 测试生成的 SQL 查询日志?

    我正在为我的 Rails 3 应用程序编写规范 我想测试数据库事务是否真的有效 如果能够看到我的应用程序在规范驱动下生成的 sql 查询 这将非常有帮助 有没有办法像在 Rails 控制台中一样查看查询 我正在使用 Rails 3 0 9
  • 使用显式值进行 BigQuery 合并

    据我所知 BigQuery 支持合并两个表 目前 INSERT操作允许将显式值插入表中 例如 INSERT dataset Inventory product quantity VALUES top load washer 10 front
  • 尚未为此带有 SQL Server 的 DbContext .NET Core 配置数据库提供程序

    我一直用这个把头撞在墙上 并且一直在谷歌上搜索无济于事 我刚刚开始一个新的 ASP NET Core MVC 项目 我已将这两个包安装 更新为 2 2 0 Microsoft EntityFrameworkCore SqlServer Mi
  • 如何将此本机 SQL 查询转换为 HQL

    所以我有这个很长的复杂的 Native SQLQuery string hql SELECT FROM SELECT a rownum r FROM select f2 filmid f2 realisateurid f2 titre f2
  • jDBI中如何进行内查询?

    我怎样才能在 jDBI 中执行这样的事情 SqlQuery select id from foo where name in
  • SQL Server 标识列值从 0 而不是 1 开始

    我遇到了一个奇怪的情况 数据库中的某些表的 ID 从 0 开始 即使 TABLE CREATE 的 IDENTITY 1 1 也是如此 对于某些表来说是这样 但对于其他表则不然 它一直有效到今天 我尝试过重置身份列 DBCC CHECKID
  • SQLite 中的累积求和值

    我正在尝试在 SQLite 中执行值的累积和 我最初只需要对一列求和并获得代码 SELECT t MyColumn SELECT Sum r KeyColumn1 FROM MyTable as r WHERE r Date lt t Da
  • Postgresql:SERIAL 在约束 INSERT 失败时递增

    有一个像这样的简单表结构 CREATE TABLE test id INT PRIMARY KEY sid SERIAL 我注意到如果我尝试插入一行但它未通过约束测试 即主键约束 SERIAL计数器无论如何都会增加 所以下一次成功插入 si
  • 解释 SQL Server 中 sys.objects 中的类型代码

    在 SQL Server 上 sys objects 表包含 Type 和 Type Desc 属性 例如 对于我的一个数据库 SELECT DISTINCT Type Type Desc FROM Sys Objects ORDER BY
  • 将子查询的结果插入表中并带有常量

    相关表格的概要如下 我有一个表 我们称之为联接 它有两列 都是其他表的外键 我们将这两列称为 userid 和buildingid 因此 join 看起来像 join userid buildingid 我基本上需要在这个表中插入一堆行 通
  • 如何更新 SQL Server 2000 中的 text 或 ntext 字段

    所以我需要更新一个文本字段 在下面使用时 UPDATE 语句或 WRITETEXT 语句都不起作用 CREATE TABLE MyTable IDField int MyField text INSERT INTO MyTable IDFi
  • INFORMATION_SCHEMA 与 sysobjects

    在 SQL Server 中 INFORMATION SCHEMA 和 sysobjects 之间有什么区别 其中一个是否比另一个提供更多信息 或者它们通常用于不同的用途 sysobjects 与 sys objects 相同吗 如果不是
  • SQL使用多个/相关列计算项目频率?

    我对 SQL 完全陌生 并且阅读了有关 SQL 的 StackOverflow 帖子来尝试弄清楚这一点 以及其他来源 但无法在 SQL 中执行此操作 开始 我有一个 3 列和数千行的表 其中包含前 2 列的数据 第三列当前为空 我需要根据第
  • 我不确定在 SQL 中声明这些变量时出了什么问题

    我有以下代码 USE pricingdb go CREATE TABLE dbo Events 060107 2012 Date Time varchar 20 COLLATE SQL Latin1 General CP1 CI AS NU
  • 仅从数据库获取我想要的数据但保留结构

    我正在尝试在 powerbi 上执行此操作 但我想这只是基本的 SQL 我想将我的数据导入到 powerBi 中 但使用一些 id 对其进行过滤 我们以这个例子为例 我与一些公司有数据库 表1 每个公司都有建筑物 表2 每个建筑物有员工 表
  • 删除原始数据中部分重复的记录

    我需要删除表中时间间隔为 1 或 2 分钟或相同且必须相同的所有记录ID但保留第一个记录 ID Time SN SD WE FW 10 2014 06 30 19 17 37 000 I 0 100 0 10 2014 06 30 19 1
  • play20 ebean 生成的 sql 在 postgresql 上抛出语法错误

    我正在尝试使用 postgresql 来启动我的 play20 应用程序 以便我可以使用并稍后部署到 Heroku 我跟着这个answer https stackoverflow com questions 12195568 errors
  • MySql如何通过过滤多列来限制多个数字?

    我想从数据库中获取 4 个不同类别的 50 个问题 我想要 4 个不同类别中每个类别的不同数量的问题 我的结果集必须包含第一类 12 个问题 第二类 20 个问题 第三类 10 个问题和第四类 8 个问题 我的问题表中总共有 50 个问题
  • SQL:比较不同表中的两个计数

    我有 3 张桌子 一张桌子上有世界上每个国家及其代币 NAME CODE Afghanistan AFG Albania AL Algeria DZ American Samoa AMSA Andorra AND Angola ANG An

随机推荐