Сервер MS SQL: сбой триггера из-за сбоя хранимой процедуры - PullRequest
0 голосов
/ 05 апреля 2020

У меня есть приложение, которое отправляет до 20 штрих-кодов данных устройства в таблицу SQL -Server «Использование устройства».

В триггере AFTER INSERT этой таблицы у меня есть хранимая процедура, которая передает штрих-коды из столбцов BC1, BC2, BC3 и др. c. в другую таблицу BC usage.

Таблица BC usage установлена ​​на IGNORE_DUP_KEY = ON, так как может случиться, что БК сканируются дважды.

Если сканируется дубликат B C, то также вставка в первую таблицу «использование устройства» не удалась!

ПОЧЕМУ?

Я не понимаю, почему не удается выполнить первоначальную вставку в таблицу «Использование устройства», хотя IGNORE_DUP_KEY = ON в таблице BC usage, которая используется только в хранимой процедуре.

ALTER TRIGGER [dbo].[UpdatePersonanenDaten] 
ON [dbo].[ScanIT_tblGeraeteerfassung] 
AFTER INSERT
AS 
BEGIN
    DECLARE @Personalnummer varchar(10)
    DECLARE @Mitarbeiter varchar(100)
    DECLARE @InOut char(1)=''
    DECLARE @ID int
    DECLARE @StartID int

    SELECT 
        @Personalnummer = a.[Personalnummer], 
        @Mitarbeiter = a.[DeviceUser] 
    FROM 
        [dbo].[ScanIT_tblDevices] a 
    INNER JOIN 
        inserted i ON a.[DeviceID] = i.[DeviceID]

    SELECT @ID = (SELECT ID FROM inserted)

    BEGIN 
        UPDATE [dbo].[ScanIT_tblGeraeteerfassung] 
        SET [Mitarbeiter] = @Mitarbeiter,
            [Personalnummer] = @Personalnummer,
            [InOut] = CASE 
                         WHEN t.[AufAbbau] = 'Aufbau (Start)' 
                            THEN 'S' 
                            ELSE 'E' 
                      END
        FROM ScanIT_tblGeraeteerfassung t
        INNER JOIN inserted i ON t.ID = i.ID
    END

    -- IN THIS SP THE INSERT INTO tbl "BC USAGE"  HAPPENS  and the Dup Key error let the trigger fail!!

    EXEC [dbo].[ScanIT_spGeraeteEinsatz1] @ID

Спасибо за любую помощь

Майкл

Редактировать:

это хранимая процедура ScanIT_spGeraeteEinsatz1 Я пытался просто описать всю работу, прежде чем , но в действительности за B C, отправляемым на серверную таблицу, следует показание счетчика устройства. Таким образом, он должен искать последнее показание счетчика записи для этого B C перед этим. S обозначает начало, E обозначает конец времени использования устройства. Также может случиться, что это устройство раньше не использовалось на стороне клиентов, тогда мы берем счетчик с инвентаризации, чтобы установить начальный счетчик на Start.

И теперь, если кто-то сканирует это устройство B C дважды он должен быть заблокирован, потому что ПЕРВОЕ вхождение этого B C является обязательным началом, а следующее вхождение должно быть окончанием. Даже если кто-то по ошибке добавит B C в стартовую запись, хотя устройство уже было отсканировано ранее в другой записи.

Я надеюсь, что смогу объяснить работу, что вы понимаете весь процесс.

ALTER PROCEDURE [dbo].[ScanIT_spGeraeteEinsatz1]
    @GeraeteerfassungID int

AS
BEGIN

