【问题标题】:SQL: Delete Rows from Dynamic list of tables where ID is nullSQL:从 ID 为空的表的动态列表中删除行
【发布时间】:2021-01-08 14:35:03
【问题描述】:

我是 SQL 新手,通常通过 Google 和 SO 解决问题,但我无法完全理解所需的 SQL。

我的问题类似于Delete sql rows where IDs do not have a match from another table,但在我的情况下,我有一个必须查询的中间表,所以这是场景:

我们有这个 INSTANCES 表,它基本上列出了发送到数据库的所有文件,但必须与 CROSS_REF 连接,以便我们的报告应用程序知道要查询哪个表以获取报告,而我们只是有孤立的 INSTANCES 行我想整理。每个 DETAIL 表都包含与其他表不同的字段。

如果在任何 DETAIL 表中没有该 Instance ID 的记录,我想从 INSTANCES 中删除所有单个记录。 DETAIL 表定期清理旧文件,但 Instance 记录没有清理,因此我们有很多没有任何关联 DETAIL 数据的 INSTANCE 记录。问题是,我必须从 CROSS_REF 中选择 Table Name 才能知道要查找 Instance ID 的 DETAIL_X 表。

那么在下面的示例中,由于 DETAIL_1 没有 Instance ID = 1001 的记录,我想从 INSTANCES 中删除 1001 记录。

实例

Instance ID Detail ID
1000 123
1001 123
1002 234

CROSS_REF

Detail ID Table Name
123 DETAIL_1
124 DETAIL_2
125 DETAIL_3

DETAIL_1

Instance ID
1000
1000
2999

【问题讨论】:

  • 你需要使用动态sql来完成这样的任务。所以我建议在谷歌上搜索这个术语,并找到一些关于如何构建动态查询的示例,其中参与查询的表名事先不知道。
  • 3张固定表不需要动态SQL。 DETAIL你有几张桌子
  • @Charlieface 大约 200 张桌子。一个实例将只有一个详细表中的记录,但在详细记录已被删除的情况下,我想删除实例记录,因为这些是用户选择的,我不想显示选项没有详细数据。
  • 就我个人而言,我会先将所有表合并为一个。您的设置完全违反了范式规则

标签: sql sql-server


【解决方案1】:

在数据库中存储表名或列名几乎总是表明数据库设计不佳。您可能想要更改它,从而摆脱这个问题。

但是,当知道可能的表名时,任务并不太难。

delete from instances i
where not exists
(
  select null
  from cross_ref cr
  left join detail_1 d1 on d1.instance_id = i.instance_id and cr.table_name = 'DETAIL_1'
  left join detail_2 d2 on d2.instance_id = i.instance_id and cr.table_name = 'DETAIL_2'
  left join detail_3 d3 on d3.instance_id = i.instance_id and cr.table_name = 'DETAIL_3'
  where cr.detail_id = i.detail_id
  and 
  (
    d1.instance_id is not null or
    d2.instance_id is not null or
    d3.instance_id is not null    
  )
);

(如果您发现= i.instance_id 更易读,您可以将is not null 替换为= i.instance_id。在这种情况下,您甚至可以从ON 子句中删除这些条件。)

【讨论】:

  • 我很乐意听到任何关于更好数据库设计的建议,因为我们在没有经验丰富的 SQL 架构师帮助的情况下完成了它。问题是,虽然我知道表名,但其中有 100 多个,我想避免编写 100 个查询。但是,如果我确实使用了表名,我认为没有理由按照您的建议使用交叉引用表,因为您已经加入 detail_X。
  • @redOctober13 关于您的设计的另一件不太理想的事情 - 为什么有 100 多个 detailXX 表,而不是只有一个带有额外列指定详细信息类型的表?然后您的 DELETE 查询变得更简单。它还降低了应用程序与数据库交互的复杂性(我假设这就是填充这些不同的详细信息表的方式)。
  • 嗯,每个 DETAIL 表都有一组完全不同的列(减去一些公共字段)。所以我们有这个 INSTANCES 表,它基本上列出了所有的东西,但必须与 CROSS_REF 连接,以便我们的报告应用程序知道要查询哪个表来获取数据,而我们只是有孤立的 INSTANCES 行我想清除。
  • @redOctober13 我什至不明白实例和其他表之间的关系。从表面上看,如果没有进一步的上下文,这似乎是不必要的。
  • @DougCoats 基本上我们有 100 个不同的文件(不同的布局)可以发送到数据库。每个人都将其记录加载到自己的表中(详细信息)。我有一个交叉引用表,上面写着 FILE_X 映射到 DETAIL_X。丑陋,正如其他人所指出的那样,但不是我设计的。
