MSSQL Trigger (NewBy)

PeterS

Aktiver Benutzer
Beiträge
27
Hallo miteinander,

als Neuling ich versuche seit Tagen, die Trigger-Welt von MSSQL zu begreifen, habe aber anscheinend noch immer einen Knoten im Hirn...
  1. Verstehe ich es richtig: es darf für jede Tabelle nur jeweils einen After-Trigger geben für INS, UPD, DEL bzw. für Kombinationen daraus?
  2. Ich möchte einen Trigger schreiben, der bei INS und UPD feuert. Geht das überhaupt mit einem After-Trigger? (UPD funzt, aber INS tut nix)
Bin für jede Erhellung sehr verbunden.
PeterS
 
Werbung:
1. Nein mehrere Trigger sind möglich. Du kannst aber die Reihenfolge fast gar nicht bestimmen. Mit EXEC sp_settriggerorder kann man hier etwas machen aber elegant läßt sich das nicht lösen. Außerdem können sich die Trigger natürlich gegenseitig beeinflussen, Rekursionen musst du verhindern.

2. Das geht, mit
Code:
CREATE TRIGGER   [dbo].[triggername]
   ON       [dbo].[tabelle]
   FOR INSERT, UPDATE, DELETE
AS
BEGIN
Wichtig ist, das der Code auch greift. Vermutlich liegt hier irgendwo der Fehler.
 
Aber nun zum eigentlichen Problem:

Wir nutzen für Adressenverwaltung und Geschäftsbetrieb ein großes Datenbanksystem (Cobra), welches als Frontend einer ziemlich komplexen MSSQL-Datenbank arbeitet. Aus Kompatibilitätsgründen zu weiteren Datenbanken, die über etwas sperrige Schnittstellen andocken, muss ich in MSSQL per Trigger einige Zusatzfelder "vormontieren", umdimensionieren bzw. umformatieren, will dabei aber tunlichst nicht in die Cobra-eigenen Trigger eingreifen.

Für die Tabelle [dbo].[ADRESSES] gibt es bereits drei Trigger, die für DELETE-Prozesse feuern (2x) und für ein Logging zuständig sind (1x). Ich denke nicht, dass die an meinem Problem Schuld sind.

Ich versuche seit Längerem, für 2 weitere Routinen einen weiteren Trigger zu schreiben:

