背景
我有两个具有一对多关系的表(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"列更新我需要确保仍满足此要求。方案:
困难的部分是,如果我一次更新每个地址,并且上述各点必须单独应用于每个公司,这也必须有效。
当前解决方案
我有一个在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秒)。 我试图想办法以另一种方式做到这一点,但我没有想出任何东西,所以任何帮助都会受到赞赏。
答案 0 :(得分:0)
我知道你问这个问题已经有6个星期了,但是我今天晚上有太多时间并且享受了一些谜题。所以这是我使用触发器的解决方案。
在用户发布更新之后使用触发器更新表是危险的,因为它会再次调用触发器,这会再次更新表...简而言之:无限循环。降压必须停在某个地方。为此,我要在Addresses
表中添加一列:FireTrigger
。如果是,则触发器将更新表。否则,触发器将不执行任何操作。
我将Addresses
表上的PK名称更改为AddressesID
。 ID
只是让我疯了。
在没有设置多个地址的情况下,您没有指定应该选择哪个地址作为主地址的规则。我的规则是,如果将多个地址指定为公司的主要地址,则选择最低AddressID
作为主要地址。
尚未经过严格测试。在某些边缘案例中可能存在漏洞,但您明白了这一点。
这是我的解决方案:
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
为主
cte1
处理用户将一个/多个地址设置为Main的情况。在这种情况下,它会选择最终主要地址的最低AddressID
。
cte2
在插入/更新后找到没有主地址的公司。发生这种情况有两个原因:您将当前的主要地址设置为false(请参阅cte3
),或者您插入了一个全新的公司,其主要地址= false(请参阅cte4
)
cte3
挖掘出之前主要地址的AddressID
。
cte4
为没有主地址的新插入公司获得最低AddressID
。
从cte1
,cte3
和cte4
添加行可以为您提供已经过插入/更新的每家公司的主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
这比其他触发器简单:
cte2
找到没有主要地址的公司(没有cte1
- 它不是拼写错误。)cte3
为主要地址选择剩余 AddressID