BEGIN TRANSACTION;   --neu
SAVE TRANSACTION MySavePoint;

    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    declare @tablename as varchar(255)   -- tbl, aus der ausglesen wird: 'ScanIT_tblGeraeteerfassung'
    declare @temptable table (BarcodeName varchar(100))    -- im @temptable werden die Feldnamen Geraetebarcode1, Zaehlerstand1, ... eingelesen
    declare @sqlDynamicString nvarchar(400)
    declare @sqlDynamicString2 nvarchar(400)
    declare @NumberBC int, @Counter int
    declare @temptable2 table (Barcode varchar(10), Zaehlerstand varchar(20))
    declare @strCounter varchar(2)
    declare @Geraetebarcode int

    declare @ID int
    declare @L_ID int

    declare @L_InOut char(1)


    declare @StartID int
    declare @GeraeteerfassungIDOUT int
    --declare @Einsatzvon datetime

    Declare @StartZaehlerstand varchar(25)
    Declare @StartDatum datetime

    declare @BC_AnzahlVerwendungen int=0

    DECLARE @LastDS TABLE
            ([ID] [int],
            [GeraeteerfassungIDOUT] [int] ,
            [GeraeteerfassungIDIN] [int] ,
            [Projektnummer] [varchar](20) ,
            [Geraetebarcode] [varchar](100) ,
            [ZaehlerstandOUT] [varchar](50) ,
            [ZaehlerstandIN] [varchar](50) ,
            [Einsatzvon] [datetime] ,
            [Einsatzbis] [datetime] ,
            [InOut] [char](1) ,
            [PaarID] [int] )


    set @tablename = 'ScanIT_tblGeraeteerfassung'
    set @Counter=1

    -- Einsatzvon-Datum/Zeit un d Projektnummer bleiben für alle Datensätze gleich
    --set @Projektnummer=(Select Arbeitsauftrag from [dbo].[ScanIT_tblGeraeteerfassung] where [ID]=@GeraeteerfassungsID);
    --set @Einsatzvon=(Select Sendtime from [dbo].[ScanIT_tblGeraeteerfassung] where [ID]=@GeraeteerfassungsID);
    --set @L_InOut=(select [InOut] from [dbo].[ScanIT_tblGeraeteerfassung] where [ID]=@GeraeteerfassungsID))   

    --select @Projektnummer=Arbeitsauftrag, @Einsatzvon=Sendtime, @L_InOut=InOut from [dbo].[ScanIT_tblGeraeteerfassung] where [ID]=@GeraeteerfassungsID


    --Insert into @temptable (BarcodeName)
    --wie viele Gerätefelder gibt es in dem SCAN-IT Formular bzw. in der Einlesetabelle?
    set @NumberBC=(SELECT count(c.name) FROM sys.columns c WHERE c.object_id = OBJECT_ID(@tablename) and  c.name like 'Geraetebarcode%' ) ;

    -- hier beginnt der Loop NEU    
        while(@Counter <= @NumberBC)
        begin
                    set @strCounter=convert(varchar(2),@Counter)

                    set @sqlDynamicString2 = 'SET @GeraeteBC=(SELECT Geraetebarcode' + @strCounter + ' FROM [dbo].[ScanIT_tblGeraeteerfassung] WHERE ID=' + convert(nvarchar(8),@GeraeteerfassungsID) + ' AND Len(Geraetebarcode' + @strCounter + ')>0)' 

                    EXECUTE sp_executesql @sqlDynamicString2, N'@GeraeteBC nvarchar(8) OUTPUT',@GeraeteBC=@Geraetebarcode OUTPUT

                    --zuerst muß einmal geschaut werden, ob dieses Gerät überhaupt schon mal im Einsatz war, daher wird die [dbo].[ScanIT_tblGeraeteeinsatz] ausgezählt
                    --bei ERSTMALIGER Verwendung ist die Anzahl 0
                    Set @BC_AnzahlVerwendungen=(Select Count(*) from [dbo].[ScanIT_tblGeraeteeinsatz] where [Geraetebarcode]=@Geraetebarcode)

                    print @Geraetebarcode
                    print @BC_AnzahlVerwendungen

                    -- es wird vorerst der Zählerstand, die Sendtime und die ID für in/out eingegeben, weil erst danach bewertet wird, ob das ein IN oder OUT Datensatz ist
                    set @sqlDynamicString=concat('SELECT  ID, ID ,', @Projektnummer, ', Geraetebarcode' + @strCounter, ', Zaehlerstand' + @strCounter, ', Zaehlerstand' + @strCounter, ', Sendtime',', Sendtime',', InOut',
                                                 ' FROM [dbo].[ScanIT_tblGeraeteerfassung] WHERE ID= ', @GeraeteerfassungsID, ' AND Len(Geraetebarcode' + @strCounter, ')>0' )

                    print @sqlDynamicString

                    BEGIN TRY    --neu
                    Insert into [dbo].[ScanIT_tblGeraeteeinsatz] ([GeraeteerfassungIDOUT],
                                                                  [GeraeteerfassungIDIN],
                                                                  [Projektnummer],
                                                                  [Geraetebarcode],
                                                                  [ZaehlerstandOUT],
                                                                  [ZaehlerstandIN],  
                                                                  [Einsatzvon],
                                                                  [Einsatzbis],
                                                                  [InOut])
                    exec (@sqlDynamicString)

                    COMMIT TRANSACTION 
                    END TRY
                        BEGIN CATCH
                            IF @@TRANCOUNT > 0
                            BEGIN
                                ROLLBACK TRANSACTION MySavePoint; -- rollback to MySavePoint
                            END
                        END CATCH


                    --die ID des eben eingegebenen Datensatzes
                    set @ID=SCOPE_IDENTITY() 


                    -- nun wird geschaut, ob der Barcode schon einmal vokam
                    -- Gerät war noch NIE im Einsatz, d.h. den Startzählerwert aus der [dbo].[Lager_tblInventarArtikel] suchen
                        if @BC_AnzahlVerwendungen = 0   

                            begin           

                                Update a  set   --a.InOut= 'S', 
                                                a.[ZaehlerstandOUT]=coalesce(a.[ZaehlerstandOUT],b.[Anfangsstand]),  --zuerste wird geschaut, ob ein Zählerstand angliefert wird, wenn nicht wird im Lagerstand geschaut
                                                a.[ZaehlerstandIN]='', 
                                                a.[Einsatzbis]=NULL, 
                                                a.[GeraeteerfassungIDIN]= NULL,
                                                a.[PaarID]=a.ID
                                    from [dbo].[ScanIT_tblGeraeteeinsatz] a 
                                    left join [dbo].[Lager_tblInventarArtikel] b on a.[Geraetebarcode]=b.[Barcode] 
                                    where a.ID=@ID

                            end         

                    --Gerät war schon im Einsatz, daher suchen aus [dbo].[ScanIT_tblGeraeteeinsatz]

                    if @BC_AnzahlVerwendungen > 0

                    begin           
                        --print @inout
                        --ID des Startdatensatzes suchen

                        print @Projektnummer
                        print @Einsatzvon 
                        print @L_InOut
                        --set @L_InOut=(select [InOut] from @LastDS)


                        --print @L_InOut


                        if @L_InOut='S'
                        --zuerst den zuvor gelieferten Datensatz suchen, um den Anfangstand zu setzen
                        begin
                                --Befüllen der Table Variablen mit dem vorletzten Datensatz 
                                INSERT INTO @LastDS 
                                            ([ID] ,
                                            [GeraeteerfassungIDOUT]  ,
                                            [GeraeteerfassungIDIN]  ,
                                            [Projektnummer]  ,
                                            [Geraetebarcode]  ,
                                            [ZaehlerstandOUT]  ,
                                            [ZaehlerstandIN]  ,
                                            [Einsatzvon]  ,
                                            [Einsatzbis]  ,
                                            [InOut]  ,
                                            [PaarID] ) 


                                SELECT Top 1 [ID] ,
                                            [GeraeteerfassungIDOUT]  ,
                                            [GeraeteerfassungIDIN]  ,
                                            [Projektnummer]  ,
                                            [Geraetebarcode]  ,
                                            [ZaehlerstandOUT]  ,
                                            [ZaehlerstandIN]  ,
                                            [Einsatzvon]  ,
                                            [Einsatzbis]  ,
                                            [InOut]  ,
                                            [PaarID]
                                from [dbo].[ScanIT_tblGeraeteeinsatz] 
                                where [Geraetebarcode]=@Geraetebarcode and ID < @ID order by ID desc 
                        end         

                        begin
                            Update a set  
                                     a.[GeraeteerfassungIDIN]=NULL  --weil GeraeteerfassungIDOUT existiert, braucht man sie nicht setzen
                                     ,a.[ZaehlerstandOut]= coalesce(a.[ZaehlerstandOut],x.[ZaehlerstandOUT])  --wenn ZaehlerstandOut abgelesen wurde, braucht man ihn nicht setzen, wenn nicht, dann aus letztem DS verwenden
                                     ,a.[ZaehlerstandIN]=''
                                    -- ,a.[Einsatzvon]=x.[Einsatzvon]               --weil Einsatzvon existiert, braucht man sie nicht setzen
                                     ,a.[Einsatzbis]= null
                                     ,a.[PaarID]=a.ID  
                            from [dbo].[ScanIT_tblGeraeteeinsatz] a 
                              left join  @LastDS x on a.[Geraetebarcode]=x.[Geraetebarcode]
                              where a.[ID]=@ID 
                        end

                        if @L_InOut='E'
                        --hier muß man nach dem letzten DS suchen, weil es einen S geben muß!!
                        --da diese Nummer schon verwendet wurde, kann man nach dem S Datensatz zu diesem Gerät suchen
                        begin
                                --Befüllen der Table Variablen mit dem vorletzten Datensatz 
                                INSERT INTO @LastDS 
                                            ([ID] ,
                                            [GeraeteerfassungIDOUT]  ,
                                            [GeraeteerfassungIDIN]  ,
                                            [Projektnummer]  ,
                                            [Geraetebarcode]  ,
                                            [ZaehlerstandOUT]  ,
                                            [ZaehlerstandIN]  ,
                                            [Einsatzvon]  ,
                                            [Einsatzbis]  ,
                                            [InOut]  ,
                                            [PaarID] ) 


                                SELECT Top 1 [ID] ,
                                            [GeraeteerfassungIDOUT]  ,
                                            [GeraeteerfassungIDIN]  ,
                                            [Projektnummer]  ,
                                            [Geraetebarcode]  ,
                                            [ZaehlerstandOUT]  ,
                                            [ZaehlerstandIN]  ,
                                            [Einsatzvon]  ,
                                            [Einsatzbis]  ,
                                            [InOut]  ,
                                            [PaarID]
                                from [dbo].[ScanIT_tblGeraeteeinsatz] 
                                where [Geraetebarcode]=@Geraetebarcode and ID < @ID order by ID desc 
                        end         


                        select * from @LastDS

                        begin
                        Update a set 
                                     a.[GeraeteerfassungIDIN]=x.[GeraeteerfassungIDOUT] 
                                     ,a.[ZaehlerstandOut]= x.[ZaehlerstandOut] 
                                     --,a.[ZaehlerstandIN]= NULL   --steht schon drinnen
                                     ,a.[Einsatzvon]=x.[Einsatzvon] 
                                     --,a.[Einsatzbis]= NULL        --steht schon drinnen
                                     ,a.[PaarID]=x.ID  
                                     ,a.[Verbrauch]= coalesce(cast(a.[ZaehlerstandIN] as float),0)-coalesce(cast(x.[ZaehlerstandOUT] as float),0) 
                                     ,a.[Standzeit]= datediff(d, coalesce(x.[Einsatzvon],0), @Einsatzvon)+1 

                              from [dbo].[ScanIT_tblGeraeteeinsatz] a 
                              left join  @LastDS x on a.[Geraetebarcode]=x.[Geraetebarcode]
                              where a.[ID]=@ID 
                        end

                    end

                    -- zuletzt @Counter hochzählen, dann loop
                    set @Geraetebarcode=null
                    set @BC_AnzahlVerwendungen=null 

                    delete from @LastDS --tablevariable leeren

                    set @Counter=@Counter+1

            end     

    --  Select @StartZaehlerstand, @StartDatum, @StartID,@InOut, @ID, @Counter