=======================================================
USE [IFW]
GO
/****** Object: Trigger [dbo].[IFW_ADDRESSES_INS_UPD] Script Date: 12.09.2017 18:52:27 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER TRIGGER [dbo].[IFW_ADDRESSES_INS_UPD]
ON [dbo].[ADDRESSES]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Kundennummer zur Übergabe nach Lexware nachziehen
BEGIN
-- Daten werden aus dem Counter in ein Textfeld [TEXT45]
-- dubliziert (ERP-Schnittstelle akzeptiert nur Textfelder)
UPDATE [dbo].[ADDRESSES]
SET [dbo].[ADDRESSES].[TEXT45] = i.COUNTER1
FROM Inserted i
INNER JOIN [dbo].[ADDRESSES] a
ON a.id = i.id
END

-- Bei Änderungen von Ortsteil und/oder Straße soll Straße/Postfach ERP nachgezogen werden
-- betroffene Felder:
-- Ortsteil = [CITY2]
-- Strasse = [STREET0]
-- Straße/Postfach ERP = [TEXT36]

IF UPDATE([CITY2]) OR UPDATE([STREET0]) OR (SELECT COUNT(*) FROM inserted) = 0
-- (SELECT COUNT(*) FROM inserted) = 0: bedeutet, dass es ein Löschvorgang ist!
BEGIN

DECLARE @ortsteil nvarchar(40)
DECLARE @strasse nvarchar(60)
DECLARE @text36 nvarchar(35)

-- Auslesen der eingetragenen Werte: INSERTED
SET @ortsteil = (SELECT [CITY2] from inserted)
SET @strasse = (SELECT [STREET0] from inserted)
SET @text36 = LTRIM(RTRIM (@ortsteil + IIF(@ortsteil = '',' ',IIF(@strasse = '',' ',', ')) + @strasse))

-- Neue Werte werden in [TEXT36] geschrieben
UPDATE a
SET a.[TEXT36] = @text36
FROM inserted i
INNER JOIN [dbo].[ADDRESSES] a ON i.[ID] = a.[ID]

END
END
=======================================================

Klemmungen gibt's beim Befüllen von [TEXT36]. Wenn ich einen neuen Datensatz erfasse, bleibt [TEXT36] leer. Ist der neue Datensatz erstmal gespeichert und ich ändere dann was in [CITY2] oder [STREET0], kommen die Änderungen sauber in [TEXT36] an.

Was habe ich falsch gemacht?

Mit Dank und Grüßen,
PeterS

PS: und wie kriege ich hier den Programmcode besser rein?
 
Also zunächst mal zu deinem Trigger-Code ein paar Anmerkungen:

a) Dein Trigger feuert nicht bei DELETE weil das Schlüsselwort fehlt. Selbst wenn es da wäre machst du einen INNER JOIN auf INSERTED, wo bei DELETE-Operationen keine Datensätze wären. Das ist ja vielleicht gewollt und dann auch nicht verkehrt aber später fragst du mit count() = 0 ab ob es sich um einen DELETE-Vorgang handelt, das wäre dann überflüssig.

b) Dein UPDATE-Statement könnte problematisch sein, da bin ich jetzt aber nicht sicher (vermutlich nicht). Wenn das läuft dann gut, wenn nicht, dann dreh mal die Tabellen um:
Code:
FROM [dbo].[ADDRESSES] a
INNER JOIN Inserted i
Auch das hinter UPDATE-Statement würde ich entsprechend anpassen, ich bin nicht sicher ob hier
[UPDATE]Alias
SET ...
FROM Tabelle Alias[/CODE]
funktioniert. Ist wieder mein Bauch der meckert aber ich verwende Aliase nur für die Tabellen die ich joine, die Tabelle im FROM die ich update behält ihren Namen.

c) Ganz fieser Fehler:
Du füllst Variablen, noch dazu schreibst du den Inhalt dieser Variablen dann wieder in die Tabelle ohne den Primärschlüssel abzugleichen. Das geht immer genau solange gut, wie in INSERTED genau ein Datensatz steht. Wenn du ein UPDATE machst das mehrere Zeilen betrifft feuert der Trigger genau einmal und nimmt die Daten eines beliebigen Datensatzes in die Variablen und schreibt sie in alle! anderen Datensätze. Auch wenn dein CRM i.d.R. nur einen Datensatz gleichzeitig aktualisiert kann das ganz schnell knallen wenn keiner mehr an den Trigger denkt. Du solltest auf jedenfall direkt das UPDATE machen, ohne die Variablen.

d) Den UPDATE-Code verstehe ich auch nicht so recht. Du hängst an Ortsteil ein Leerzeichen an wenn Ortsteil 0 Zeichen lang ist. Das wird dann durch ltrim() abgeschnitten... Was ist denn wenn Ortsteil oder Straße NULL ist?

e) Dein eigentliches Problem resultiert eventuell aus NULL-Werten beim anlegen des Datensatzes. Kann es sein das die Spalten erst durch einen anderen Trigger befüllt werden? Dann hast du schlechte Karten wegen der Anzahl der Trigger und der Reihenfolge.

Mal ein anderer Ansatz:
Ließe sich das ganze nicht auch per View realisieren? Deine Schnittstelle würde dann auf die View und nicht die ADRESSES Tabelle zugreifen.

Zu Conbra:
Wir standen vor gefühlt 100 Jahren vor der Wahl. Ich habe mich dann für Combit statt Cobra als CRM entschieden weil das Datenmodell von Cobra (zumindest damals) total schlecht war. Im Prinzip nichts normalisiert sondern das meiste in einer Tabelle. Daher sind die Trigger vermutlich von Cobra selbst oder wurden die für euer Projekt entwickelt?

Im wesentlichen ist Combit recht ähnlich, da arbeite ich natürlich auch mit Triggern. Habe aber das ganze Datenmodell sauber aufgebaut, Schnittstellen (z.B. zu Outlook) bilde ich auch per View ab.
 
Erstmal ein groooooßes Dankeschön. Ich setze mich nachher gleich dran und versuche die Abänderung. Sicherlich gibt's dabei noch die eine oder andere Frage...

°° [Cobra]
Ich habe das Dings als Altlast "geerbt". Selbst für den Outlook-Einsatz brauchten wir eine Ausnahmegenehmigung für's Intranet (die arbeiten standardmäßig mit Thunderbird). Eigentlich ist Cobra für unsere Zwecke völlig überdimensioniert, aber der Chef liebt es inniglich (wenn er selbst damit auch fast garnicht umgeht), und so kann ich nur versuchen, das Beste daraus zu machen.

Der View-Ansatz wird wohl nichts, denn die Schnittstellen docken allesamt ausschließlich an der Tabelle [dbo].[ADRESSES] an und akzeptieren noch nicht mal jedes Feld [kann z.B. nichtmal den Inhalt des Counters (erzeugt unikate Kundernummers) nach LX übertragen - die Zahl muß in einem Textfeld stehen]. Auch das Cobra-eigene DMS akzeptiert nur Mailadressen aus [dbo].[ADRESSES], wodurch alle Träume von etwas relationaler Ordnung schon wieder ausgeträumt sind. Dazu ist das ganze Dings - wahrscheinlich sogar vorsätzlich - wirklich grottenschlecht dokumentiert, was natürlich den professionellen Supportern die (Brief-) Taschen füllt.

°° [zu e)]
Es ist mir in der Tat ziemlich nebulös, was bei einem INSERT (neuer Datensatz) tatsächlich abläuft. Kann ich das irgendwie transparenter machen? Mich wundert bsp., dass ein neuer Datensatz gespeichert wird, wenn man den Cursor in einen anderen Datensatz stellt. Einen "SPEICHERN"-Button gibt es nicht. Ist das übliches Handling?
 
°° [zu d)]
Du hast völlig recht: Mein Problem entsteht im UPDATE-Code bei NULL-Werten.
Code:
IIF((SELECT [CITY2] from inserted) = '','',IIF((SELECT [STREET0] from inserted) = '','',', '))
ist Quatsch.

Aber wie macht mal sowas ordentlich?
WENN [CITY2] in inserted nicht NULL oder '' ist, soll gebildet werden
Code:
[TEXT36] = [CITY2]{Komma}{1 Leerzeichen}[STREET0]
ansonsten
Code:
[TEXT36] = [STREET0]
Falls [CITY2] und [STREET0] NULL ist oder '' wird, bleibt/wird [TEXT36] leer

UND: Gibt es eigentlich einen Weg, um jeden Datensatz beim Speichen "auszuputzen", d.h. Leerzeichen am Anfang oder am Ende eines jeden Feldes zu löschen und doppelte Leerzeichen zu entfernen? Cobra speichert solchen Quatsch stets erstmal mit ab.
 
Zuletzt bearbeitet:
Also um sowohl '' als auch NULL abzufangen ginge das:
Code:
[TEXT36] = isnull([CITY2],'') + (CASE WHEN [CITY2] != '' AND [CITY2 IS NOT NULL AND [STREET0[ != '' AND [STREET0] IS NOT NULL THEN ', ' ELSE '' END) + isnull([STREET0],'')
Ist natürlich frickelig aber grade kommt mir nichts eleganteres in den Sinn.

Das mit Datenmüll in den Textfeldern ist echt nervig, das habe ich aber genau so. Habe dafür tatsächlich eine Funktion geschrieben und rufe diese bei jedem Insert per Trigger auf für Textfelder. Das würde ich aber nicht machen solange ich nicht genau weiß welche Trigger was tun und in welcher Reihenfolge, das wirst du dir schon genau ansehen müssen. Das kannst du machen in dem du den Code genau betrachtest oder ein Testsystem aufsetzt. Du kannst auch mit Tools wie SQL Server Profiler mitlesen was beim Insert passiert, ist aber alles andere als übersichtlich wie ich finde.

Wie genau sehen denn deine Schnittstellen aus, geht es um Outlook Synchronisation oder andere SQL Datenbanken? Für Outlook nehme ich Genius Connect, das war sehr angenehm und unabhängig von so beknackten Tools vom CRM Hersteller oder Systemhaus.
 
Ich hab nun erstmal Deinen Code etwas nachgeputzt und eingebaut:

Code:
    -- ----------------------------------------------------------------------------
        -- [2. JOB] Kombifeld bilden aus Ortsteil und Straße (zur Übergabe nach LX)
    -- ----------------------------------------------------------------------------
    -- Bei Änderungen von Ortsteil und/oder Straße soll Straße/Postfach ERP nachgezogen werden
    -- betroffene Felder:
    --   Ortsteil                    = [CITY2]
    --   Strasse                     = [STREET0]
    --   Straße/Postfach ERP         = [TEXT36]

    IF UPDATE([CITY2]) OR UPDATE([STREET0]) OR (SELECT COUNT(*) FROM inserted) = 0
    -- (SELECT COUNT(*) FROM inserted) = 0: bedeutet, dass es ein Löschvorgang ist!
    BEGIN
        UPDATE [dbo].[ADDRESSES]
        SET [dbo].[ADDRESSES].[TEXT36] = isnull(i.[CITY2],'') + (CASE WHEN i.[CITY2] != '' AND i.[CITY2] IS NOT NULL AND i.[STREET0] != '' AND i.[STREET0] IS NOT NULL THEN ', ' ELSE '' END) + isnull(i.[STREET0],'')
        FROM [dbo].[ADDRESSES]
        INNER JOIN inserted i
        ON [dbo].[ADDRESSES].[ID] = i.[ID]
    END
Ist das richtig, wenn ich alle Felder aus inserted ziehe?

UND: was genau macht Dein 'CASE WHEN'-Teil?

Könntest Du bitte auch mal auf die Alias-Struktur schauen: Meintest Du das so? Oder kommt in die UPDATE-Zeile auch ein Alias zur späteren Verwendung?

°° [Schnittstellen...]
gibt's im Wesentlichen 3:
- DMS (da kann ich wenigstens die Ablageverzeichnisstrukturen und Dateinamenbildung konfigurieren). DMS geht auch mit ander externer SW (z.B. Leitzordner) - hab ich aber noch nicht ausprobiert.
- Outlook (keinerlei Doku, alles nach dem Prinzip "Friß oder stirb", die Verwendung anderer externer Mailprogramme wird nicht unterstützt; nur Serienmail kann ich an Outlook vorbei direkt zum Mailserver schicken lassen; zeitversetzte Serienmails funktionierten bei uns aber noch nie, obwohl der Cobra-Server ständig online ist)
- Lexware [oder wohl auch andere Buchhaltungs-SW, die ich nicht] (fast keine Doku; konfigurierbar ist nur das ID-Feld [wir nehmen dafür z.Zt. die DebitorenNr. - ich mußte Cobra aber 'wasserdicht' machen, damit mir niemand die DebitorenNr. verfummeln kann]; Synchronisation von Kreditoren ist nicht vorgesehen; dynamisches Update über die ERP-Schnittstelle gibt's fast nur beim ID-Feld. Alle anderen Felder lassen sich fast fester LX-Matrix zuordnen, soweit LX der Cobra-Feldtyp paßt. Akzeptiert werden wohl nur Cobra-Textfelder. Dafür gibt's in Cobra kaum Buchhaltungsstandards - z.B. die IBAN-Prüfung etc.)

Mein Hauptproblem bei Cobra <--> LX ist aber, dass LX für Stammdaten nur winzige Felder bereithält (z.B. Hausnummer in varchar (5), Firmenadressen mit HausNr. '512-515' passen da schon mal nicht rein). Dass es in DE inzwischen zunehmend mehr Adressen mit Ortsteil-Angaben gibt, haben die LX-Leute genausowenig nicht bemerkt wie, dass der eine oder andere Kunde auch mal einen akademischen Grad haben kann. Das alles über die 5 Freifelder zu regeln, die dann in LX wie Exoten behandelt werden, ist fast unmöglich, zumal ich in LX keinerlei 'Verfummeleien' vorbeugen kann.

Neuerdings kann man aber unter dem Cobra-Frontend das System mit 'freien Tabellen' etwas ausweiten und unter Einbeziehung der Cobra-Adressenverwaltung erste eigene Relationen bauen. Dabei kann ich eigene 'Tabellen' und 'Brückentabellen' neu anlegen. Was 'Brückentabellen' sind und wozu man die braucht/einsetzt, habe ich aber bis heute nicht ansatzweise begriffen (ob das in Richtung n:n-Verknüpfungen geht?).

Aber wie gesagt, ich muß mit dieser Grundausstattung leben und kann nur das Beste daraus machen und einen Weg finden, der noch Praxistauglich ist.
 
Also der Code sieht gut aus und müsste aus meiner Sicht so funktionieren. Den Teil
Code:
OR (SELECT COUNT(*) FROM inserted) = 0
kannst du aber, wie schon angemerkt, ersatzlos streichen. Bei einem Löschvorgang gibt es danach nichts mehr, was durch dein Statement aktualisiert würde.

Das mit den Aliasen hast du jetzt so gemacht wie ich es nutze, das wird funktionieren. Ob es anders wirklich ein Problem darstellt glaube ich gar nicht mal, musst du mal probieren.

Mein "Code" macht nichts besonders es verkettet nur CITY2 und STREET0. Ist einer der Werte NULL verkettet er einen 0-Zeichen-String, so das das Ergebnis nicht automatisch NULL wird. Außerdem wird durch das CASE, wenn beide Werte weder NULL noch 0-Zeichen lang sind, ein Komma + Leerzeichen dazwischen gesetzt.

Eigentlich sind Trigger für das prüfen und zusammenpuzzeln solcher Felder schon ganz sinnvoll. Das Problem in deinem Fall sind die bereits vorhandenen Trigger, die machen dir das Leben schwer. Wo du kannst solltest du auf einen Zwischenschritt wie eine View oder eine eigene Tabelle für die Bereitstellung der Daten setzen.
 
Werbung:
Jop! Sieht so aus, als wenn's jetzt läuft (jedenfalls ist mir noch nichts Gegenteiliges aufgefallen oder bekannt geworden).

Ich will als nächstes mal versuchen, per Trigger einige der Adressenfelder auszuwerten und ein Zusatzfeld "Adressentyp" befüllen zu lassen (Privatadresse, Firmenhauptadresse, Firmennebenadresse, Mitarbeiteradresse, Verweisung). Cobra bringt zwar auch im Frontend Spielwiesen für "Kopierregeln" bzw. "dynamische Felder" mit, aber die funktionieren eher selten wirklich stabil und Routinen über mehrere Tabellen hinweg gehen da fast garnicht. Und dann steht noch das Thema Rechenfelder bzw. Aktualisierung von Zahlenfeldern (z.B. Anzahl von verkauften Posten) an. Es wird also sicherlich noch Hilfe bei neuen Klemmungen nötig sein...

GLG, PeterS
 
Zurück
Oben