【问题标题】:Using temporary table in where clause在 where 子句中使用临时表
【发布时间】:2010-11-14 16:20:25
【问题描述】:

我想删除一些 (6) 表中具有相同字段值集的许多行。我可以通过删除每个表中的一个子查询的结果来做到这一点(解决方案 1),这将是多余的,因为子查询每次都是相同的;所以我想将子查询的结果存储在一个临时表中,并删除表中每一行(临时表的)的值(解决方案 2)。哪种解决方案更好?

第一个解决方案:

DELETE FROM dbo.SubProtocols
WHERE ProtocolID IN (
    SELECT ProtocolID
    FROM dbo.Protocols
    WHERE WorkplaceID = @WorkplaceID
)

DELETE FROM dbo.ProtocolHeaders
WHERE ProtocolID IN (
    SELECT ProtocolID
    FROM dbo.Protocols
    WHERE WorkplaceID = @WorkplaceID
)

// ...

DELETE FROM dbo.Protocols
WHERE WorkplaceID = @WorkplaceID

第二种解决方案:

DECLARE @Protocols table(ProtocolID int NOT NULL)

INSERT INTO @Protocols
SELECT ProtocolID
FROM dbo.Protocols
WHERE WorkplaceID = @WorkplaceID

DELETE FROM dbo.SubProtocols
WHERE ProtocolID IN (
    SELECT ProtocolID
    FROM @Protocols
)

DELETE FROM dbo.ProtocolHeaders
WHERE ProtocolID IN (
    SELECT ProtocolID
    FROM @Protocols
)

// ...

DELETE FROM dbo.Protocols
WHERE WorkplaceID = @WorkplaceID

是否可以在没有子查询的情况下执行解决方案 2?说做WHERE ProtocolID IN @Protocols(但语法正确)?

我正在使用 Microsoft SQL Server 2005。

【问题讨论】:

    标签: sql sql-server sql-server-2005


    【解决方案1】:

    如果没有临时表,您可能会在第二次删除中删除不同的行,但这需要执行三个操作。

    您可以从第一个表中删除并使用 OUTPUT INTO 子句将所有 ID 插入临时表,然后使用该临时表删除第二个表。这将确保您只使用两个语句删除相同的键。

    declare @x table(RowID int identity(1,1) primary key, ValueData varchar(3))
    declare @y table(RowID int identity(1,1) primary key, ValueData varchar(3))
    declare @temp table (RowID int)
    
    insert into @x values ('aaa')
    insert into @x values ('bab')
    insert into @x values ('aac')
    insert into @x values ('bad')
    insert into @x values ('aae')
    insert into @x values ('baf')
    insert into @x values ('aag')
    
    insert into @y values ('aaa')
    insert into @y values ('bab')
    insert into @y values ('aac')
    insert into @y values ('bad')
    insert into @y values ('aae')
    insert into @y values ('baf')
    insert into @y values ('aag')
    
    DELETE @x
        OUTPUT DELETED.RowID
        INTO @temp
        WHERE ValueData like 'a%'
    
    DELETE y
        FROM @y               y
            INNER JOIN @temp  t ON y.RowID=t.RowID
    
    select * from @x
    select * from @y
    

    选择输出:

     RowID       ValueData
    ----------- ---------
    2           bab
    4           bad
    6           baf
    
    (3 row(s) affected)
    
    RowID       ValueData
    ----------- ---------
    2           bab
    4           bad
    6           baf
    

    (受影响的 3 行)

    【讨论】:

    • Re »如果没有临时表,您可能会在第二次删除中删除不同的行«:我正在事务中执行整个查询。这仍然是个问题吗?
    • 第一次删除之后但在第二次删除之前,有人添加了一个符合删除条件的新项目(基于 where 条件)。如果没有临时表,您将在第二次删除时将其删除,但不会在第一次删除时被删除。使用临时表捕获第一次删除的实际行的键,您只会在第二次删除时删除这些键。
    【解决方案2】:

    DELETE ... FROM 是标准 SQL DELETE 的 T-SQL 扩展,它提供了使用子查询的替代方法。来自帮助:

    D.基于子查询使用 DELETE 并使用 Transact-SQL 扩展 以下示例显示了 用于删除的 Transact-SQL 扩展 来自基表的记录,即 基于连接或相关 子查询。第一个 DELETE 语句 显示 SQL-2003 兼容的子查询 解决方案,第二个 DELETE 语句显示 Transact-SQL 延期。两个查询都删除行 来自 SalesPersonQuotaHistory 表 根据存储的年初至今销售额 在 SalesPerson 表中。

    -- SQL-2003 Standard subquery
    
    USE AdventureWorks;
    GO
    DELETE FROM Sales.SalesPersonQuotaHistory 
    WHERE SalesPersonID IN 
        (SELECT SalesPersonID 
         FROM Sales.SalesPerson 
         WHERE SalesYTD > 2500000.00);
    GO
    
    
    
    -- Transact-SQL extension
    USE AdventureWorks;
    GO
    DELETE FROM Sales.SalesPersonQuotaHistory 
    FROM Sales.SalesPersonQuotaHistory AS spqh
        INNER JOIN Sales.SalesPerson AS sp
        ON spqh.SalesPersonID = sp.SalesPersonID
    WHERE sp.SalesYTD > 2500000.00;
    GO
    

    在您的第二个解决方案中,您会想要类似

    -- 未经测试! 删除自 dbo.SubProtocols -- ProtocolHeaders 等 从 dbo.SubProtocols 内部加入 @Protocols ON SubProtocols.ProtocolID = @Protocols.ProtocolID

    但是!!

    是否不可能更改您的设计,以便所有辅助协议表都有一个FOREIGN KEYDELETE CASCADE 到主Protocols 表?然后你可以只用DELETEProtocols 来处理其余的事情......

    编辑添加:

    如果您已经设置了FOREIGN KEYs,则需要使用 DDL 来更改它们(我认为需要删除并重新创建),以便它们启用 DELETE CASCADE。一旦到位,主表中的DELETE 将自动从子表中DELETE 相关记录。

    【讨论】:

    • 虽然我的表都有合适的FOREIGN KEYs,但我认为DELETE CASCADE 是最好的方法。
    • 我刚刚看到这不是一个真正的 DML 语句,它是对外键的描述。有没有办法在 DML 中做到这一点?
    【解决方案3】:

    可以试试这个

    DELETE  FROM dbo.ProtocolHeaders
    FROM    dbo.ProtocolHeaders INNER JOIN
            dbo.Protocols ON ProtocolHeaders.ProtocolID = Protocols.ProtocolID
    WHERE   Protocols.WorkplaceID = @WorkplaceID
    

    【讨论】:

      【解决方案4】:

      虽然您可以通过连接避免 SQL Server 中的子查询,如下所示:

      delete from sp
      from subprotocols sp
      inner join protocols p on
          sp.protocolid = p.protocolid
          and p.workspaceid = @workspaceid
      

      您会发现,与您的任何一种方法相比,这并没有真正为您带来任何性能。通常,对于您的子查询,SQL Server 2005 将in 优化为inner join,因为它不依赖于每一行。此外,在您的情况下,SQL Server 可能会缓存子查询,因此很可能不需要将其放入临时表中。

      不过,第一种方式在交易期间容易受到Protocols 的变化的影响,而第二种方式则不会。只是想一想。

      【讨论】:

        猜你喜欢
        • 2011-08-24
        • 2010-10-12
        • 1970-01-01
        • 2018-04-12
        • 1970-01-01
        • 1970-01-01
        • 2010-12-25
        • 1970-01-01
        • 2014-08-28
        相关资源
        最近更新 更多