我有一个应用程序,可将多达20个设备条形码数据发送到SQL-Server表“设备使用情况”。
在该表的AFTER INSERT
触发器中,我有一个存储过程,该过程将条形码从BC1,BC2,BC3等列传输到另一个表BC usage
中。
表BC usage
设置为IGNORE_DUP_KEY = ON
,因为可能会对BC进行两次扫描。
如果扫描了重复的BC,则第一个表“设备使用情况”中的插入也会失败!
为什么?
尽管表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 在此之前,我曾试图简单地描述整个工作,但实际上,发送到服务器表的BC后面是设备的计数器读数。 因此,它需要查找此BC之前该记录的最后一个计数器读数。 S代表开始时间,E代表设备使用时间的结束时间。 可能还会出现一个问题,那就是该设备之前从未在客户端使用过,然后我们从盘点中取出计数器,将初始计数器设置为Start。
现在,如果有人扫描此设备BC两次,则必须将其阻止,因为该BC的第一次出现是强制性的,因此必须以“开始”作为起点,而下一次出现的则是“结束”。即使有人错误地将BC添加到了“开始”记录中,尽管该设备之前已经在另一个记录中进行了扫描。
我希望我能解释一下您了解整个过程的工作。
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
答案 0 :(得分:1)
TL; DR :一个显而易见的问题是:为什么还要插入重复的键值?您应该解决问题的根源,而不是寻找无法解决任何问题并造成更多问题的解决方法。
如果不难检查表中是否已存在记录并跳过插入。换句话说,您应该从源头上确保数据的质量,而不是在清理之后对重复项进行排序。
简短的回答:没有容易解决的问题,因为您遇到的问题范围很广,我们也没有提供数据和环境的副本来提供深入的建议。 长答案:请参见下面的一些建议。
在没有对您的应用程序有全面了解的情况下,我仍然觉得该方法过时且不必要。将记录转移到另一个表是重复,是浪费的数据存储,如果触发器的编码不正确,则不可靠。
为什么不建立指向原始表的视图(或存储过程)?我什至不认为您应该尝试修复此代码,而是简化代码。重新考虑您的数据模型和操作流程。
您的过程已经足够复杂,就我所知,您甚至拥有动态SQL 。它很少是合理的,因为它增加了一层复杂性,并且可能带来安全风险。
当您使事情变得比原本应该复杂的多时,代码将变得难以阅读,不那么可靠且难以调试。有太多的hoopla可以执行相对简单的基本操作。
我们没有您的数据副本,因此很难再现您的问题。如果您发布具有索引和约束的表结构,并且一些示例数据可能会更进一步。
另一个问题是您的代码中没有全面的错误处理。从现在开始,您应该开始这样做,以使代码更可靠,更健壮。 然后,当发生错误时,您可以捕获某些变量的内容并获得有关违规数据的更多信息。 例如,在这里看看:How to implement error handling in SQL Server。 仅10行代码就会有所作为。
您有一些交易,但这还不够。它们使用不当,没有涵盖整个过程。
使用触发器通常是一个不好的解决方案,尤其是当您已经具有存储过程来插入/更新数据时。如果可能,您应该将逻辑集中在一个地方。在错误的假设下,触发器经常被误解和错误编码。
如果您仍在阅读本文,现在这里有一些可能会影响您的潜在重要信息。我认为这是@smor所暗示的,但我会强调这一点。
插入触发器的一个鲜为人知的方面(至少在MSSQL中是),当您在一批中插入多行时,触发器将为整个操作调用一次,而不是为每行调用一次(重点是我的):< / p>
因为INSERT INTO(table_name)可以触发INSERT触发器 SELECT语句,插入许多行可能导致单个 触发调用。
来源:Create DML Triggers to Handle Multiple Rows of Data
所以这也可能是您的问题之一(这种问题通常在很长一段时间内都未被发现...)。
不幸的是,您的代码很难看懂,但是您可以做的事情花费很少,并带来一些好处:
例如,在存储过程ScanIT_spGeraeteEinsatz1
中,您定义了一个内存表:
declare @temptable table (BarcodeName varchar(100))
,但显然您并未真正使用它。 @temptable2
不再使用或不再使用。只会带来混乱。