none
Kann mir jemand beim Trigger helfen? RRS feed

  • Frage

  • Hallo Leute, und danke für's reinschauen...

    Ich benötige Hilfe bei einem DB-Trigger, und wäre euch dankbar für eure Tipps. 

    ist es möglich mit einem Trigger eine Sortierung in X Richtung vornehmen wenn was dazu bzw. entfehrnt wird?

    Beispiel:
    Insert: x -> 1, 2, 3, 4 wird eine dazu genommen mit x=3 -> 1, 2, 3, 3, 4 wird vom trigger in 1, 2, 3, 4, 5 verwandelt.
    oder
    Delete x -> 1, 2, 3, 4 wird eine gelöscht mit x = 3 -> 1, 2, 4 wird vom trigger in 1, 2, 3 verwandelt.

    danke im voraus...
    Montag, 5. Januar 2015 11:01

Alle Antworten

  • Hallo,

    Ja, solche leicht merkwürdigen Anforderungen kann man lösen, dazu muss man aber die komplette gewünschte Logik wissen. Warum soll bei einem Insert die Daten abgeändert werden, warum soll bei einem Delete ein anderer Datensatz als vorgegeben gelöscht werden.

    Was wenn bei einem Delete gleich mehrere Datensätze gelöscht oder beim Insert mehrere eingefügt werden sollen? Vergiss nicht, ein Trigger zündet nicht je Datensatz, sondern je Transaktion, in den virtuellen Tabellen inserted/deleted können also 1-n Datensätze stehen.


    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Montag, 5. Januar 2015 11:20
  • Vielen Dank für den Gedanken...

    Letztendlich sollen es platten darstellen. Es ist nicht möglich, dass zwei der Platten auf x=3 liegen... genau so ist es nicht möglich, dass eine Zahl in Reihe fehlt. Angenommen ich füge mehrere zu. Beispiel: ist Tabelle -> 1, 2, 3, 4, 5 und ich füge 2, 3, 4 ein. Das Ergebnis wäre so: 1, 2, 2, 3, 3, 4, 4, 5. Er soll daraus 1, 2, 3, 4, 5, 6, 7, 8 machen. Also immer nach vorne stellen. 

    Beim delete:

    von 2, 3, 4 wäre das Ergebnis rückwärts... ich hoffe ich habe jetzt kein Knoten im Kopf... 

    Montag, 5. Januar 2015 12:06
  • Ich denke, das sich eine solche Logik über kurz oder lang rächen wird.
    Was ist denn der Primary Key der Tabelle? Diese Zahlen?

    Woraus soll sich den die Sortierung ergeben?

    Könntest Du nicht viel einfacher mit ROW_NUMBER() OVER(ORDER BY ...) zur Laufzeit eine Zahl (für die Reihenfolge) erzeugen?
     Einen schönen Tag noch,
    Christoph
    --
    Microsoft SQL Server MVP - http://www.insidesql.org/blogs/cmu

    Montag, 5. Januar 2015 13:14
    Beantworter
  • Das ist nun kein vernünftiges Datenbank-Design. Beispiel ich gebe 3 ein und meinen Namen als zusätzliches Datum, speichere ab, suche dann nach 3 und erhalte den Datensatz "Max Mustermann" statt meinen.

    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    • Als Antwort vorgeschlagen Mathias Liefke Dienstag, 6. Januar 2015 10:05
    Dienstag, 6. Januar 2015 09:11
  • Naja, ich wollte jetzt nicht eine Diskussion auslösen ob es gut oder schlecht ist. Es geht mir nur um DB Sortierung nach X. Natürlich gibt es ein anderen Primärschlüssel. Wobei das eigentlich die DB selbst verwalten sollte. Selbst wenn ich jetzt als Beispiel probieren würde eine 3 reinzupacken, und auf dem Platz liegt nun mal keine Platte, dann soll er mir daraus natürlich eine 1 machen. Die Platte selbst hat einen Barcode, sowie ID spalte gibt es auch. Kleine Übersicht über die Spalten: ID(PK), Barcode, X_Pos, L, B, D 

    Deswegen suche ich im Prinzip nicht nach gelegten Position, sondern eigentlich nur nach Barcode.

    Ich wollte mich eigentlich im Programm nicht mit der Position beschäftigen...

    Danke fürs mitmachen Leute...

    Dienstag, 6. Januar 2015 12:49
  • Die gewünschte Sortierung kannst Du bei der Abfrage vorgeben, dazu muss man nicht eine künstliche Spalte verwenden. Wenn die wie gewünscht immer aktualisiert werden soll, musst Du dabei ja auch die Sortierung vorgeben.

    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Dienstag, 6. Januar 2015 13:30
  • ??? Verstehe ich nicht... die Sortierung erfolgt nur im X Richtung. Alles andere ist eigentlich irrelevant. Barcodes können ausgedacht sein, sowie die ID. Die ID ist nur der PK von dem ganzen. es könnte ja sein, dass ich sortiert die platten einfüge, und hinterher mitten drin eine lösche. zwar ist die ID dann aufsteigend, ist aber nicht sinnvoll. Denn wenn ich dann wieder eine einfüge in die Mitte, wird die ID größer... ich hoffe man kann meinen Gedankengang folgen...
    Dienstag, 6. Januar 2015 14:07
  • Fall Beispiel: ist Tabelle -> 1, 2, 3, 4, 5 und ich füge 2, 3, 4

    Kommt die neue 2 vor der alten oder nach der alten 2?


    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Dienstag, 6. Januar 2015 14:59
  • Vielen Dank für den Gedanken...

    Letztendlich sollen es platten darstellen. Es ist nicht möglich, dass zwei der Platten auf x=3 liegen... genau so ist es nicht möglich, dass eine Zahl in Reihe fehlt. Angenommen ich füge mehrere zu. Beispiel: ist Tabelle -> 1, 2, 3, 4, 5 und ich füge 2, 3, 4 ein. Das Ergebnis wäre so: 1, 2, 2, 3, 3, 4, 4, 5. Er soll daraus 1, 2, 3, 4, 5, 6, 7, 8 machen. Also immer nach vorne stellen. 

    Beim delete:

    von 2, 3, 4 wäre das Ergebnis rückwärts... ich hoffe ich habe jetzt kein Knoten im Kopf... 

    Immer nach vorne... Ich dachte meine Beispiele wären hinreichend... sollte es die Zahl nicht geben, dann nächst-fortlaufende. Beispiel: 1,2,3,4,5 ich füge eine 10 ein, dann wird aus 10 eine 6... 

    Bei der Zahl 0 und kleiner wird automatisch eine 1 gemacht, und wieder nach ganz vorne gestellt. Somit kann ich jede nur erdenkliche Situation abdecken...

    Mittwoch, 7. Januar 2015 06:45
  • Ich denke, wir sollten das jetzt mal mit einem konkreten Beispiel angehen. Es gibt also eine Tabelle #Platten, die für verschiedene Reihen eine fortlaufende Nummer liefern soll (nicht enthalten!). Hier zeige ich mal, wie es ohne Trigger gehen könnte:

    Create Table #Platten(ID Integer Identity, Reihe integer, Barcode
    varchar(10), Sortierung integer, Eingefuegt Datetime DEFAULT GETDATE());
    
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'A_BC', 1);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'B_BC', 2);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'C_BC', 3);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'D_BC', 4);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'E_BC', 5);
    
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(2, 'X_BC', 10);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(2, 'B_BC', 10);
    
    Select Reihe, Barcode, ROW_NUMBER() OVER(PARTITION BY Reihe ORDER BY
    Sortierung, Eingefuegt Desc) as LfdNr, Sortierung, Eingefuegt
    from #Platten;
     Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'X_BC', 2);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'Y_BC', 3);
    Insert into #Platten(Reihe, Barcode, Sortierung) Values(1, 'Z_BC', 4);
    
    Select Reihe, Barcode, ROW_NUMBER() OVER(PARTITION BY Reihe ORDER BY
    Sortierung, Eingefuegt Desc) as LfdNr, Sortierung, Eingefuegt
    from #Platten;
    
    Drop Table #Platten;

    Die zuletzt eingefügten Sätze X..Z werden immer vor den bestehenden Nummern einsortiert, diese wandern dann nach hinten.

    Einen schönen Tag noch,
    Christoph
    --
    Microsoft SQL Server MVP - http://www.insidesql.org/blogs/cmu

    Mittwoch, 7. Januar 2015 07:48
    Beantworter
  • Vielen Dank, aber das ist es so irgend wie nicht was ich wollte...

    Die sollen ja nicht nach hinten wandern... Ich kann es per sql string machen. aber sollte schon automatisch gehen, ganz wichtig auch in der mitte... Beispiel:

    update [Lagen] set x = (SELECT TOP 1 x FROM [Lagen] where PlatzNr = 1 and x = 2 order by x asc, id desc) + 1 where ID = (SELECT TOP 1 id FROM [Lagen] where PlatzNr = 1 and x = 2 order by PlatzPosition asc, id asc)

    ID ist fortlaufend. damit erfahre ich, welches teil gehört jetzt nach vorne. Beispiel:

    ID // PlatzNr // X

    1 // 1 // 1

    2 // 1 // 2

    3 // 1 // 3

    Jetzt kommt ein Eintrag: ID wird automatisch vergeben und somit eine 4! Also mein neuer DS wäre so:

    ID(Auto), PlatzNr=1, X = 2, BC = Irgend was... 

    Die DB macht daraus jetzt wie folgt:

    ID // PlatzNr // X

    1 // 1 // 1

    2 // 1 // 2

    3 // 1 // 3

    4 // 1 // 2

    Jetzt macht der Trigger daraus:

    ID // PlatzNr // X

    1 // 1 // 1

    4 // 1 // 2

    2 // 1 // 3

    3 // 1 // 4

    Kann doch nicht so schwer sein... oder? 

    Jedenfalls dachte ich mir so, dass er nach der ID guckt, was als letztes insertet worden ist, soll nach vorn usw...


    • Bearbeitet EugenIS_001 Mittwoch, 7. Januar 2015 10:59 Spalten haben sich verschoben...
    Mittwoch, 7. Januar 2015 10:57
  • Hallo Eugen,

    Christophs Ansatz wäre schon der Richtige, Du musst ihn nur auf Deine Belange anpassen...

    Eine mögliche Variante (nur oberflächlich getestet):

    USE tempdb;
    GO
    IF OBJECT_ID('Lagen', 'U') IS NOT NULL DROP TABLE Lagen;
    GO
    CREATE TABLE Lagen(
        Id int IDENTITY(1, 1) NOT NULL, 
        PlatzNr int NOT NULL,
        Sortierung int NOT NULL DEFAULT (0), 
        Sortierung_Alt int NOT NULL DEFAULT (0), -- nur für Test
        Eingefuegt datetime2 NOT NULL DEFAULT SYSDATETIME());
    GO
    CREATE TRIGGER TR_Lagen
    ON Lagen
    FOR INSERT, UPDATE, DELETE
    AS
        SET NOCOUNT ON;
        
        UPDATE Lagen
        SET Sortierung_Alt = Lagen.Sortierung, -- nur für Test
            Sortierung = LagenSortierung.Sortierung
        FROM Lagen
        INNER JOIN 
            (SELECT 
                    Id, 
                    ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY PlatzNr, Sortierung, Id)
                FROM Lagen
                WHERE PlatzNr IN (
                    SELECT PlatzNr FROM inserted
                    UNION SELECT PlatzNr FROM deleted)
            ) AS LagenSortierung(Id, Sortierung)
            ON Lagen.Id = LagenSortierung.Id
        WHERE Lagen.Sortierung != LagenSortierung.Sortierung;
    GO
    
    INSERT INTO Lagen(PlatzNr, Sortierung) 
    VALUES  (1, 1),
            (1, 2),
            (1, 4),
            (1, 3),
            (2, 1),
            (2, 2);
    SELECT * FROM Lagen ORDER BY PlatzNr, Sortierung;
    
    -- Weitere Einfügen
    INSERT INTO Lagen(PlatzNr, Sortierung) 
    VALUES  (1, 2),
            (1, 5),
            (2, 2),
            (2, 4);
    SELECT * FROM Lagen ORDER BY PlatzNr, Sortierung;
    
    -- Löschen
    DELETE Lagen
    WHERE PlatzNr = 1 AND Sortierung IN (3, 4);
    SELECT * FROM Lagen ORDER BY PlatzNr, Sortierung;
    
    -- Verschieben
    UPDATE Lagen
    SET PlatzNr = 3
    WHERE PlatzNr = 2 AND Sortierung IN (1, 4);
    SELECT * FROM Lagen ORDER BY PlatzNr, Sortierung;
    GO
    

    Gruß Elmar

    P. S.: Günstiger ist es, Du lieferst gleich das notwendige SQL mit, das erspart Nachfragen und Irrtümer.

    Mittwoch, 7. Januar 2015 12:35
  • Falls ich es richtig verstanden habe, müsste im Trigger eine Zeile geändert werden (ID DESC):

                                 ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY PlatzNr,
    Sortierung, Id DESC)

    Diese Variante sollten den Fall des Fragenstellers ohne Trigger abdecken:

    Create Table #Platten(ID Integer Identity, PlatzNr integer, X integer);
    
    Insert into #Platten(PlatzNr, X) Values(1, 1);
    Insert into #Platten(PlatzNr, X) Values(1, 2);
    Insert into #Platten(PlatzNr, X) Values(1, 3);
    
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    Insert into #Platten(PlatzNr, X) Values(1, 2);
    
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    Drop Table #Platten;
    go

    Und diese Variante basiert auf den Daten in Elmars Posting:

    Create Table #Platten(ID Integer Identity, PlatzNr integer, X integer);
    
    -- Weitere Einfügen (siehe Elmars Posting)
    INSERT INTO #Platten(PlatzNr, X)
    VALUES    (1, 1),
                 (1, 2),
                 (1, 4),
                 (1, 3),
                 (2, 1),
                 (2, 2);
    
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    INSERT INTO #Platten(PlatzNr, X)
    VALUES    (1, 2),
                 (1, 5),
                 (2, 2),
                 (2, 4);
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    -- Löschen
    With NumberedRows as
    (Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    )
    DELETE NumberedRows
    WHERE PlatzNr = 1 AND LfdNr IN (3, 4);
    
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    -- Verschieben
    With NumberedRows as
    (Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    )
    UPDATE NumberedRows
    SET PlatzNr = 3
    WHERE PlatzNr = 2 AND LfdNr IN (1, 4);
    
    Select ID, PlatzNr, ROW_NUMBER() OVER(PARTITION BY PlatzNr ORDER BY X, ID
    Desc) as LfdNr, X
    from #Platten
    order by PlatzNr, LfdNr;
    
    Drop Table #Platten;

    Nachteil bei der Variante ohne Trigger: Gelegentlich sind CTEs notwendig (hier NumberedRows).
    Nachteil beim Trigger: Es werden mehr Sperren und IO erzeugt und daher ist die Laufzeit schlechter, was bei steigender Datensatzanzahl zunimmt.

    Einen schönen Tag noch,
    Christoph
    --
    Microsoft SQL Server MVP - http://www.insidesql.org/blogs/cmu

    Mittwoch, 7. Januar 2015 13:15
    Beantworter
  • Danke euch... Ich werde es vermutlich doch in C# Lösen. Ist weniger aufwähndig und bietet doch schon mehr Möglichkeiten... Außerdem beim hin und her Probieren habe ich festgestellt, dass die DB zu selbständig ist. Hinterher weis keiner mehr was da passiert...

    Dennoch vielen Dank euch für's mitmachen. Hab doch was draus gelernt...

    Freitag, 9. Januar 2015 07:35
  • Hallo Eugen,

    ich sehe nicht, was an der einen Anweisung - mit oder ohne Trigger - so schwierig ist.

    Zumal ich das mit C# in LINQ fast genauso machen würde, mit dem enormen Nachteil, dass dabei jede Zeile einzeln verändert würde - langsam und deutlich fehleranfälliger.

    Nicht zuletzt... wenn man es in einem externen Programm löst, weiß am Ende bestimmt keiner mehr, was da passiert...

    Gruß Elmar

    Freitag, 9. Januar 2015 07:41
  • Hallo Eugen,
    wenn Du es in C# löst, gilt es nur für eine Anwendung. Der Trigger würde zumindest sicherstellen, dass bei allen Aktionen das gleiche rauskommt.

    Allerdings kann ich die Befürchtung vor zu viel Selbstständigkeit verstehen und würde die Lösung ohne Trigger über eine View abfedern, die dann die richtige Reihenfolge liefert. Dann müssen nur alle Anwendungen diese View verwenden.

    Einen schönen Tag noch,
    Christoph
    --
    Microsoft SQL Server MVP - http://www.insidesql.org/blogs/cmu

    Freitag, 9. Januar 2015 07:58
    Beantworter
  • Hinterher weis keiner mehr was da passiert...
    Das tritt auf wenn Du wie geplant die Daten mal so per Trigger abänderst, so das keiner vorhersagen kann, was aus den ursprünglichen Zahlen schlussendlich wird. 

    Olaf Helper

    [ Blog] [ Xing] [ MVP]

    Freitag, 9. Januar 2015 08:06