END

1 Ответ

1 голос
/ 05 апреля 2020

TL; DR : очевидный вопрос: почему вы даже пытаетесь вставить повторяющиеся значения ключа? Вы должны решить root проблемы вместо того, чтобы искать обходные пути, которые ничего не решают и создают больше проблем.

Если нетрудно проверить, существует ли уже запись в таблице и пропустить вставку. Иными словами, вы должны стремиться к качеству данных в источнике, а не очищать и сортировать дубликаты после.

Краткий ответ: не легко решить вашу проблему, потому что у вас есть широкий диапазон проблем, и у нас нет копии ваших данных и среды, чтобы предоставить подробные рекомендации. Длинный ответ: см. Ниже несколько предложений.


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

Почему бы не создать представление (или хранимая процедура), которая указывает на исходную таблицу ? Я даже не думаю, что вы должны пытаться исправить этот код, вместо этого упростите свой код. Пересмотрите свою модель данных и поток операций.

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

Когда вы делаете вещи более сложными, чем они должны быть, код становится трудным для чтения, менее надежным и трудным для отладки. , Существует так много шуток для выполнения основных операций c, которые должны быть относительно простыми.

У нас нет копии ваших данных, поэтому трудно воспроизвести вашу проблему. Если вы опубликовали свою структуру таблицы с индексами и ограничениями и некоторыми примерами данных, возможно, будет возможно go продолжить.

