【问题标题】:Complicated SQL constraint复杂的 SQL 约束
【发布时间】:2022-11-17 01:08:10
【问题描述】:

我想防止使用约束写入数据,但约束很复杂。

我有 3 列:A B C

如果 A & B 匹配现有记录,则记录重复,如果 A & C 匹配现有记录,但 B & C 匹配现有记录是有效的,除非 A=2。

例如。 假设现有记录

A=1,B=2,C=3

如果我插入

A=1,B=2,C=4

我应该得到一个错误

如果我插入

A=1,B=99,C=3

我应该得到一个错误

如果我插入:

A=99,B=2,C=3

那会成功的。

如果我插入:

A=2,B=2,C=3

我应该得到一个错误。

检查必须在 INSERT 时间完成。我不能选择然后插入,因为数据可能在选择和插入之间发生了变化。

想法:

  • 如果我将逻辑放在 TRIGGER 中,那行得通吗?我不确定触发器是否是 100% 原子的。
  • 交易有效吗?我不想锁定表,因为其他进程会不断插入数据。
  • 我可以添加一个或多个约束吗?有没有办法在插入时检查约束?

【问题讨论】:

  • 触发器定义的代码在哪里?还请添加您正在使用的表的定义(如 CREATE TABLE ... 语句)。 (像DBFIDDLE这样的东西会让你的问题更容易回答)
  • 这是一个非常简化的示例,我无法显示真实的表格。我会尝试 DBFIDDLE。
  • 所以最后的要求是如果A=2那么B&C的组合不能出现任何地方,甚至在哪里A!=2??
  • 如果你先 INSERT INTO dbo.YourTable (A,B,C) VALUES(2,2,3); 然后 INSERT INTO dbo.YourTable (A,B,C) VALUES(12,2,3); 可以吗?
  • @Larnu 2,2,3 应该失败,但是 12,2,3 应该成功。

标签: sql sql-server


【解决方案1】:

对于 A & BA & C 列的唯一性的简单组合,您可以使用唯一索引或唯一约束来解决这个问题。然而,对于后一个问题,这并不容易。

我在这里使用TRIGGER,并检查是否找到具有相同值BC的匹配行(ID不同,否则该行将与自身匹配)以及插入的行的值为 2 for A 或现有行的值为 2。如果找到匹配,则错误为THROWn,导致INSERT/UPDATE失败。

CREATE TABLE dbo.YourTable (ID int IDENTITY,
                            A int NOT NULL,
                            B int NOT NULL,
                            C int NOT NULL);
GO

CREATE UNIQUE INDEX UX_YourTable_AB ON dbo.YourTable (A,B);
GO
CREATE UNIQUE INDEX UX_YourTable_AC ON dbo.YourTable (A,C);
GO

CREATE TRIGGER trg_chk_YourTable_BC_A2 ON dbo.YourTable
AFTER INSERT, UPDATE AS
BEGIN

    IF EXISTS (SELECT 1
               FROM inserted i
                    JOIN dbo.YourTable YT ON i.B = YT.B
                                         AND i.C = YT.C
                                         AND i.ID != YT.ID
                WHERE 2 IN (i.A,YT.A))
        THROW 54697, N'The check trigger ''trg_chk_YourTable_BC_A2'' on table ''dbo.YourTable'' failed. Cannot insert duplicate row on columns ''B'' and ''C'' where at least one row has the value of ''2''.',16;
END;
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(1,2,3);
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(1,2,4); --Fails
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(1,99,3); --Fails
GO
INSERT INTO dbo.YourTable (A,B,C)
VALUES(99,2,3); --Works
GO
INSERT INTO dbo.YourTable (A,B,C)
VALUES(2,2,3); --Fails
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(4,5,6); --Works

GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(5,5,6); --Works
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(2,5,6); --Fails
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(3,7,9); --Works
GO

INSERT INTO dbo.YourTable (A,B,C)
VALUES(2,7,9); --Fails

GO
SELECT *
FROM dbo.YourTable;

GO
DROP TABLE dbo.YourTable;

显然,您很可能希望更改错误编号和消息,但我写了一些合理的东西。 db<>fiddle

【讨论】:

  • 谢谢@larnu,我认为触发器是正确的答案。
【解决方案2】:

我会尝试使用公用表表达式并在插入语句中添加一个 where 子句。如果插入返回零行更新,那么考虑插入失败?

-- Assuming A and B do not permit NULLs
; WITH cteAB AS (
    SELECT DISTINCT a,b FROM YourTable
)
, cteBC AS (
    SELECT DISTINCT b,c FROM YourTable
)
INSERT  YourTable ( a, b )
SELECT NewA, NewB
FROM YourTable yt 
LEFT JOIN cteAB ab ON ab.a=NewA AND ab.b=NewB
LEFT JOIN cteBC bc ON bc.a=NewA AND bc.b=NewB
WHERE ( ab.a IS NULL AND bc.a IS NULL )
    OR NewA=2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    • 2022-08-22
    • 1970-01-01
    • 1970-01-01
    • 2012-01-13
    • 2013-05-25
    相关资源
    最近更新 更多