Oracle - Trigger erstellen Sie eine Geschichte auf Zeile aktualisieren
Ersten, wir haben derzeit das Verhalten, das gewünscht ist, aber es ist nicht trivial zu erhalten, wenn änderungen an der Datenbank erforderlich sind. Ich bin auf der Suche nach etwas einfacher, effizienter oder einfacher zu pflegen (alles, was nicht jeder von denen 3 wäre sehr willkommen). Wenn wir ein update durchzuführen, eine Geschichte Zeile angelegt, die eine Kopie der aktuellen Zeile, und die aktuelle Zeile die Werte dann aktualisiert. Das Ergebnis ist, dass wir eine Geschichte aufnehmen, wie die Reihe war, bevor es aktualisiert wurde.
Argumentation: Wir haben die Kompatibilität mit einer Reihe von Bundes-Regeln, und ging diesen Weg, um eine vollständige audit-Historie der alles, so gut wie wir können sich an der Datenbank an einem beliebigen Punkt in der Zeit und sehen, wie die Dinge sah (für zukünftige Anforderungen). Aus ähnlichen Gründen, die ich nicht ändern kann, wie Geschichte aufgenommen wird...irgendeine Lösung müssen die gleichen Daten wie die aktuelle Trigger erstellen.
Hier ist, was der aktuelle Auslöser Aussehen für die Contact
Tabelle:
(stripped nutzlos Felder für die Kürze, die Anzahl der Felder spielt keine Rolle)
Bevor update (jede Zeile):
DECLARE
indexnb number;
BEGIN
:new.date_modified := '31-DEC-9999';
indexnb := STATE_PKG.newCONTACTRows.count + 1;
:new.date_start := sysdate;
:new.version := :old.version + 1;
state_pkg.newCONTACTRows(indexnb).ID := :old.ID;
state_pkg.newCONTACTRows(indexnb).PREFIX := :old.PREFIX;
state_pkg.newCONTACTRows(indexnb).FIRST_NAME := :old.FIRST_NAME;
state_pkg.newCONTACTRows(indexnb).MIDDLE_NAME := :old.MIDDLE_NAME;
state_pkg.newCONTACTRows(indexnb).LAST_NAME := :old.LAST_NAME;
--Audit columns after this
state_pkg.newCONTACTRows(indexnb).OWNER := :old.OWNER;
state_pkg.newCONTACTRows(indexnb).LAST_USER := :old.LAST_USER;
state_pkg.newCONTACTRows(indexnb).DATE_CREATED := :old.DATE_CREATED;
state_pkg.newCONTACTRows(indexnb).DATE_MODIFIED := sysdate;
state_pkg.newCONTACTRows(indexnb).VERSION := :old.VERSION;
state_pkg.newCONTACTRows(indexnb).ENTITY_ID := :old.id;
state_pkg.newCONTACTRows(indexnb).RECORD_STATUS := :old.RECORD_STATUS;
state_pkg.newCONTACTRows(indexnb).DATE_START := :old.DATE_START;
END;
Bevor update (einmal für alle Zeilen):
BEGIN
state_pkg.newCONTACTRows := state_pkg.eCONTACTRows;
END;
Nach update (einmal für alle Zeilen):
DECLARE
BEGIN
for i in 1 .. STATE_PKG.newCONTACTRows.COUNT loop
INSERT INTO "CONTACT" (
ID,
PREFIX,
FIRST_NAME,
MIDDLE_NAME,
LAST_NAME,
OWNER,
LAST_USER,
DATE_CREATED,
DATE_MODIFIED,
VERSION,
ENTITY_ID,
RECORD_STATUS,
DATE_START)
VALUES (
CONTACT_SEQ.NEXTVAL,
state_pkg.newCONTACTRows(i).PREFIX,
state_pkg.newCONTACTRows(i).FIRST_NAME,
state_pkg.newCONTACTRows(i).MIDDLE_NAME,
state_pkg.newCONTACTRows(i).LAST_NAME,
state_pkg.newCONTACTRows(i).OWNER,
state_pkg.newCONTACTRows(i).LAST_USER,
state_pkg.newCONTACTRows(i).DATE_CREATED,
state_pkg.newCONTACTRows(i).DATE_MODIFIED,
state_pkg.newCONTACTRows(i).VERSION,
state_pkg.newCONTACTRows(i).ENTITY_ID,
state_pkg.newCONTACTRows(i).RECORD_STATUS,
state_pkg.newCONTACTRows(i).DATE_START
);
end loop;
END;
Paket definiert als (gekürzt, die volle version ist gerade Kopie dieses pro Tisch):
PACKAGE STATE_PKG IS
TYPE CONTACTArray IS TABLE OF CONTACT%ROWTYPE INDEX BY BINARY_INTEGER;
newCONTACTRows CONTACTArray;
eCONTACTRows CONTACTArray;
END;
Das aktuelle Ergebnis
Hier ist eine resultierende Geschichte Beispiel:
ID First Last Ver Entity_ID Date_Start Date_Modified
1196 John Smith 5 0 12/11/2009 10:20:11 PM 12/31/9999 12:00:00 AM
1201 John Smith 0 1196 12/11/2009 09:35:20 PM 12/11/2009 10:16:49 PM
1203 John Smith 1 1196 12/11/2009 10:16:49 PM 12/11/2009 10:17:07 PM
1205 John Smith 2 1196 12/11/2009 10:17:07 PM 12/11/2009 10:17:19 PM
1207 John Smith 3 1196 12/11/2009 10:17:19 PM 12/11/2009 10:20:00 PM
1209 John Smith 4 1196 12/11/2009 10:20:00 PM 12/11/2009 10:20:11 PM
Jeder Geschichte-Eintrag hat eine Entity_ID das ist die ID der aktuellen Zeile, die Date_Start auf der neuen Platte entspricht der Date_Modified der letzten Geschichte Reihe. Dies erlaubt es uns, Anfragen wie Where Entity_ID = :id Or ID = :id And :myDate < Date_Modified And :myDate >= Date_Start
. Die Geschichte kann abgerufen werden, indem Entity_ID = :current_id
.
Ist es ein besserer Ansatz, der hoffentlich mehr wartbar/flexible, dies zu tun? Das Konzept ist einfach, wenn die Aktualisierung einer Zeile, kopieren Sie Sie auf die gleiche Tabelle über eine insert mit den alten Werten, dann aktualisieren Sie die aktuelle Zeile...aber eigentlich tut das, ich habe noch eine einfachere Möglichkeit. Ich hoffe jemand viel schwieriger/weiser in Oracle gibt es eine bessere Lösung für dieses. Geschwindigkeit spielt keine große Rolle, wir sind 99% liest 1% schreibt wie die meisten web-Anwendungen und alle bulk operations sind Einsätze, nicht die updates, das würde nicht jeder schaffen Geschichte.
Wenn jemand irgendwelche Ideen zur Vereinfachung der Wartung auf dieser, ich wäre sehr dankbar, danke!
- Sorry, dass ich den Punkt verpasst das erste mal um. Ich habe neu geschrieben, meine Antwort auf eigentlich Ihr Problem.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Okay, das ist ein rewrite. Was ich vermisste, als ich zuerst geantwortet, dass die Anwendung ist die Speicherung seiner Geschichte in der Haupt-Tabelle. Jetzt verstehe ich, warum @NickCraver ist so entschuldigend über den code.
Nun, das erste, was zu tun ist, auf die Jagd nach dem Täter dieses design, und stellen Sie sicher, dass Sie es nie wieder tun. Die Speicherung der Geschichte wie diese nicht skaliert werden, macht die normalen (nicht-historischen) Abfragen komplizierter und sabotiert relationale Integrität. Natürlich gibt es Szenarien, in denen keine von diesen Fragen, und vielleicht Ihre Website ist einer von Ihnen, aber im Allgemeinen ist dies eine sehr schlechte Umsetzung.
Der beste Weg, dies zu tun ist,Oracle 11g-Total Recall. Es ist eine elegante Lösung, mit einem völlig unsichtbar und effizienten Umsetzung, und - bei den standards von Oracle weitere kostenpflichtige extras - ganz preiswert.
Aber wenn "Total Recall" ist aus der Frage, und Sie wirklich tun müssen, es auf diese, nicht erlauben, updates. Eine änderung zu einem bestehenden KONTAKT-Datensatz ein insert. Um diese Arbeit zu machen, die Sie benötigen, um eine Sicht mit einem INSTEAD OF-trigger. Es ist immer noch yucky, aber nicht ganz so yucky wie das, was Sie jetzt haben.
Als von Oracle 11.2.0.4 Total Recall wurde umbenannt Flashback Archive und ist Bestandteil der Enterprise-Lizenz (obwohl geschoren, die komprimierte journal-Tabellen, es sei denn, wir kaufen Sie die Erweiterte option "Komprimieren").
Diese Großzügigkeit von Oracle sollte die FDA die normale Art und Weise der Speicherung der Geschichte: es ist effizient, es ist performativ, es ist eine Oracle built-in mit der standard-syntax zur Unterstützung von historischen Abfragen. Ach ich erwarten, um zu sehen, halbgare Implementierungen mit spatchcocked Trigger gebrochen Primärschlüssel und schreckliche Leistung für noch viele Jahre. Da journalling scheint eine von diesen Ablenkungen, die Entwickler erfreuen, trotz der Tatsache, dass es low-level-Sanitär -, das ist weitgehend irrelevant für 99,99% der gesamten Geschäftstätigkeit.
:new.date_modified := '31-DEC-9999'; :new.date_start := sysdate; :new.version := :old.version + 1;
in einem einzelnen trigger? Dies kann funktionieren...es gab einen Grund dafür konnten wir nicht tun, single-Trigger vor, aber es ist eine der Situationen, es würde so lange her, ich kann mich nicht erinnern, was der Grund war, lassen Sie mich versuchen, Ihren Ansatz und sehen, was passiert. Danke!ORA-04091: table CONTACT is mutating, trigger/function may not see it
dies ist der Grund, warum könnten wir nicht diesen Weg gehen, bevor...nahm es zu sehen, sich zu erinnern, wie die meisten alten Sachen.DB.Contacts.Current()
vsDB.Contacts
macht es einfach, Zugriff auf Geschichte in einer generischen Art und Weise bis oben...das ist die Argumentation hinter es. Die Aufteilung basiert auf Entity_Id (=0, !=0) bleibt die Leistung gut in unserem Fall....aber das system ist nicht riesig doch. Es ist möglich, eine bessere Lösung konnte erreicht werden durch ändern der Datenbank-Geschichte und linq-Ebenen, aber keine Zeit momentan.Leider gibt es keine Möglichkeit zu vermeiden, verweisen auf alle Spalten-Namen, (: ALT.dies, :ALT.dass, usw.) in Triggern. Jedoch, was Sie tun könnten, ist, ein Programm zu schreiben generieren der trigger-code aus der Tabelle definition (in USER_TAB_COLS). Dann, wenn die Tabelle geändert wird, können Sie generieren und kompilieren Sie eine neue Kopie der Auslöser.
Sehen diese AskTom thread für wie das zu tun.
select * into temp
, aktualisieren Sie nur die paar audit-Felder, ein insert....Ich bin mir nicht sicher, wie die Arbeit dieses heraus, obwohl, Oracle ist nicht mein primäres Feld 🙁Den Fall, dass jemand die gleiche hochspezialisierte Fall machen wir (Linq Zugriff machen einzelne Tabelle Geschichte es ist viel Reiniger/einfacher, das ist, was ich am Ende tun, um zu vereinfachen, was wir haben, willkommen, keine Verbesserungen....das ist nur ein Skript, dass ausgeführt wird, wenn änderungen in der Datenbank, zur Regeneration der audit-Trigger, die wichtigste änderung wird
PRAGMA AUTONOMOUS_TRANSACTION;
Platzierung der Geschichte erzeugen eine autonome Transaktion und nicht die Sorge um die mutation (welche ist egal, wie wir das audit):Wenn Sie verbessern können, fühlen Sie sich frei,...ich habe nur geschrieben, eine Handvoll von PL/SQL-Skripte, die müssen nicht auftreten oft...wahrscheinlich viel zu wünschen übrig es.
Antwort Kredit zu APC für immer mich zu sehen das ein bisschen schwieriger. Ich weiß nicht empfehlen diese Geschichte layout, sofern es seine übrigen Modell/Anwendung/stack sehr gut. Für diese Anwendung ist, dass wir immer wieder zeigen, eine Mischung aus Geschichte und aktuellen, und die Filterung ist wesentlich einfacher als die Kombination, wenn es um eine Linq-to-SQL-ähnlichen Zugriff. Danke für die ganzen Antworten Jungs, alle guten Vorschläge...und wenn ich mehr Zeit und bin nicht knirschte, indem ein release-Plan, das ist etwas, ich werde überdenken, um zu sehen, ob es weiter verbessert werden kann.
Ich verstehe Ihre spezifischen Anwendung Anforderungen zu haben, die Geschichte und die aktuellen Werte in der gleichen Tabelle, aber vielleicht könnte dies gehandhabt werden, indem man auf der üblichen route mit einem separaten audit-Tabelle, aber Bau es so ein pseudo-materialized-view zu präsentieren, der eine kombinierte Ansicht für die Anwendung.
Für mich, das hat den Vorteil, dass eine einfache "aktuell" - Ansicht sowie eine separate aber völlig automatisierte "Prüfung" - Ansicht (die in diesem Fall auch die aktuelle Ansicht).
Etwas wie:
Wenn Sie wollen, entwickeln Sie eine generische Lösung, möchten Sie vielleicht werfen Sie einen Blick auf DBMS_SQL-Paket. Mit ihm konnte man die Entwicklung ein Paket/Prozedur, die eine Tabelle name als Eingabe und erstellt die updates auf dieser Grundlage durch die Untersuchung der Struktur einer Tabelle im dictionary und Gebäude die updates on-the-fly. Wäre es nicht trivial, sich vor der Entwicklung, aber viel weniger Wartung in der Zukunft, denn wenn die Struktur einer Tabelle ändert, wird der code würde spüren, dass und anzupassen. Würde diese Methode funktioniert für jede Tabelle, die Sie interessieren, es zu benutzen mit.
Abhängig von der Komplexität Ihrer Datenbank (Anzahl der Tabellen, die Größe, Tiefe der PK/FK-Beziehungen, andere Logik in Triggern), möchten Sie vielleicht zu schauen,Oracle Workspace Management. Sie machen einen API-Aufruf zu einem Tisch, unter workspace management, die Ergebnisse in einer Oracle-ersetzen Sie die Tabelle mit einer aktualisierbaren Sicht und entsprechenden Objekte, erhalten eine Historie aller Versionen der Zeilen.
Ich habe diese verwendet und zwar gibt es Nachteile, ein Vorteil für die Prüfung ist, dass die code-Objekte sind alle generierten Oracle und deren Richtigkeit im Allgemeinen angenommen wird.
Die einzige Zeit, die ich empfehlen könnte, dass historische Belege werden in der gleichen Tabelle gespeichert wie die "aktuelle" Datensätze ist, wenn FK links zu den Aufzeichnungen müssen oder benötigen könnten, mit Ihnen zu verbinden. Z. B. eine Anwendung, die ich gesehen habe hatten einige FK links, würde der link zu der Aufzeichnung als ein "point in time", das heißt, wenn der Datensatz aktualisiert wurde, wird der FK würde noch den link zu den historischen Rekord - das war ein wichtiger Teil des design-und Trennung-Geschichte Datensätze in einer zweiten Tabelle haben würde, machte es eher unhandlich.
Abgesehen davon, würde ich es vorziehen, dass ein Unternehmen die Voraussetzung für die Verfolgung aller änderungen, die gelöst werden soll mit einer eigenen "Geschichte" Tabelle für jede Tabelle. Sicher, es bedeutet mehr, DDL -, aber es vereinfacht den code der Anwendung enorm und Sie werden auch profitieren von besserer performance und Skalierbarkeit.