Другая проблема заключается в том, что вы этого не делаете иметь комплексную обработку ошибок в вашем коде. Это то, что вы должны начать делать с этого момента, чтобы сделать ваш код более надежным и надежным. Затем, когда возникает ошибка, вы можете перехватить содержимое определенных переменных и получить больше информации о проблемных данных. Посмотрите здесь, например: Как реализовать обработку ошибок в SQL Сервер . Разница всего в 10 строках кода.

У вас есть транзакции, но этого недостаточно. Они не используются должным образом и не охватывают все процедуры.

Использование триггера часто является плохим решением, особенно если у вас уже есть хранимая процедура для вставки / обновления данных. Если возможно, вы должны держать логи c сосредоточенными в одном месте. Слишком часто триггеры неправильно понимаются и неправильно кодируются в соответствии с неверными предположениями .

Если вы все еще читаете это, вот некоторая потенциально важная информация, которая может повлиять на вас. Я думаю, что это то, на что намекает @smor, но я подчеркну это.

Один малоизвестный аспект триггеров вставки (по крайней мере, в MS SQL) заключается в том, что при вставке нескольких строк в один пакет, триггер вызывается один раз для всей операции, а не один раз для каждой строки (выделено мое):

, поскольку триггер INSERT может быть запущен с помощью инструкции SELECT INSERT INTO (table_name), вставка множества строк может вызвать один вызов триггера.

