INSERTS abfangen und umleiten
Vorab: Das Ganze ist hier mehr exemplarisch und war eher als „proof of concept“ gedacht. Das Beispiel ist extra einfach gehalten und soll das Verfahren nur verdeutlichen.
Problemstellung:
Wir haben eine externe Software, die mit einer proprietärer Datenbank arbeitet, auf die man von Außen nicht zugreifen kann, wir aber nun die Daten benötigen. Es steht nur eine Exportfunktion zur Verfügung, um den Bestand komplett zu exportieren. Neben einem Flatfile-Export kann die Software auch in eine SQL Server Datenbank schreiben, aber
- festes, denormalisiertes Tabellendesign
- nur Varchar-Felder / Daten
- nur INSERT Statements
Also ein ganz „toller“ SQL Export, wurde wohl nur zu Marketing-Zwecken eingebaut, „Tooles Feature, wir können in eine SQL DB schreiben!“ (boah, ey).
Nun könnte man sagen: „Lass machen; wenn es fertig ist, bereite ich mir die Daten anderweitig mit SSIS und/oder SQL Scripten auf“; also ein Staging-Bereich im Dataware House zu Verfügung stellen und damit weiter arbeiten. Bei einem umfangreichen Szenario, sprich viel Tabelle und/oder Daten, wäre der Aufwand zur Sicherstellung der Daten-Qualität und Performanz gerechtfertig.
Und wie könnte man es ohne großen Aufwand als kleine, prozessunabhängige Lösung umsetzen?
Lösungsansatz:
Des Softwares Wille soll sein Himmelreich (oder was immer) sein. Aber eine Tabelle gibt es trotzdem nicht, da bin ich Stur.
Die eigentlichen Tabellen lege ich nach meinen Vorstellungen und natürlich normalisiert an. Hinzu kommt dann ein View, das von den Feldern her dem geforderten Tabellendesign entspricht und die Daten aus meinen Tabellen selektiert.
Dieses View ist aber definitiv nicht aktualisierbar, INSERTs können darauf nicht ausgeführt werden; wie kann jetzt trotzdem die externe Software hier rein schreiben?
Ganz einfach, mittels eines INSTEADOF Trigger, der, wieder Name schon sagt, statt des eigentlichen INSERTs ausgeführt wird und in der virtuellen Tabelle „inserted“ die Daten mit Tabellendesigns der View hat. Im Trigger kann man dann die Business Logic umsetzten, mit der man die Daten in die eigentlichen Tabellen weg schreibt.
Hier mein Demo Script dazu:
SET NOCOUNT ON; -- Kein Feedback über Anzahl
-- Basistabellen anlegen
CREATE TABLE dbo.FirstNames
(Id int IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
firstname nvarchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.LastNames
(Id int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
lastname nvarchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Names
(Id int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
firstname_id int NOT NULL,
lastname_id int NOT NULL);
GO
-- Kombination Vor/Nachname Unique
CREATE UNIQUE NONCLUSTERED INDEX [UQ_FirstLastName]
ON [dbo].[Names]
([firstname_id] ASC,
[lastname_id] ASC )
GO
-- Sicht mit den Namen auf die Basistabellen
CREATE VIEW dbo.vwNames
AS
SELECT N.Id, F.firstname, L.lastname
FROM dbo.Names AS N
INNER JOIN dbo.FirstNames AS F
ON N.firstname_id = F.Id
INNER JOIN dbo.LastNames AS L
ON N.lastname_id = L.Id;
GO
-- Geht natürlich (noch) nicht
INSERT INTO dbo.vwNames (firstname, lastname)
VALUES ('Olaf', 'Helper');
GO
-- InsteadOf Trigger zum Abfangen der INSERTS
CREATE TRIGGER dbo.TRI_vwNames ON dbo.vwNames
INSTEAD OF INSERT
AS
BEGIN
-- Fehlende Vornamen eintragen
INSERT INTO dbo.FirstNames (firstname)
SELECT DISTINCT I.firstname -- Distinct wegen Unique!!
FROM inserted as I
LEFT JOIN dbo.FirstNames AS F
ON I.firstname = F.firstname
WHERE F.firstname IS NULL
AND NOT I.firstname IS NULL
-- Dito Nachnamen
INSERT INTO dbo.LastNames (lastname)
SELECT DISTINCT I.lastname -- Distinct wegen Unique!!
FROM inserted as I
LEFT JOIN dbo.LastNames AS L
ON I.lastname = L.lastname
WHERE L.lastname IS NULL
AND NOT I.lastname IS NULL
-- Nun fehlende Namenseinträge
INSERT INTO dbo.Names (firstname_id, lastname_id)
SELECT DISTINCT F.Id, L.Id -- Distinct wegen Unique!!
FROM inserted AS I
INNER JOIN dbo.FirstNames AS F
ON I.firstname = F.firstname
INNER JOIN dbo.LastNames AS L
ON I.lastname = L.lastname
LEFT JOIN dbo.Names AS N
ON N.firstname_id = F.Id
AND N.lastname_id = L.Id
WHERE N.Id IS NULL
END;
GO
-- Nun mehrere Datensätze in einem Rutsch
-- direkt ins View schreiben
INSERT INTO dbo.vwNames
(firstname, lastname)
SELECT 'Olaf' AS firstname, 'Helper' AS lastname
UNION ALL SELECT 'Peter', 'Mustermann'
UNION ALL SELECT 'Anonymus', 'Unbekannt'
UNION ALL SELECT 'Peter', 'Unbekannt' -- Vorhandener Vor+Nachname
UNION ALL SELECT 'Olaf', 'Helper' -- Doppelter Name
UNION ALL SELECT NULL, NULL -- Schrott
GO
-- Angekommen? Jawoll!
SELECT * FROM dbo.vwNames
GO
Ergebnis:
Id firstname lastname
--- ---------- ------------
1 Anonymus Unbekannt
2 Olaf Helper
3 Peter Mustermann
4 Peter Unbekannt
Fazit:
Mit der Methode kann man zum einen Fremdappliaktionen einen Schreibzugriff einrichten und zugleich die eigentliche Datenbankstruktur gegenüber Außen völlig transparent halten. Zudem lässt sich so eine kleiner datengetriebener ETL Prozeß realisieren.
Zugegeben, nichts neues, nichts wildes, aber wollte ich halt mal ausprobieren.