防止在MS-SQL表中循环引用

时间:2019-04-01 13:19:50

标签: sql sql-server sql-server-2008 circular-reference

我有一个ID为ID和ParentAccountID的Account表。这是重现步骤的脚本。

如果ParentAccountID为NULL,则将其视为顶级帐户。 每个帐户最终都应以顶级帐户结尾,即ParentAccountID为NULL

    Declare @Accounts table (ID INT, ParentAccountID INT )    


    INSERT INTO @Accounts values (1,NULL), (2,1), (3,2) ,(4,3), (5,4), (6,5)

    select * from @Accounts

     -- Request to update ParentAccountID to 6 for the ID 3
    update @Accounts  
    set ParentAccountID = 6
    where ID = 3

    -- Now the above update will cause circular reference 
    select * from @Accounts

当请求要更新一个帐户的ParentAccountID时,如果这导致循环引用,则在更新之前需要对其进行识别。

任何有想法的人!

4 个答案:

答案 0 :(得分:3)

似乎您已经为表定义了一些业务规则:

  • 所有连锁店都必须以顶级帐户结尾
  • 一条链可能没有循环引用

您有两种方法可以执行此操作。

您可以在数据库中创建一个trigger,然后检查触发器中的逻辑。这具有在数据库内部运行的优势,因此它适用于每个事务,而与客户端无关。但是,数据库触发器并不总是很流行。我将它们视为side effect,它们可能很难调试。触发器作为SQL的一部分运行,因此,如果触发器运行缓慢,则SQL将会运行缓慢。

另一种方法是在应用程序层中强制执行此逻辑-无论与数据库进行通信。这更易于调试,并使您的业务逻辑对新开发人员明确-但它不在数据库中运行,因此如果您有多个客户端应用程序,最终可能会复制逻辑。

答案 1 :(得分:2)

这里是一个示例,您可以将其用作实现数据库约束的基础,该约束应防止在单行更新中使用循环引用;我认为,如果更新了多行,这不会阻止循环引用。

/*
ALTER TABLE dbo.Test  DROP CONSTRAINT chkTest_PreventCircularRef
GO
DROP FUNCTION dbo.Test_PreventCircularRef 
GO
DROP TABLE dbo.Test 
GO
*/

CREATE TABLE dbo.Test (TestID INT PRIMARY KEY,TestID_Parent INT)
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 1 AS TestID,NULL  AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 2 AS TestID,1     AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 3 AS TestID,2     AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 4 AS TestID,3     AS TestID_Parent
INSERT INTO dbo.Test(TestID,TestID_Parent) SELECT 5 AS TestID,4     AS TestID_Parent
GO

GO
CREATE FUNCTION dbo.Test_PreventCircularRef (@TestID INT,@TestID_Parent INT)
RETURNS INT
BEGIN
    --FOR TESTING:
    --SELECT * FROM dbo.Test;DECLARE @TestID INT=3,@TestID_Parent INT=4

    DECLARE @ParentID INT=@TestID
    DECLARE @ChildID INT=NULL
    DECLARE @RetVal INT=0
    DECLARE @Ancestors TABLE(TestID INT)
    DECLARE @Descendants TABLE(TestID INT)

    --Get all descendants
    INSERT INTO @Descendants(TestID) SELECT TestID FROM dbo.Test WHERE TestID_Parent=@TestID
    WHILE (@@ROWCOUNT>0)
    BEGIN
        INSERT INTO @Descendants(TestID)
            SELECT t1.TestID
            FROM dbo.Test t1
            LEFT JOIN @Descendants relID ON relID.TestID=t1.TestID
            WHERE relID.TestID IS NULL
            AND t1.TestID_Parent IN (SELECT TestID FROM @Descendants)
    END

    --Get all ancestors
    --INSERT INTO @Ancestors(TestID) SELECT TestID_Parent FROM dbo.Test WHERE TestID=@TestID
    --WHILE (@@ROWCOUNT>0)
    --BEGIN
    --  INSERT INTO @Ancestors(TestID)
    --      SELECT t1.TestID_Parent
    --      FROM dbo.Test t1
    --      LEFT JOIN @Ancestors relID ON relID.TestID=t1.TestID_Parent
    --      WHERE relID.TestID IS NULL
    --      AND t1.TestID_Parent IS NOT NULL
    --      AND t1.TestID IN (SELECT TestID FROM @Ancestors)
    --END

    --FOR TESTING:
    --SELECT TestID AS [Ancestors] FROM @Ancestors;SELECT TestID AS [Descendants] FROM @Descendants;

    IF EXISTS (
        SELECT *
        FROM @Descendants
        WHERE TestID=@TestID_Parent
    )
    BEGIN
        SET @RetVal=1
    END

    RETURN @RetVal
END
GO

ALTER TABLE dbo.Test 
  ADD CONSTRAINT chkTest_PreventCircularRef
  CHECK (dbo.Test_PreventCircularRef(TestID,TestID_Parent) = 0); 
GO

SELECT * FROM dbo.Test

--This is problematic as it creates a circular reference between TestID 3 and 4; it is now prevented
UPDATE dbo.Test SET TestID_Parent=4 WHERE TestID=3

答案 2 :(得分:1)

在SQL中使用自引用表/递归关系进行处理并不容易。我想这可以通过以下事实得到证明:多个人仅检查一次深度循环就无法解决问题。

要使用表约束实施此操作,您将需要基于递归查询的检查约束。最好的情况是特定于DBMS的支持,并且如果必须在每个更新上运行它,它可能无法很好地运行。

我的建议是让包含UPDATE语句的代码强制执行此操作。这可能有几种形式。无论如何,如果需要严格执行它,可能需要将对表的UPDATE访问限制为存储的proc或外部服务使用的服务帐户。

使用存储过程的方式与CHECK约束类似,不同之处在于您可以在执行更新之前使用过程(迭代)逻辑来查找周期。但是,在存储的proc中放置太多逻辑已经变得不受欢迎,并且是否应该执行这种类型的检查是团队之间/团队之间/组织之间进行判断的依据。

同样,使用基于服务的方法将使您可以使用过程逻辑来查找周期,并且可以用更适合此类逻辑的语言来编写它。这里的问题是,如果服务不是您的体系结构的一部分,那么引入一个全新的层会很繁重。但是,服务层(至少目前)可能被认为比通过存储的过程集中更新更现代/更受欢迎。

牢记这些方法-并且理解数据库中的过程和递归语法都是特定于DBMS的-确实有太多可能的语法选项可供使用。但想法是:

  • 检查提议的父母。
  • 递归检查其父项
  • 您是否曾在达到顶级帐户之前就接触了建议的孩子?如果不允许,请允许更新

答案 3 :(得分:0)

最后,我在一些失败后创建了脚本,对我来说运行正常。

import React from 'react';
import {StyleSheet} from 'react-native';
import HTMLView from 'react-native-htmlview';

class App extends React.Component {
  render() {
    const htmlContent = `<p><a href="http://jsdf.co">&hearts; nice job!</a></p>`;

    return (
      <HTMLView
        value={htmlContent}
        stylesheet={styles}
      />
    );
  }
}

const styles = StyleSheet.create({
  a: {
    fontWeight: '300',
    color: '#FF3366', // make links coloured pink
  },
});
相关问题