Триггер AFTER INSERT завершается ошибкой при обновлении более одной строки

Давно читающий, впервые позвонивший и все такое...

Вот бизнес-задача: пользователь делает один или несколько запросов на документы. Через некоторое время документ загружается в систему. Если этот документ соответствует одному или нескольким запросам, запрос документа выполняется. Так, например, может быть один или несколько запросов на документ А. Когда документ А загружается, все запросы для документа А выполняются.

И вот моя техническая проблема: у меня есть триггер AFTER INSERT для таблицы, в которой записана загрузка документа. Он проверяет таблицу DocumentRequest и обновляет каждую строку, соответствующую загруженному документу. Если совпадает только одна строка (только один запрос на документ А), все в порядке. Однако, если совпадений более одного, оператор UPDATE в триггере завершается ошибкой — я вижу эту ошибку:

Подзапрос вернул более 1 значения. Это не разрешено, когда подзапрос следует за =, !=, ‹, ‹= , >, >= или когда подзапрос используется как выражение.

Вот операторы CREATE для соответствующих таблиц:

CREATE TABLE [dbo].[DocumentRequest](
    [DocumentRequestId] [int] IDENTITY(1,1) NOT NULL,
    [BatchID] [int] NULL,
    [CertificateNum] [varchar](20) NOT NULL,
    [RequestedTypCd] [varchar](5) NULL,
    [RequestedReason] [varchar](60) NULL,
    [DestinationTypCd] [varchar](5) NULL,
    [DocumentPackageTypCd] [varchar](5) NULL,
    [RequestedDtm] [datetime] NOT NULL,
    [RequestNotes] [varchar](1000) NULL,
    [RequestStatusTypCd] [varchar](5) NOT NULL,
    [InactiveFlag] [char](1) NULL,
    [CreationDtm] [datetime] NOT NULL,
    [CreationUserID] [varchar](10) NOT NULL,
    [CompletedDtm] [datetime] NULL,
    [CompletedUserID] [varchar](10) NULL,
    [ModifiedDtm] [datetime] NULL,
    [ModifiedUserID] [varchar](10) NULL,
 CONSTRAINT [XPKDocumentRequest] PRIMARY KEY NONCLUSTERED 
(
    [DocumentRequestId] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

--------------
CREATE TABLE [dbo].[DocumentRequestContents](
    [DocumentTypCd] [varchar](5) NOT NULL,
    [CreationDtm] [datetime] NOT NULL,
    [CreationUserID] [varchar](10) NOT NULL,
    [DocumentRequestId] [int] NOT NULL,
    [DocumentReceivedDtm] [datetime] NULL,
    [DocumentReceivedFlag] [char](1) NULL,
    [DocumentIgnoreFlag] [char](1) NULL,
 CONSTRAINT [XPKDocumentRequestContents] PRIMARY KEY NONCLUSTERED 
(
    [DocumentRequestId] ASC,
    [DocumentTypCd] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

--------------
CREATE TABLE [dbo].[DocumentStorage](
    [DocumentStorageID] [int] IDENTITY(1,1) NOT NULL,
    [CertificateNum] [varchar](10) NULL,
    [DocumentHandleID] [int] NULL,
    [DocumentTypVal] [varchar](100) NULL,
    [CreationDtm] [datetime] NULL,
    [CreationUserID] [varchar](10) NULL,
    [lastupd_user] [varchar](8) NULL,
    [lastupd_stamp] [datetime] NULL,
    [DocumentNam] [varchar](100) NULL,
PRIMARY KEY NONCLUSTERED 
(
    [DocumentStorageID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]


--------------
CREATE TABLE [dbo].[DocumentProfile](
    [DocumentProfileID] [int] IDENTITY(1,1) NOT NULL,
    [DocumentTypCd] [varchar](5) NULL,
    [DocumentVersionNum] [varchar](10) NULL,
    [DocumentApproveInd] [varchar](1) NULL,
    [CreationDtm] [datetime] NULL,
    [CreationUserID] [varchar](10) NULL,
    [lastupd_stamp] [datetime] NULL,
    [lastupd_user] [varchar](8) NULL,
    [DocumentStorageTypVal] [varchar](100) NULL,
    [MIMETypVal] [varchar](50) NULL,
    [DocumentSourceTypCd] [varchar](5) NULL,
    [DocumentFormatTypCd] [varchar](5) NULL,
    [DocumentReceiveLocationTypCd] [varchar](5) NULL,
    [DocumentIndexTypCd] [varchar](5) NULL,
    [DocumentNam] [varchar](100) NULL,
    [DocumentTrackedInd] [char](1) NULL CONSTRAINT [DF_DocumentProfile_DocumentTrackedInd]  DEFAULT ('N'),
PRIMARY KEY NONCLUSTERED 
(
    [DocumentProfileID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

А триггер...

CREATE TRIGGER [trg_DMTDocumentReceived_DocumentStorage]
   ON  [dbo].[DocumentStorage]
   AFTER INSERT
AS 
BEGIN
    SET NOCOUNT ON;

    UPDATE DocumentRequestContents
    SET DocumentRequestContents.DocumentReceivedFlag = 'Y',
        DocumentRequestContents.DocumentReceivedDtm = getdate()
    FROM
        DocumentRequestContents
        INNER JOIN DocumentRequest
            ON DocumentRequestContents.DocumentRequestId = DocumentRequest.DocumentRequestId
        INNER JOIN DocumentProfile
            ON DocumentRequestContents.DocumentTypCd = DocumentProfile.DocumentTypCd
        INNER JOIN Inserted
            ON DocumentProfile.DocumentStorageTypVal = Inserted.DocumentTypVal
    WHERE
        (DocumentRequestContents.DocumentReceivedFlag <> 'Y' OR
            DocumentRequestContents.DocumentReceivedFlag IS NULL)
        AND (DocumentRequest.InactiveFlag IS NULL)
        AND (DocumentRequest.CertificateNum = Inserted.CertificateNum)
        AND (DocumentProfile.DocumentStorageTypVal = Inserted.DocumentTypVal)
END

Я немного в недоумении, куда идти отсюда. Можешь дать парню руку?

EDIT: запрос может содержать более одного документа, поэтому таблица, которая обновляется в триггере (DocumentRequestContents), также имеет триггер, который определяет, был ли выполнен весь запрос. Насколько я понимаю, у него тоже нет подзапроса, но вот он:

CREATE TRIGGER [trg_DMTDocumentReceived_CompletenessCheck]
   ON  [dbo].[DocumentRequestContents] 
   AFTER UPDATE
AS 
BEGIN
    SET NOCOUNT ON;

    DECLARE @TotalDocs int, @ReceivedDocs int

    IF UPDATE(DocumentReceivedFlag)
    BEGIN
        IF (SELECT DocumentReceivedFlag FROM Inserted) = 'Y' AND (SELECT DocumentReceivedFlag FROM Deleted) = 'N'
        BEGIN
            SELECT @TotalDocs = count(*)
            FROM DocumentRequestContents
                INNER JOIN Inserted
                    ON DocumentRequestContents.DocumentRequestID = Inserted.DocumentRequestID
            WHERE (DocumentRequestContents.DocumentIgnoreFlag <> 'Y' OR DocumentRequestContents.DocumentIgnoreFlag IS NULL)

            SELECT @ReceivedDocs = count(*)
            FROM DocumentRequestContents
                INNER JOIN Inserted
                    ON DocumentRequestContents.DocumentRequestID = Inserted.DocumentRequestID
            WHERE DocumentRequestContents.DocumentReceivedFlag = 'Y'

            IF (@ReceivedDocs = @TotalDocs)
            BEGIN
                UPDATE DocumentRequest
                SET RequestStatusTypCd = 'CMPLT',
                    CompletedDtm = getdate(),
                    CompletedUserID = 'SYSTEM',
                    ModifiedDtm = getdate(),
                    ModifiedUserID = 'SYSTEM'
                FROM DocumentRequest
                    INNER JOIN Inserted 
                        ON DocumentRequest.DocumentRequestId = Inserted.DocumentRequestId
            END
        END
    END
END

Спасибо, Джейсон


person Jason Ellsworth-Aults    schedule 14.01.2010    source источник
comment
Но у вашего триггера нет подзапроса   -  person Remus Rusanu    schedule 14.01.2010
comment
Зачем вам нужно последнее выражение в предложении where? Это кажется избыточным на основе JOIN?   -  person Sparky    schedule 14.01.2010
comment
Вы правы, это пережиток развития. Я могу удалить это.   -  person Jason Ellsworth-Aults    schedule 14.01.2010


Ответы (5)


Оскорбительная строка выглядит здесь:

IF (SELECT DocumentReceivedFlag FROM Inserted) = 'Y' AND
   (SELECT DocumentReceivedFlag FROM Deleted) = 'N' ...

По сути, это «подзапрос». Оператор IF написан с расчетом на то, что и в inserted, и в deleted будет только одна строка. Не зная подробностей бизнес-логики, я могу только строить догадки, но, скорее всего, все будет в порядке, если вы просто перепишете это так:

IF EXISTS(SELECT 1 FROM inserted WHERE DocumentReceivedFlag = 'Y') AND
   EXISTS(SELECT 1 FROM deleted WHERE DocumentReceivedFlag = 'N'))

Но это может быть сложнее... может быть, вам действительно нужно соединить вставленные и удаленные строки и проверить, действительно ли флаг изменился с «Y» на «N».

person Aaronaught    schedule 14.01.2010
comment
Отлично, спасибо! Я считаю, что этого достаточно, но я проведу дополнительные тесты, чтобы определить. - person Jason Ellsworth-Aults; 15.01.2010

Проверьте, нет ли у вас также триггера на DocumentRequestContents.

person Madalina Dragomir    schedule 14.01.2010
comment
Да, я добавил код этого триггера в вопрос. Как вы думаете, это то, что вызывает эту ошибку? Насколько я вижу, в этом триггере нет подзапросов. - person Jason Ellsworth-Aults; 14.01.2010

При повторном прочтении очевидной проблемой является эта часть второго триггера:

IF (SELECT DocumentReceivedFlag FROM Inserted) = 'Y' 
    AND (SELECT DocumentReceivedFlag FROM Deleted) = 'N'

Это приведет к ошибке, если будет вставлено несколько строк, поскольку SQL Server ожидает, что оба этих подзапроса вернут одну строку или не вернут ни одной строки.

Кстати, я лично избегаю триггеров любой ценой. Они создают слишком много сложности для меня.

person Andomar    schedule 14.01.2010

Вы можете попробовать использовать псевдоним для таблицы Inserted в своем соединении. Хотя я сам не сталкивался с этим, возможно, он пытается выполнить подзапрос в вашем предложении WHERE, а не использовать объединенную таблицу.

person Adam Robinson    schedule 14.01.2010

В вашем триггере нигде нет подзапроса - это единственный триггер в вашей таблице? Эта ошибка не вызвана объединением или обновлением нескольких строк, поэтому я не понимаю, почему ваш триггер может генерировать эту ошибку.

Вы получаете ошибку, когда пытаетесь вставить в саму таблицу? Возможно, на самом деле это запрос, который вы используете для вставки, не работает - пожалуйста, опубликуйте также этот оператор вставки.

person SqlRyan    schedule 14.01.2010