【问题标题】:How can we use Triggers to insert records for particular column values that are present in main table我们如何使用触发器插入主表中存在的特定列值的记录
【发布时间】:2021-03-25 11:52:12
【问题描述】:

这是我的主表(参考)

    CREATE TABLE [ptref].[EarningDecode](
    [CountryGroupingCd] [char](2) NOT NULL,
    [EarningsCd] [char](4) NOT NULL,
    [EarningsDesc] [nchar](25) NULL,
    [CreateUserID] [varchar](50) NOT NULL,
    [CreateDttm] [datetime] NOT NULL,
    [UpdateUserId] [varchar](50) NOT NULL,
    [UpdateDttm] [datetime] NOT NULL,
 CONSTRAINT [PK_EarningDecode] PRIMARY KEY CLUSTERED 
(
    [CountryGroupingCd] ASC,
    [EarningsCd] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]

GO 

还有一张桌子

CREATE TABLE [rt].[AdditionalPaymentsRealTime](
    [PeopleKey] [int] NOT NULL,
    [PersonnelNbr] [varchar](8) NOT NULL,
    [SAPStartDt] [datetime] NOT NULL,
    [SAPEndDt] [datetime] NOT NULL,
    [EarningsCd] [char](4) NOT NULL,
    [FlatAmountPaymentAmt] [decimal](13, 2) NULL,
    [CurrencyCd] [char](5) NULL,
    [PaymentFrequencyCd] [char](3) NULL,
    [FrequencyCountNbr] [decimal](3, 0) NULL,
    [AdditionalPaymentsNbr] [decimal](7, 2) NULL,
    [UnitCd] [char](3) NULL,
    [PayrollWageTypeInd] [char](1) NOT NULL,
    [RecordTypeInd] [smallint] NOT NULL,
    [MRDRExpiryDt] [datetime] NULL,
    [SAPChangeDt] [datetime] NULL,
    [SAPUpdateUserNm] [varchar](50) NULL,
    [DocumentCreateDttm] [datetime] NOT NULL,
    [IDOCId] [varchar](40) NOT NULL,
    [IDOCCounter] [varchar](40) NOT NULL,
    [CreateUserID] [varchar](50) NOT NULL,
    [CreateDttm] [datetime] NOT NULL,
    [UpdateUserId] [varchar](50) NOT NULL,
    [UpdateDttm] [datetime] NOT NULL,
 CONSTRAINT [PK_AdditionalPaymentsRealTime] PRIMARY KEY NONCLUSTERED 
(
    [PeopleKey] ASC,
    [PersonnelNbr] ASC,
    [SAPStartDt] ASC,
    [SAPEndDt] ASC,
    [EarningsCd] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]

GO 

这里的要求是在 rt.AdditionalPaymentsRealTime 中仅记录应插入 EarningsCd in (Select distinct EarningsCd from ptref.EarningDecode) 的位置,对于无效值应抛出错误。

我想出了以下查询,但它似乎不起作用。

CREATE TRIGGER trgrr
ON rt.AdditionalPaymentsRealTime
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON ;
Declare @EarningsCdnew char(4)
Set @EarningsCdnew = (Select EarningsCd from rt.AdditionalPaymentsRealTime)
IF (NOT EXISTS(
      SELECT EarningsCd 
      FROM ptref.EarningDecode 
      WHERE EarningsCd = @EarningsCdnew))
ROLLBACK TRAN ;   
THROW 60000, 'Cannot process invalid EarningsCd', 1 ; 

END

有人可以帮忙吗?

【问题讨论】:

  • 为什么不使用外键约束?
  • 声明Set @EarningsCdnew = (Select EarningsCd from rt.AdditionalPaymentsRealTime)假设rt.AdditionalPaymentsRealTime只能包含一个行;我怀疑这是真的。
  • 最后,在TRIGGER 中有一个ROLLBACK 是个坏主意。相反,您应该增加THROW 的严重性(至少为10),然后调用进程将使用ROLLBACK 处理错误和/或输入外部CATCH。将 ROLLBACK 放入 TRIGGER 会产生额外的错误。
  • 您的 throw 与 IF 语句的 THEN 部分不在同一块内 - 您没有 BEGIN / END 。 INSTEAD-OF 触发器将实际将更改移动到触发器编写器的正确表中的负担 - 您也忽略了这一点。让我们养成良好的习惯。始终使用语句终止符 - 而不是随机使用。

标签: sql sql-server triggers insert


【解决方案1】:

与其继续发布更多的 cmets,我将使用一个答案来解释您在这里的缺陷,包括我自己和 cmets 中其他人所触及的所有缺陷。

首先我和@Charlieface 提出的观点是,您的触发器存在缺陷,因为它假定单行计数。具体来说,这个问题出在这一行:

Set @EarningsCdnew = (Select EarningsCd from rt.AdditionalPaymentsRealTime)

此语句仅在对象 (rt.AdditionalPaymentsRealTime) 包含一行或零行时才有效。如果没有,那么您将收到此错误:

子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。

这是因为您不能为 标量 变量分配许多行的值;它只能被分配一个标量值。

接下来,让我们看看您的IFROLLBACKTHROW。正如@Smor 突出显示的那样,首先您的THROW 不在您的IF 内,这是一个问题。这意味着每次每次调用触发器(并且之前的错误不会发生)都会抛出错误'Cannot process invalid EarningsCd'。如果我们添加一些更好的格式,这就是你的IF 块看起来更像:

IF (NOT EXISTS(SELECT EarningsCd 
               FROM ptref.EarningDecode 
               WHERE EarningsCd = @EarningsCdnew))
    ROLLBACK TRAN;   
THROW 60000, 'Cannot process invalid EarningsCd', 1 ; 

请注意,我取消了 THROW 的缩进;那是因为它不是IF 的一部分。当您不使用BEGINENDIF 时,IF 解析为TRUE 时执行下一行。但是,这并不意味着如果解决方案正确,则添加BEGINEND。你不应该在TRIGGER 中执行ROLLBACK;这会导致调用 SQL 中的进一步错误。相反,只需将THROW 放入您的IF 并将严重性增加到10

IF (NOT EXISTS(SELECT EarningsCd 
               FROM ptref.EarningDecode 
               WHERE EarningsCd = @EarningsCdnew))
    THROW 60000, 'Cannot process invalid EarningsCd', 10; 

然而,问题是所有这些陈述都完全没有实际意义。您应该真正在这里做的是使用外键约束。然而,问题是你不能用你当前的设计来做到这一点。在您的表ptref.EarningDecode 上,您已将列(复数)CountryGroupingCdEarningsCd 定义为您的PRIMARY KEY,但是,在您的表rt.AdditionalPaymentsRealTime 中,您缺少列CountryGroupingCd,并且您没有说你在另一个表的EarningsCd 列上有一个UNIQUE INDEX(或CONSTRAINT)。因此,您无法创建指向 ptref.EarningDecode 的外键约束。

为了让事情“更糟”rt.AdditionalPaymentsRealTime 有一个由 5 列组成的主键;这肯定是一个设计缺陷。主键通常由 1 列组成,在极少数情况下可能由 2 列组成。它们可能有更多的场合,但这些通常是用于多个表之间的多对多关系的桥接表。因此,表中唯一的列是主键列,仅此而已;这显然不是这里的情况。

这可能意味着以下几种情况之一:

  1. 当您应该有多个表时,您正在使用多个列来保持唯一性
  2. 其他列不应该是主键的一部分,但 PK 应该是 NONCLUSTERED 并且列(包括 PK)应该在 CLUSTERED INDEX 中(我怀疑 rt.AdditionalPaymentsRealTime)。李>
  3. PK 定义错误,其他列不应该是它的一部分,或者CLUSTERED INDEX
  4. PK 应该是一个完全独立的列,并且应该在您定义为 PK 的列上创建 UNIQUE INDEX/UNIQUE CONSTRAINT

看看ptref.EarningDecode 的表我会这应该是几个表,2 个具有单独的 PK 用于 EarningCountGroup,然后是具有外键约束的 3rd两者都是它自己独立的主键约束。对于 AdditionalPaymentsRealTime,我怀疑 PK 定义完全错误,第 3 点或第 4 点都适用。

由于我们没有样本数据,因此无法知道这里的正确答案是什么,但我强烈希望其中一个答案是正确的。修复设计后,您可以使用新的单列主键轻松地将 FOREIGN KEY CONSTRAINT 从表 rt.AdditionalPaymentsRealTime 变为新定义的 ptref.EarningDecode

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 2014-06-14
    • 2013-10-05
    • 2021-08-28
    相关资源
    最近更新 更多