每组设置主记录(一对多关系)

时间:2014-08-19 07:20:55

标签: triggers sql-server-2008-r2

背景

我有两个具有一对多关系的表(Companies.ID(1)到Addresses.CompanyID(N))。地址表还有一列" Mainaddress"属于' bit'。

示例数据

公司:

ID | Name
1  | Company1
2  | Company2

地址:

ID | CompanyID | MainAddress
1  | 1         | true
2  | 1         | false
3  | 1         | false
4  | 2         | false
5  | 2         | true

我想要实现的目标

每家公司必须拥有一个(且只有一个)主要地址。每次" MainAddress"列更新我需要确保仍满足此要求。方案:

  1. ID为2的地址记录成为主要地址 - > ID为1的地址的MainAddress必须设置为false
  2. ID为1的地址记录的MainAddress从true设置为false - >如果它是该公司的唯一记录,则需要再次设置为true,否则其他记录之一成为主要地址(不管哪一个)
  3. ID为2的地址和& 3成为主要地址 - >必须将两者中的一个(不管哪个)的MainAddress设置为true,该公司的每个其他MainAddress都必须设置为false
  4. 删除MainAddress = true的记录 - >该公司的其他记录之一必须成为主要地址(同样,哪一个并不重要)
  5. 插入了MainAddress = true的新记录 - >该公司的每个其他MainAddress必须设置为false
  6. 困难的部分是,如果我一次更新每个地址,并且上述各点必须单独应用于每个公司,这也必须有效。

    当前解决方案

    我有一个在Insert,Update,Delete之后运行的数据库触发器,它为插入或删除的表中的每个CompanyID创建一个游标:

    INSERT INTO #Temp_SetMainAddress
    SELECT DISTINCT "CompanyID" FROM inserted;
    
    DECLARE reccursor CURSOR STATIC FORWARD_ONLY READ_ONLY FOR
    SELECT ID
    FROM #Temp_SetMainAddress
    

    然后根据每个公司的5个点单独应用更改。

    问题

    我想知道这是否可以在不依赖游标的情况下实现?因为它是我担心的性能(如果我更新大约7000个地址,它已经需要大约20秒)。 我试图想办法以另一种方式做到这一点,但我没有想出任何东西,所以任何帮助都会受到赞赏。

1 个答案:

答案 0 :(得分:0)

我知道你问这个问题已经有6个星期了,但是我今天晚上有太多时间并且享受了一些谜题。所以这是我使用触发器的解决方案。