Источник: Создать триггеры DML для обработки нескольких строк данных

Так что это может быть одной из ваших проблем (эта проблема часто остается незамеченной в течение очень долгого времени ...).


К сожалению, ваш код трудно читать, но есть вещи, которые вы можете сделать, которые будут стоить мало и приносит некоторую пользу:

  • используйте значимых имен для имен переменных и объектов, чтобы избежать путаницы и улучшить читаемость.
  • избавиться от динамичности c SQL - это само по себе сделает код немного легче для понимания и понимания
  • улучшит форму вашего кода - табуляция важна
  • добавьте обработку ошибок как можно скорее - может произойти много ошибок, которые вы не заметите и не нарушите целостность ваших данных
  • удалите неиспользуемые вещи из вашего кода

Например, в хранимой процедуре ScanIT_spGeraeteEinsatz1 вы определяете таблицу в памяти:

declare @temptable table (BarcodeName varchar(100))

, но, очевидно, вы фактически не используете ее. @temptable2 больше не используется или больше не используется. Это только вносит путаницу.

Короче говоря, сейчас самое время сократить код. Подумайте о своих потребностях и упростите. Не пытайтесь решать проблемы, когда вы можете их избежать. Совершенствуйте свой код и документируйте себя, даже если это требует времени Плохой код в долгосрочной перспективе стоит дорого, потому что его сложно поддерживать, и он создает больше работы, когда приходится устранять ошибки и недостатки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...