【解决方案2】:

非常感谢@DougCoats,这就是我最终的结果。

这就是我最终得到的结果(@Doug,如果你想更新你的答案,我会标记你的正确)。

DECLARE @Count INT, @Sql VARCHAR(MAX), @Max INT;

SET @Count = (SELECT MIN(DetailID) FROM CROSS_REF)
SET @Max = (SELECT MAX(DetailID) FROM CROSS_REF)

WHILE @Count <= @Max
    BEGIN
        IF (select count(*) from CROSS_REF where file_id = @count) <> 0
        BEGIN
        SET @sql ='DELETE i
        FROM Instances i
        WHERE NOT EXISTS
            (
                SELECT InstanceID
                FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=@Count)+' d
                WHERE d.InstanceId=i.InstanceID
                AND i.detailID ='+ cast(@Count as varchar) +'
            )
            AND i.detailID ='+ cast(@Count as varchar)
        EXEC(@sql);
        SET @Count=@Count+1
        END
    END

【讨论】:

    【解决方案3】:

    此答案假设您在 CROSS_REF 表中有顺序数据。如果你不这样做,你需要改变它来解释它(因为它会因为缺少对象引用而爆炸)。

    但是,这应该会给您一个想法。它也可能被写成更基于集合的方法,但我的答案是演示动态 sql 的使用。不过在使用动态 SQL 时要小心。

        DECLARE @Count INT, @Sql VARCHAR(MAX), @Max INT;
    
        SET @Count = (SELECT MIN(DetailID) FROM CROSS_REF)
        SET @Max = (SELECT MAX(DetailID) FROM CROSS_REF)
    
        WHILE @Count <= @Max
            BEGIN
                IF (select count(*) from CROSS_REF where file_id = @count) <> 0
                BEGIN
                SET @sql ='DELETE i
                FROM Instances i
                WHERE NOT EXISTS
                    (
                        SELECT InstanceID
                        FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=@Count)+' d
                        WHERE d.InstanceId=i.InstanceID
                        AND i.detailID ='+ cast(@Count as varchar) +'
                    )
                    AND i.detailID ='+ cast(@Count as varchar)
                EXEC(@sql);
                SET @Count=@Count+1
                END
            END
        
    

    【讨论】:

    • 这里缺少括号。此外,您应该确保安全地 注入动态对象的名称,以防万一其中有任何“不友好”字符。最后,你真的应该使用sp_executesqlEXEC(@SQL) 无法参数化。 Bad Habits to Kick : Using EXEC() instead of sp_executesql
    • @Larnu 我确实说过这只是为了展示这个想法,更不用说我确实说过“小心点”。我个人尽量不使用动态 sql,如果可以避免的话,更不用说如果数据结构建议不是糟糕的动态 sql 甚至不需要使用。不过谢谢:P
    • 我并不是说答案是“错误的”,Doug(除了@Count 后面缺少的右括号会导致语法错误),只是它们是使用的好习惯;安全引用以确保不会发生注入,后者因为它更容易参数化并促进它(参数化)。 :)
    • 什么可以防止删除所有在您正在查看的 DETAIL 表中不匹配但可能在另一个详细表中匹配的 INSTANCE 行?
    • @Larnu 你说得对,抱歉不是故意的。我本可以提供更多关于为什么要小心动态 sql 的详细信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-06
    • 2016-02-17
    • 1970-01-01
    • 2018-12-01
    • 2017-11-02
    • 2022-11-01
    • 2021-12-09
    相关资源
    最近更新 更多