我们开始前的几句话:

  1. 在用户发布更新之后使用触发器更新表是危险的,因为它会再次调用触发器,这会再次更新表...简而言之:无限循环。降压必须停在某个地方。为此,我要在Addresses表中添加一列:FireTrigger。如果是,则触发器将更新表。否则,触发器将不执行任何操作。

  2. 我将Addresses表上的PK名称更改为AddressesIDID只是让我疯了。

  3. 在没有设置多个地址的情况下,您没有指定应该选择哪个地址作为主地址的规则。我的规则是,如果将多个地址指定为公司的主要地址,则选择最低AddressID作为主要地址。

  4. 尚未经过严格测试。在某些边缘案例中可能存在漏洞,但您明白了这一点。

  5. 这是我的解决方案:

    插入和更新时触发

    CREATE TRIGGER Addresses_OnInsertUpdate
        ON Addresses
        AFTER INSERT, UPDATE
    AS
    BEGIN
        DECLARE @tmp TABLE (CompanyID int, MainAddressID int)
    
        ;WITH cte1 AS
        (
            SELECT  AddressID, CompanyID,
                    ROW_NUMBER() OVER (PARTITION BY CompanyID ORDER BY AddressID) AS RowNumber
            FROM    inserted
            WHERE   MainAddress = 1
                AND FireTrigger = 1
        )
    
        INSERT INTO @tmp (CompanyID, MainAddressID)
            SELECT  CompanyID, AddressID
            FROM    cte1
            WHERE   RowNumber = 1
    
        ;WITH cte2 AS
        (
            SELECT      i.CompanyID
            FROM        inserted    i
            LEFT  JOIN  Addresses   a ON i.CompanyID = a.CompanyID
                                     AND a.MainAddress = 1
            WHERE       i.FireTrigger = 1
            GROUP BY    i.CompanyID
            HAVING      COUNT(a.AddressID) = 0
        ),
        cte3 AS
        (
            SELECT      d.CompanyID, d.AddressID
            FROM        deleted     d
            INNER JOIN  cte2        c2 ON d.CompanyID = c2.CompanyID
            WHERE       d.MainAddress = 1
        ),
        cte4 AS
        (
            SELECT      i.CompanyID, i.AddressID,
                        ROW_NUMBER() OVER (PARTITION BY i.CompanyID ORDER BY i.AddressID) AS RowNumber
            FROM        inserted    i
            INNER JOIN  cte2        c2 ON i.CompanyID = c2.CompanyID
        )
    
        INSERT INTO @tmp (CompanyID, MainAddressID)
            SELECT  CompanyID, AddressID
            FROM    cte3
            UNION
            SELECT  CompanyID, AddressID
            FROM    cte4
            WHERE   RowNumber = 1
    
    
        UPDATE Addresses
            SET         MainAddress = CASE a.AddressID
                                        WHEN MainAddressID THEN 1
                                        ELSE 0
                                      END,
                        FireTrigger = 0
            FROM        Addresses   a
            INNER JOIN  @tmp        tmp ON a.CompanyID = tmp.CompanyID
    END
    

    @tmp表决定在表格更新为用户的规范后,选择AddressID为主

    1. cte1处理用户将一个/多个地址设置为Main的情况。在这种情况下,它会选择最终主要地址的最低AddressID

    2. cte2在插入/更新后找到没有主地址的公司。发生这种情况有两个原因:您将当前的主要地址设置为false(请参阅cte3),或者您插入了一个全新的公司,其主要地址= false(请参阅cte4

    3. cte3挖掘出之前主要地址的AddressID

    4. cte4为没有主地址的新插入公司获得最低AddressID

    5. cte1cte3cte4添加行可以为您提供已经过插入/更新的每家公司的主AddressID。要触发触发器,您必须将FireTrigger设置为true:

      INSERT INTO Addresses (..., FireTrigger)
          VALUES (...., 1)
      
      UPDATE Adddresses
          SET ..., FireTrigger = 1
          WHERE ...
      

      删除时触发

      CREATE TRIGGER dbo.Addresses_OnDelete
          ON dbo.Addresses
          AFTER DELETE
      AS
      BEGIN
          ;WITH cte2 AS
          (
              SELECT      d.CompanyID
              FROM        deleted     d
              LEFT JOIN   Addresses   a ON d.CompanyID = a.CompanyID
                                       AND a.MainAddress = 1
              GROUP BY    d.CompanyID
              HAVING      COUNT(a.AddressID) = 0
          ),
          cte3 AS
          (
              SELECT      a.CompanyID, a.AddressID,
                          ROW_NUMBER() OVER (PARTITION BY a.CompanyID ORDER BY a.AddressID) AS RowNumber
              FROM        cte2        c2
              INNER JOIN  Addresses   a   ON c2.CompanyID = a.CompanyID
          )
      
          UPDATE Addresses
              SET         MainAddress = 1,
                          FireTrigger = 0
              FROM        Addresses   a
              INNER JOIN  cte3        c3 ON a.CompanyID = c3.CompanyID
                                        AND a.AddressID = c3.AddressID
              WHERE       c3.RowNumber = 1
      END
      

      这比其他触发器简单:

      1. 再次,cte2找到没有主要地址的公司(没有cte1 - 它不是拼写错误。)
      2. cte3为主要地址选择剩余 AddressID