Diplom-Biologe | Senior IT-Consultant
|
SH |
Sascha Hess xenosystems.de - IT-Consulting & Data Management |
www.xenosystems.de |
|
|
Strategische Wissens-Roadmap 2026 |
|
|
SQL Server |
|
|
|
|
|
Datenbankdesign |
|
|
Klare Regeln für saubere SQL-Server-Architekturen im Mittelstand |
WAS SIE IN DIESEM KIT ERHALTEN:
|
|
1 |
Entscheidungsmatrix 10 Kriterien — wann View, wann Stored Proc, wann beides |
|
|
2 |
10 Design-Fallen Typische Fehler in gewachsenen Datenbanken — mit Sofort-Korrekturen |
|
|
3 |
Performance-Muster Indexed Views, Execution Plans, Plan Caching — richtig eingesetzt |
|
|
4 |
Sicherheits-Framework Berechtigungen, Row-Level Security und Ownership Chaining |
|
|
5 |
30-Tage-Refactoring-Plan Gewachsene Datenbanken strukturiert bereinigen — ohne Betriebsunterbrechung |
HAFTUNGSAUSSCHLUSS
Alle Skripte und Empfehlungen wurden sorgfältig erarbeitet. Da jede Datenbankumgebung individuell ist, übernimmt der Autor keinerlei Haftung für Datenverlust, Systemausfälle oder sonstige Schäden. Erstellen Sie vor jeder Änderung an Produktionsobjekten ein vollständiges Backup und testen Sie in einer Entwicklungsumgebung.
KEINE ERGEBNISGARANTIE
Genannte Performance-Gewinne sind Erfahrungswerte aus realen KMU-Umgebungen. Tatsächliche Ergebnisse hängen von Datenvolumen, Hardwareausstattung, Indexstruktur und Abfragemuster ab.
VERSIONSHINWEIS
Die Inhalte beziehen sich auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x) sowie Azure SQL Database, Stand März 2026. Durch künftige Updates von Microsoft können Abweichungen entstehen.
URHEBERRECHT
Dieses Dokument ist für den persönlichen oder betriebsinternen Gebrauch des Käufers lizenziert. Weiterverkauf, Weitergabe an Dritte und öffentliche Veröffentlichung sind ohne schriftliche Genehmigung nicht gestattet.
KEINE VERBINDUNG ZU MICROSOFT
Dieses Kit ist ein unabhängiges Werk und steht in keinerlei Verbindung zur Microsoft Corporation. SQL Server ist eine eingetragene Marke der Microsoft Corporation.
Eine ausführliche Version dieses Haftungsausschlusses befindet sich am Ende dieses Dokuments.
01 Einleitung
Warum die falsche Wahl Ihre Datenbank langfristig lähmt
02 Grundlagen
Views und Stored Procedures — Konzepte, Stärken, Grenzen
03 Entscheidungsmatrix
10 Kriterien mit Bewertungsschema für jede Situation
04 Die 10 Design-Fallen
Typische Fehler in gewachsenen Datenbanken — mit Korrekturen
05 Views in der Praxis
Einfache Views, Indexed Views, CTEs und Security Views
06 Stored Procedures in der Praxis
Kapselung, Fehlerbehandlung, Output-Parameter, Transaktionen
07 Performance-Muster
Plan Caching, Parameter Sniffing, Indexed Views und Statistiken
08 Sicherheits-Framework
Berechtigungen, Ownership Chaining, Row-Level Security
09 Namenskonventionen & Dokumentation
Standards für wartbare, langlebige Datenbankarchitektur
10 30-Tage-Refactoring-Plan
Gewachsene Datenbanken strukturiert bereinigen
01
In mittelständischen Unternehmen entstehen Datenbankarchitekturen selten durch bewusste Planung — sie wachsen. Über Jahre hinweg fügen verschiedene Entwickler, Berater und ERP-Anpassungsprojekte Objekte hinzu: Views, die eigentlich Stored Procedures sein sollten. Stored Procedures, die eigentlich Views sein sollten. Business-Logik in 400-Zeilen-Prozeduren, die niemand mehr versteht. Reporting-Abfragen direkt auf Basistabellen, die Performance-Probleme erzeugen.
Das Ergebnis ist immer dasselbe: Änderungen sind riskant, weil niemand alle Abhängigkeiten kennt. Performance-Probleme sind schwer lokalisierbar. Neue Entwickler brauchen Monate, um die Struktur zu verstehen. Power-BI-Berichte greifen direkt auf undokumentierte Tabellen zu.
Stored Procedures und Views sind die beiden zentralen Werkzeuge für die Kapselung von Logik in SQL Server. Beide falsch einzusetzen kostet — in Wartungsaufwand, Performance und Sicherheit.
→ Eine View ist ein benannter, gespeicherter SELECT — keine Daten, nur eine gespeicherte Abfragedefinition. Sie ist transparent, immer aktuell und ideal für Leseoperationen.
→ Eine Stored Procedure ist ein ausführbares Programm in T-SQL — mit Parametern, Fehlerbehandlung, Transaktionen und der Fähigkeit, Daten zu verändern. Sie ist ideal für Schreiboperationen, komplexe Geschäftslogik und sicherheitskritische Abläufe.
Die Entscheidung zwischen beiden ist keine Geschmacksfrage. Sie folgt klaren Regeln — die dieses Kit vermittelt.
|
|
DIE DREI HÄUFIGSTEN FEHLMUSTER IN KMU-DATENBANKEN ■ Reporting direkt auf Basistabellen — ohne Views als Abstraktionsschicht. Jede Schemaänderung bricht alle Berichte. ■ Stored Procedures als Queries — Prozeduren ohne Parameter, die einfach einen SELECT ausführen und ein Resultset zurückgeben. Dafür sind Views zuständig. ■ Views mit Seiteneffekten — Entwickler versuchen, Views mit INSTEAD OF Triggern schreibbar zu machen. Fast immer ein Wartungsalptraum. |
02
Eine View ist ein virtuelles Objekt. Sie speichert keine Daten, sondern eine SELECT-Anweisung. Jeder Zugriff führt die hinterlegte Abfrage neu aus.
|
-- Einfache View: Abstraktionsschicht für Reporting CREATE OR ALTER VIEW rpt.V_Auftraege_Aktuell AS SELECT a.AuftragsNr, k.Name AS Kunde, k.Region, m.Vorname + ' ' + m.Nachname AS Vertreter, a.Auftragsdatum, a.Lieferdatum, SUM(p.Menge * p.Einzelpreis * (1 - p.Rabatt)) AS Nettowert, a.Status FROM dbo.Auftraege a JOIN dbo.Kunden k ON a.KundenNr = k.KundenNr JOIN dbo.Mitarbeiter m ON a.VertreterNr = m.MitarbeiterNr JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE a.Status NOT IN ('STORNO', 'ENTWURF') GROUP BY a.AuftragsNr, k.Name, k.Region, m.Vorname, m.Nachname, a.Auftragsdatum, a.Lieferdatum, a.Status; |
Stärken von Views:
→ Abstraktionsschicht: Schemaänderungen an Basistabellen können hinter der View verborgen werden.
→ Sicherheit: Spalten und Zeilen können gezielt ausgeblendet werden, ohne Tabellenberechtigungen zu ändern.
→ Wiederverwendbarkeit: Komplexe JOIN-Logik einmal definieren, überall verwenden.
→ Power BI Kompatibilität: DirectQuery und Import Mode funktionieren nahtlos mit Views.
Grenzen von Views:
→ Keine Parameter — eine View kann keine Eingabewerte entgegennehmen.
→ Keine Fehlerbehandlung — ein Fehler in der View-Abfrage bricht den aufrufenden Query ab.
→ Keine Transaktionen — Views können nicht atomar schreiben.
→ Performance-Falle bei tiefer Verschachtelung — eine View, die eine View aufruft, die eine View aufruft, erzeugt komplexe Execution Plans.
Eine Stored Procedure ist kompilierter, gecachter T-SQL-Code mit vollem Programmiermodell: Parameter, Variablen, Schleifen, Fehlerbehandlung, Transaktionen.
|
-- Stored Procedure: Auftrag anlegen mit vollständiger Geschäftslogik CREATE OR ALTER PROCEDURE dbo.SP_Auftrag_Anlegen @KundenNr INT, @VertreterNr INT, @Lieferdatum DATE, @AuftragsNr INT OUTPUT, -- Rückgabewert @Fehlermeldung NVARCHAR(500) OUTPUT AS BEGIN SET NOCOUNT ON; SET @Fehlermeldung = NULL;
BEGIN TRY -- Geschäftsregeln prüfen IF NOT EXISTS (SELECT 1 FROM dbo.Kunden WHERE KundenNr = @KundenNr AND Gesperrt = 0) BEGIN SET @Fehlermeldung = 'Kunde nicht vorhanden oder gesperrt.'; RETURN -1; END
IF @Lieferdatum < CAST(GETDATE() AS DATE) BEGIN SET @Fehlermeldung = 'Lieferdatum darf nicht in der Vergangenheit liegen.'; RETURN -2; END
BEGIN TRANSACTION; INSERT INTO dbo.Auftraege (KundenNr, VertreterNr, Auftragsdatum, Lieferdatum, Status) VALUES (@KundenNr, @VertreterNr, CAST(GETDATE() AS DATE), @Lieferdatum, 'NEU');
SET @AuftragsNr = SCOPE_IDENTITY();
-- Audit-Log INSERT INTO dbo.AuditLog (Tabelle, AktionTyp, ObjektID, Zeitpunkt) VALUES ('Auftraege', 'INSERT', @AuftragsNr, SYSUTCDATETIME()); COMMIT TRANSACTION;
RETURN 0; -- Erfolg
END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; SET @Fehlermeldung = ERROR_MESSAGE(); RETURN -99; END CATCH END |
Stärken von Stored Procedures:
→ Transaktionen — atomare Schreiboperationen über mehrere Tabellen.
→ Fehlerbehandlung — TRY/CATCH mit definiertem Rollback-Verhalten.
→ Parameter — flexible, wiederverwendbare Geschäftslogik.
→ Sicherheit — Benutzer benötigen nur EXECUTE-Recht, keine direkten Tabellenrechte.
→ Plan Caching — einmal kompilierter Ausführungsplan wird wiederverwendet.
Grenzen von Stored Procedures:
→ Kein direkter Einsatz in SELECT/FROM — eine Stored Procedure kann nicht wie eine Tabelle abgefragt werden.
→ Ergebnis schwerer zu komponieren — Resultsets aus Stored Procedures lassen sich nicht direkt mit anderen Abfragen joinen.
→ Overhead bei einfachen Leseabfragen — für reine SELECT-Operationen ohne Logik sind Views effizienter.
Inline Table-Valued Functions (iTVF) kombinieren die Parameterfähigkeit von Stored Procedures mit der Kompositionsfähigkeit von Views. Sie sind oft die beste Wahl für parametrisierte Leseoperationen.
|
-- Inline TVF: parametrisierte View — verwendbar in JOINs und WHERE-Klauseln CREATE OR ALTER FUNCTION dbo.FN_Auftraege_Je_Kunde ( @KundenNr INT, @DatumVon DATE, @DatumBis DATE ) RETURNS TABLE AS RETURN ( SELECT a.AuftragsNr, a.Auftragsdatum, a.Status, SUM(p.Menge * p.Einzelpreis) AS Bruttowerk FROM dbo.Auftraege a JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE a.KundenNr = @KundenNr AND a.Auftragsdatum BETWEEN @DatumVon AND @DatumBis AND a.Status NOT IN ('STORNO','ENTWURF') GROUP BY a.AuftragsNr, a.Auftragsdatum, a.Status );
-- Verwendung: wie eine Tabelle — joinbar, filterbar, in CTEs verwendbar SELECT k.Name, f.AuftragsNr, f.Bruttowerk FROM dbo.Kunden k CROSS APPLY dbo.FN_Auftraege_Je_Kunde(k.KundenNr, '2025-01-01', '2025-12-31') f WHERE f.Bruttowerk > 10000 ORDER BY f.Bruttowerk DESC; |
03
Bewerten Sie jede Situation anhand dieser Kriterien. Das Objekt mit den meisten Treffern ist die richtige Wahl.
|
# |
Kriterium / Anforderung |
View |
Stored Proc |
iTVF |
|
1 |
Nur Lesezugriff, keine Datenveränderung |
✓ |
— |
✓ |
|
2 |
Schreiboperation (INSERT / UPDATE / DELETE) |
— |
✓ |
— |
|
3 |
Eingabeparameter werden benötigt |
— |
✓ |
✓ |
|
4 |
Ergebnis soll in anderem SELECT verwendbar sein (JOIN, CTE) |
✓ |
— |
✓ |
|
5 |
Transaktion über mehrere Tabellen erforderlich |
— |
✓ |
— |
|
6 |
Fehlerbehandlung mit TRY/CATCH und Rollback |
— |
✓ |
— |
|
7 |
Abstraktion für Power BI / Reporting |
✓ |
— |
✓ |
|
8 |
Komplexe Geschäftslogik mit Verzweigungen |
— |
✓ |
— |
|
9 |
Zeilenweise Filterung nach Benutzerkontext |
✓ (+ RLS) |
✓ |
✓ |
|
10 |
Soll indiziert werden (Indexed View) |
✓ |
— |
— |
|
|
DIE FAUSTREGEL FÜR DEN ALLTAG ■ Lesen ohne Parameter → View. ■ Lesen mit Parametern → Inline Table-Valued Function. ■ Schreiben, Transaktionen, Fehlerbehandlung → Stored Procedure. ■ Alles andere → Stored Procedure, aber prüfen ob eine View + TVF sauberer wäre. |
Manche Anforderungen lassen sich weder mit Views noch mit Stored Procedures elegant lösen:
→ Komplexe Zeilenverarbeitung (CURSOR über große Datenmengen) → prüfen ob setbasierter Ansatz mit Window Functions möglich ist, bevor ein CURSOR gebaut wird.
→ Dynamisches SQL (Tabellenname als Parameter) → Stored Procedure mit sp_executesql, aber nur wenn wirklich nötig — SQL Injection Risiko.
→ Datentransformation während des Ladens → ETL-Prozess (SSIS, dbt) statt Datenbanklogik.
→ Zeitgesteuerte Ausführung → SQL Agent Job, nicht View oder Stored Procedure.
04
Eine View ohne SCHEMABINDING kann "unter dem Gesicht" geändert werden: Spalten der Basistabelle werden gelöscht oder umbenannt, die View kompiliert neu ohne Fehler — und liefert beim nächsten Aufruf einen Laufzeitfehler oder falsche Daten. Erst wenn jemand den Bericht aufruft, fällt der Fehler auf.
LÖSUNG:
✓ SCHEMABINDING für alle produktiven Views verwenden.
✓ Schemaänderungen an Basistabellen erzwingen dann eine explizite View-Anpassung.
✓ Ausnahme: Views auf externe Daten (Linked Server, Synonyme) können nicht schema-bound sein.
|
-- Mit SCHEMABINDING: SQL Server verhindert Schemaänderungen an Basistabellen CREATE OR ALTER VIEW rpt.V_Kunden_Aktiv WITH SCHEMABINDING AS SELECT k.KundenNr, k.Name, k.PLZ, k.Ort, k.Email, k.Jahresumsatz FROM dbo.Kunden k -- Schema muss explizit angegeben werden (dbo.) WHERE k.Gesperrt = 0; -- Jetzt schlägt ALTER TABLE dbo.Kunden DROP COLUMN Email fehl: -- "Cannot DROP COLUMN 'Email' because it is referenced by object 'V_Kunden_Aktiv'." |
Jede DML-Anweisung in einer Stored Procedure sendet standardmäßig eine "X rows affected"-Nachricht an den Client. Bei Prozeduren mit vielen DML-Anweisungen oder Schleifen erzeugt das unnötigen Netzwerk-Overhead und kann in manchen ORM-Frameworks zu falschen Zeilenanzahl-Auswertungen führen.
LÖSUNG:
✓ SET NOCOUNT ON als erste Anweisung in jeder Stored Procedure.
✓ Wenn Zeilenanzahl gebraucht wird: @@ROWCOUNT in einer Variablen sichern.
|
CREATE OR ALTER PROCEDURE dbo.SP_Statistik_Aktualisieren AS BEGIN SET NOCOUNT ON; -- Erste Zeile, immer
DECLARE @Zeilen INT;
UPDATE dbo.Kunden SET Jahresumsatz = ( SELECT SUM(Nettobetrag) FROM dbo.Auftraege WHERE KundenNr = dbo.Kunden.KundenNr AND YEAR(Auftragsdatum) = YEAR(GETDATE()) ); SET @Zeilen = @@ROWCOUNT; -- Sofort nach DML sichern
INSERT INTO dbo.ETL_Protokoll (ProzessName, ZeilenVerarbeitet, Zeitpunkt) VALUES ('Statistik_Aktualisieren', @Zeilen, SYSUTCDATETIME()); END |
Eine View mit SELECT * expandiert die Spaltenliste zum Zeitpunkt der View-Erstellung. Wird der Basistabelle später eine Spalte hinzugefügt, erscheint sie nicht automatisch in der View — obwohl der Name "Stern" das suggeriert. Noch gefährlicher: Wird eine Spalte verschoben (DROP + ADD in anderer Reihenfolge), liefert die View falsche Werte ohne Fehlermeldung.
LÖSUNG:
✓ Niemals SELECT * in Views — immer explizite Spaltenliste.
✓ Nach jeder Schemaänderung an einer Basistabelle: betroffene Views mit sp_refreshview oder CREATE OR ALTER neu kompilieren.
|
-- Schlecht: SELECT * ist eine Zeitbombe CREATE VIEW rpt.V_Artikel_Schlecht AS SELECT * FROM dbo.Artikel; -- NIEMALS so!
-- Gut: explizite Spaltenliste CREATE OR ALTER VIEW rpt.V_Artikel WITH SCHEMABINDING AS SELECT a.ArtikelNr, a.Bezeichnung, a.Warengruppe, a.Einheit, a.VKPreis, a.EKPreis, a.AktuellerBestand FROM dbo.Artikel a WHERE a.Gesperrt = 0; |
Eine View, die eine andere View aufruft, die wiederum eine View aufruft. SQL Server muss alle Schichten expandieren, bevor er einen Execution Plan erstellt. Bei drei Ebenen sind komplexe Plans mit suboptimalen Joins und fehlenden Pushdown-Optimierungen die Regel.
LÖSUNG:
✓ Maximale Verschachtelungstiefe: zwei Ebenen (eine Basis-View, eine Aggregations-View).
✓ Komplexe Logik in einer einzigen gut dokumentierten View zusammenführen statt zu schachteln.
✓ Execution Plan prüfen: erscheinen Remote Scans oder Hash Matches, die nicht sein sollten?
|
-- Anti-Pattern: View auf View auf View CREATE VIEW rpt.V_Umsatz_Top AS SELECT * FROM rpt.V_Umsatz_Gesamt -- View auf View WHERE Rang <= 10;
-- Besser: direkt auf die Basistabellen mit CTE CREATE OR ALTER VIEW rpt.V_Umsatz_Top WITH SCHEMABINDING AS WITH Basis AS ( SELECT k.KundenNr, k.Name, SUM(p.Menge * p.Einzelpreis) AS Umsatz, RANK() OVER (ORDER BY SUM(p.Menge * p.Einzelpreis) DESC) AS Rang FROM dbo.Kunden k JOIN dbo.Auftraege a ON k.KundenNr = a.KundenNr JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE a.Status NOT IN ('STORNO','ENTWURF') GROUP BY k.KundenNr, k.Name ) SELECT KundenNr, Name, Umsatz, Rang FROM Basis WHERE Rang <= 10; |
Eine Stored Procedure ohne TRY/CATCH lässt Fehler unkontrolliert nach oben propagieren. Bei Transaktionen über mehrere Tabellen kann das zu halbfertigen Datenständen führen — Tabelle A wurde geschrieben, Tabelle B nicht, der Fehler wird erst beim nächsten Programmaufruf sichtbar.
LÖSUNG:
✓ Jede Stored Procedure, die DML enthält, bekommt TRY/CATCH mit explizitem ROLLBACK.
✓ Fehlerinformationen in Protokolltabelle schreiben, nicht nur nach oben werfen.
✓ XACT_STATE() prüfen — nicht @@TRANCOUNT — um den Transaktionsstatus korrekt zu bewerten.
|
CREATE OR ALTER PROCEDURE dbo.SP_Bestellung_Abschliessen @BestellungsNr INT AS BEGIN SET NOCOUNT ON;
BEGIN TRY BEGIN TRANSACTION;
UPDATE dbo.Bestellungen SET Status = 'ABGESCHLOSSEN', AbschlussDatum = GETDATE() WHERE BestellungsNr = @BestellungsNr;
UPDATE dbo.Lagerbestand SET AktuellerBestand = AktuellerBestand - p.Menge FROM dbo.Lagerbestand lb JOIN dbo.Bestellpositionen p ON lb.ArtikelNr = p.ArtikelNr WHERE p.BestellungsNr = @BestellungsNr;
-- Konsistenzprüfung: kein negativer Bestand erlaubt IF EXISTS (SELECT 1 FROM dbo.Lagerbestand WHERE AktuellerBestand < 0) BEGIN RAISERROR('Bestand würde negativ werden.', 16, 1); END
COMMIT TRANSACTION;
END TRY BEGIN CATCH -- XACT_STATE(): -1 = nicht committbar, 1 = committbar, 0 = keine Transaktion IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
INSERT INTO dbo.Fehlerprotokoll (Prozedur, Fehlernummer, Fehlermeldung, Zeitpunkt, Parameter) VALUES ('SP_Bestellung_Abschliessen', ERROR_NUMBER(), ERROR_MESSAGE(), SYSUTCDATETIME(), CAST(@BestellungsNr AS NVARCHAR));
THROW; -- Fehler an Aufrufer weitergeben END CATCH END |
Komplexe Berechnungen — Deckungsbeiträge, Provisionsformeln, Bonitätsbewertungen — landen in Views, weil es "praktisch" erscheint. Nach zwei Jahren weiß niemand mehr, wo die Formel herkommt. Sie wird in Views, Power BI Measures und Excel gleichzeitig gepflegt — mit drei verschiedenen Ergebnissen.
LÖSUNG:
✓ Business-Logik gehört in eine Scalar Function oder Stored Procedure mit Dokumentation.
✓ Views rufen diese zentrale Funktion auf — die Logik ist einmal definiert.
✓ Jede Business-Regel bekommt einen Kommentar mit Quelle: "Gem. Vertriebshandbuch §3.2, Stand 2024-01".
|
-- Business-Logik als dokumentierte Scalar Function kapseln CREATE OR ALTER FUNCTION dbo.FN_Deckungsbeitrag ( @VKPreis DECIMAL(18,4), @EKPreis DECIMAL(18,4), @Menge DECIMAL(18,4), @Rabatt DECIMAL(5,4) -- 0.10 = 10 % ) RETURNS DECIMAL(18,2) AS BEGIN -- Gem. Kalkulations-Richtlinie Einkauf, Version 2025-03 -- DB I = Nettoumsatz - Wareneinsatz RETURN (@VKPreis * @Menge * (1 - @Rabatt)) - (@EKPreis * @Menge); END
-- View verwendet die zentrale Funktion — Logik nur einmal CREATE OR ALTER VIEW rpt.V_Auftragspositionen_DB WITH SCHEMABINDING AS SELECT p.PositionsID, p.AuftragsNr, p.ArtikelNr, p.Menge, p.Einzelpreis, p.Rabatt, dbo.FN_Deckungsbeitrag(p.Einzelpreis, a.EKPreis, p.Menge, p.Rabatt) AS DeckungsbeitragI FROM dbo.Auftragspositionen p JOIN dbo.Artikel a ON p.ArtikelNr = a.ArtikelNr; |
Power BI kann Stored Procedures aufrufen — aber DirectQuery funktioniert nicht, Incremental Refresh nicht, und der Query Folding-Mechanismus von Power Query wird unterbrochen. Viele Entwickler bauen trotzdem alle Reporting-Abfragen als Stored Procedures, weil sie "sicherer" erscheinen.
LÖSUNG:
✓ Power BI greift ausschließlich auf Views oder Inline TVFs zu — nie auf Stored Procedures.
✓ Stored Procedures sind für Schreiboperationen aus der Anwendung zuständig, nicht für Reporting.
✓ Wenn Parameterübergabe an Power BI nötig ist: Inline TVF mit CROSS APPLY oder Query-Parameter in Power Query.
|
-- FALSCH: Power BI ruft Stored Procedure auf -- EXEC dbo.SP_Umsatz_Je_Region @Jahr = 2025 -- kein DirectQuery, kein Query Folding
-- RICHTIG: Inline TVF — voll kompatibel mit Power BI DirectQuery CREATE OR ALTER FUNCTION rpt.FN_Umsatz_Je_Region ( @Jahr INT ) RETURNS TABLE AS RETURN ( SELECT k.Region, SUM(p.Menge * p.Einzelpreis * (1 - p.Rabatt)) AS Nettoumsatz, COUNT(DISTINCT a.AuftragsNr) AS Auftragsanzahl, COUNT(DISTINCT a.KundenNr) AS Kundenanzahl FROM dbo.Auftraege a JOIN dbo.Kunden k ON a.KundenNr = k.KundenNr JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE YEAR(a.Auftragsdatum) = @Jahr AND a.Status NOT IN ('STORNO','ENTWURF') GROUP BY k.Region ); |
Eine View oder Stored Procedure wird geändert. Welche anderen Views, Prozeduren, Jobs und Anwendungen hängen davon ab? In gewachsenen Datenbanken weiß das niemand. Die Änderung wird live getestet — und drei Berichte brechen.
LÖSUNG:
✓ sys.sql_expression_dependencies für Abhängigkeitsanalyse verwenden, bevor Objekte geändert werden.
✓ Änderungsscript immer mit Abhängigkeitsprüfung beginnen.
|
-- Alle Objekte, die von einer bestimmten View abhängen SELECT OBJECT_SCHEMA_NAME(d.referencing_id) AS AbhaengigesSchema, OBJECT_NAME(d.referencing_id) AS AbhaengigesObjekt, o.type_desc AS ObjektTyp FROM sys.sql_expression_dependencies d JOIN sys.objects o ON d.referencing_id = o.object_id WHERE d.referenced_entity_name = 'V_Auftraege_Aktuell' AND d.referenced_schema_name = 'rpt' ORDER BY o.type_desc, AbhaengigesObjekt;
-- Alle Objekte, von denen ein bestimmtes Objekt abhängt (umgekehrte Richtung) SELECT d.referenced_schema_name + '.' + d.referenced_entity_name AS Abhaengigkeit, d.referenced_minor_name AS Spalte FROM sys.sql_expression_dependencies d WHERE d.referencing_id = OBJECT_ID('rpt.V_Auftraege_Aktuell') ORDER BY Abhaengigkeit; |
Dynamisches SQL via EXEC(@sql) ist eine SQL-Injection-Einladung und verhindert Plan Caching. Trotzdem findet sich dieses Muster regelmäßig in gewachsenen Datenbanken.
LÖSUNG:
✓ Dynamisches SQL immer via sp_executesql mit parametrisierten Platzhaltern.
✓ Eingaben nie per String-Konkatenation in die Query einbauen.
✓ sys.fn_isrolemember oder Whitelist-Prüfung für dynamisch übergebene Tabellennamen.
|
-- GEFÄHRLICH: SQL Injection möglich, kein Plan Caching DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM dbo.' + @Tabellenname; EXEC(@sql); -- NIEMALS SO!
-- SICHER: sp_executesql mit parametrisierten Werten CREATE OR ALTER PROCEDURE dbo.SP_Artikel_Suche @Suchbegriff NVARCHAR(100), @Warengruppe NVARCHAR(50) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @sql NVARCHAR(MAX); DECLARE @params NVARCHAR(500);
SET @sql = N'SELECT ArtikelNr, Bezeichnung, VKPreis FROM dbo.Artikel WHERE Bezeichnung LIKE @p_Such AND (@p_WG IS NULL OR Warengruppe = @p_WG) AND Gesperrt = 0';
SET @params = N'@p_Such NVARCHAR(102), @p_WG NVARCHAR(50)';
EXEC sp_executesql @sql, @params, @p_Such = '%' + @Suchbegriff + '%', @p_WG = @Warengruppe; END |
Eine View auf einer 10-Millionen-Zeilen-Tabelle wird täglich tausendmal von Power BI abgefragt. Jeder Aufruf führt einen Full Table Scan durch, weil die View nicht materialisiert ist. Ein Indexed View löst das Problem — wird aber fast nie eingesetzt, weil das Feature unbekannt ist.
LÖSUNG:
✓ Indexed Views (Materialized Views in SQL Server) für Views mit hoher Abfragefrequenz und stabilen Daten.
✓ Voraussetzungen: SCHEMABINDING, deterministischer Inhalt, kein SELECT *, kein OUTER JOIN.
✓ Kosten: Index muss bei jeder Datenänderung aktualisiert werden — nur sinnvoll bei lesedominierten Szenarien.
|
-- Indexed View: materialisiert die Aggregation permanent CREATE OR ALTER VIEW rpt.V_Umsatz_Je_Warengruppe_Monat WITH SCHEMABINDING -- Pflicht für Indexed Views AS SELECT a.Warengruppe AS Warengruppe, YEAR(b.Auftragsdatum) AS Jahr, MONTH(b.Auftragsdatum) AS Monat, SUM(p.Menge * p.Einzelpreis) AS Umsatz, COUNT_BIG(*) AS Anzahl -- COUNT_BIG Pflicht bei GROUP BY FROM dbo.Artikel a JOIN dbo.Auftragspositionen p ON a.ArtikelNr = p.ArtikelNr JOIN dbo.Auftraege b ON p.AuftragsNr = b.AuftragsNr WHERE b.Status NOT IN ('STORNO', 'ENTWURF') GROUP BY a.Warengruppe, YEAR(b.Auftragsdatum), MONTH(b.Auftragsdatum);
-- Unique Clustered Index: materialisiert die View physisch CREATE UNIQUE CLUSTERED INDEX IDX_V_Umsatz_WG_Monat ON rpt.V_Umsatz_Je_Warengruppe_Monat (Warengruppe, Jahr, Monat);
-- Ab jetzt: SQL Server nutzt den Index automatisch — auch wenn die Basistabellen direkt abgefragt werden |
05
Eine klare Schichtentrennung verhindert Wildwuchs und macht Abhängigkeiten nachvollziehbar:
|
-- Schicht 1: Basis-Views — 1:1 auf Tabellen, nur Filterung und Umbenennung CREATE OR ALTER VIEW base.V_Kunden WITH SCHEMABINDING AS SELECT KundenNr, Name, PLZ, Ort, Email, Region, Jahresumsatz, Gesperrt FROM dbo.Kunden;
-- Schicht 2: Join-Views — verknüpfen mehrere Basis-Views CREATE OR ALTER VIEW stg.V_Auftraege_Komplett WITH SCHEMABINDING AS SELECT a.AuftragsNr, a.Auftragsdatum, a.Status, k.KundenNr, k.Name AS Kunde, k.Region, m.MitarbeiterNr AS VertreterNr, m.Vorname + ' ' + m.Nachname AS Vertreter FROM dbo.Auftraege a JOIN dbo.Kunden k ON a.KundenNr = k.KundenNr JOIN dbo.Mitarbeiter m ON a.VertreterNr = m.MitarbeiterNr WHERE a.Status NOT IN ('STORNO', 'ENTWURF');
-- Schicht 3: Aggregations-Views — Kennzahlen für Reporting CREATE OR ALTER VIEW rpt.V_KPI_Umsatz_Monatlich AS SELECT YEAR(a.Auftragsdatum) AS Jahr, MONTH(a.Auftragsdatum) AS Monat, k.Region, SUM(p.Menge * p.Einzelpreis * (1 - p.Rabatt)) AS Nettoumsatz, COUNT(DISTINCT a.AuftragsNr) AS Auftragsanzahl FROM dbo.Auftraege a JOIN dbo.Kunden k ON a.KundenNr = k.KundenNr JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE a.Status NOT IN ('STORNO', 'ENTWURF') GROUP BY YEAR(a.Auftragsdatum), MONTH(a.Auftragsdatum), k.Region;
-- Schicht 4: Security-Views — blendet sensible Spalten aus CREATE OR ALTER VIEW sec.V_Kunden_Oeffentlich WITH SCHEMABINDING AS SELECT KundenNr, Name, PLZ, Ort, Region -- KEIN Email, KEINE Umsatzdaten FROM dbo.Kunden WHERE Gesperrt = 0; |
|
-- Alle Views mit Metadaten und letztem Änderungsdatum SELECT SCHEMA_NAME(v.schema_id) AS Schema, v.name AS ViewName, v.create_date AS Erstellt, v.modify_date AS GeaendertAm, OBJECTPROPERTY(v.object_id,'IsSchemaBound') AS HatSchemabinding, OBJECTPROPERTY(v.object_id,'IsIndexed') AS HatIndex, -- Abhängigkeitsanzahl (SELECT COUNT(*) FROM sys.sql_expression_dependencies WHERE referenced_id = v.object_id) AS WirdVerwendetVon, -- Spaltenanzahl (SELECT COUNT(*) FROM sys.columns WHERE object_id = v.object_id) AS Spaltenanzahl FROM sys.views v ORDER BY Schema, ViewName;
-- Views OHNE SCHEMABINDING — Kandidaten für Risikobewertung SELECT SCHEMA_NAME(schema_id) + '.' + name AS View_Ohne_Schemabinding FROM sys.views WHERE OBJECTPROPERTY(object_id,'IsSchemaBound') = 0 ORDER BY modify_date DESC; |
06
|
-- Vollständiges SP-Template für produktive Umgebungen CREATE OR ALTER PROCEDURE dbo.SP_[Name] -- INPUT-Parameter @Param1 DATENTYP, @Param2 DATENTYP = NULL, -- Optionaler Parameter mit Default -- OUTPUT-Parameter (immer am Ende) @NeuID INT = NULL OUTPUT, @Fehler NVARCHAR(500)= NULL OUTPUT AS BEGIN SET NOCOUNT ON; SET @Fehler = NULL;
-- ── 1. Eingabevalidierung ────────────────────────────────────────── IF @Param1 IS NULL BEGIN SET @Fehler = 'Parameter @Param1 darf nicht NULL sein.'; RETURN -1; END
-- ── 2. Geschäftsregel-Prüfungen ─────────────────────────────────── -- (Berechtigungen, Statusprüfungen, Limits)
-- ── 3. Hauptlogik mit Transaktion ───────────────────────────────── BEGIN TRY BEGIN TRANSACTION;
-- DML-Anweisungen hier INSERT INTO dbo.[Tabelle] ([Spalten]) VALUES ([Werte]); SET @NeuID = SCOPE_IDENTITY();
-- Audit-Eintrag INSERT INTO dbo.AuditLog (Tabelle, Aktion, ObjektID, Benutzer, Zeitpunkt) VALUES ('[Tabelle]', 'INSERT', @NeuID, SUSER_SNAME(), SYSUTCDATETIME());
COMMIT TRANSACTION; RETURN 0; -- Erfolg
END TRY BEGIN CATCH IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
SET @Fehler = ERROR_MESSAGE(); INSERT INTO dbo.Fehlerprotokoll (Prozedur, Fehlernummer, Fehlermeldung, Zeitpunkt) VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE(), SYSUTCDATETIME());
RETURN -99; END CATCH END |
|
-- Effizientes Upsert-Pattern mit MERGE CREATE OR ALTER PROCEDURE dbo.SP_Artikel_Upsert @ArtikelNr NVARCHAR(20), @Bezeichnung NVARCHAR(200), @VKPreis DECIMAL(18,4), @Warengruppe NVARCHAR(50), @GeaendertID INT OUTPUT AS BEGIN SET NOCOUNT ON;
BEGIN TRY BEGIN TRANSACTION;
MERGE dbo.Artikel AS ziel USING (SELECT @ArtikelNr AS ArtikelNr, @Bezeichnung AS Bezeichnung, @VKPreis AS VKPreis, @Warengruppe AS Warengruppe) AS quelle ON (ziel.ArtikelNr = quelle.ArtikelNr) WHEN MATCHED AND ( ziel.Bezeichnung <> quelle.Bezeichnung OR ziel.VKPreis <> quelle.VKPreis OR ziel.Warengruppe <> quelle.Warengruppe ) THEN UPDATE SET ziel.Bezeichnung = quelle.Bezeichnung, ziel.VKPreis = quelle.VKPreis, ziel.Warengruppe = quelle.Warengruppe, ziel.GeaendertAm = SYSUTCDATETIME() WHEN NOT MATCHED THEN INSERT (ArtikelNr, Bezeichnung, VKPreis, Warengruppe, AnlageDatum) VALUES (quelle.ArtikelNr, quelle.Bezeichnung, quelle.VKPreis, quelle.Warengruppe, SYSUTCDATETIME());
SET @GeaendertID = (SELECT ArtikelID FROM dbo.Artikel WHERE ArtikelNr = @ArtikelNr);
COMMIT TRANSACTION; RETURN 0;
END TRY BEGIN CATCH IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; THROW; END CATCH END |
07
Stored Procedures profitieren von Plan Caching: der Execution Plan wird beim ersten Aufruf kompiliert und wiederverwendet. Views kompilieren ihren Plan jedes Mal neu im Kontext der umgebenden Query.
|
-- Cached Plans für Stored Procedures analysieren SELECT qs.execution_count, qs.total_elapsed_time / qs.execution_count / 1000 AS avg_ms, qs.total_logical_reads / qs.execution_count AS avg_logical_reads, OBJECT_NAME(st.objectid) AS ProzedurName, qp.query_plan AS ExecutionPlan FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp WHERE st.objectid IS NOT NULL -- nur Objekte, keine Ad-hoc-Queries AND OBJECT_NAME(st.objectid) LIKE 'SP_%' ORDER BY qs.total_elapsed_time DESC; |
|
-- Parameter Sniffing erkennen: hohe Varianz in Laufzeiten derselben Prozedur SELECT OBJECT_NAME(ps.object_id) AS Prozedur, ps.execution_count, ps.total_elapsed_time / ps.execution_count AS avg_elapsed_us, ps.min_elapsed_time AS min_us, ps.max_elapsed_time AS max_us, -- Hohe Ratio max/avg deutet auf Parameter Sniffing hin ps.max_elapsed_time / NULLIF(ps.total_elapsed_time / ps.execution_count, 0) AS max_avg_ratio FROM sys.dm_exec_procedure_stats ps WHERE ps.max_elapsed_time / NULLIF(ps.total_elapsed_time / NULLIF(ps.execution_count,0), 0) > 10 ORDER BY max_avg_ratio DESC;
-- Lösung: OPTION (OPTIMIZE FOR UNKNOWN) für variable Parameter CREATE OR ALTER PROCEDURE dbo.SP_Auftraege_Je_Kunde @KundenNr INT, @DatumVon DATE, @DatumBis DATE AS BEGIN SET NOCOUNT ON; SELECT a.AuftragsNr, a.Auftragsdatum, a.Status, SUM(p.Menge * p.Einzelpreis) AS Wert FROM dbo.Auftraege a JOIN dbo.Auftragspositionen p ON a.AuftragsNr = p.AuftragsNr WHERE a.KundenNr = @KundenNr AND a.Auftragsdatum BETWEEN @DatumVon AND @DatumBis GROUP BY a.AuftragsNr, a.Auftragsdatum, a.Status OPTION (OPTIMIZE FOR UNKNOWN); -- verhindert Sniffing-Fallen END |
|
-- Views mit hoher Laufzeit identifizieren (Ad-hoc-Queries auf Views) SELECT TOP 20 qs.total_elapsed_time / qs.execution_count / 1000 AS avg_ms, qs.execution_count, qs.total_logical_reads / qs.execution_count AS avg_reads, SUBSTRING(st.text, 1, 200) AS QueryText FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st WHERE st.text LIKE '%rpt.V_%' -- Views im rpt-Schema ORDER BY qs.total_elapsed_time DESC; |
08
Das Prinzip: Benutzer erhalten niemals direkte Tabellenrechte. Sie greifen ausschließlich über Views (Lesen) und Stored Procedures (Schreiben) zu.
|
-- Rollen anlegen CREATE ROLE Rolle_Reporting; -- Nur Lesezugriff via Views CREATE ROLE Rolle_Sachbearbeiter; -- Lesen + Schreiben via SPs CREATE ROLE Rolle_Teamleiter; -- Erweiterte Views + SPs
-- Reporting: nur SELECT auf rpt-Schema-Views GRANT SELECT ON SCHEMA::rpt TO Rolle_Reporting; DENY SELECT ON SCHEMA::dbo TO Rolle_Reporting; -- keine Basistabellen
-- Sachbearbeiter: EXECUTE auf Stored Procedures GRANT EXECUTE ON dbo.SP_Auftrag_Anlegen TO Rolle_Sachbearbeiter; GRANT EXECUTE ON dbo.SP_Artikel_Upsert TO Rolle_Sachbearbeiter; GRANT SELECT ON SCHEMA::rpt TO Rolle_Sachbearbeiter;
-- Benutzer zuweisen ALTER ROLE Rolle_Reporting ADD MEMBER [domain\bi-service-account]; ALTER ROLE Rolle_Sachbearbeiter ADD MEMBER [domain\max.mustermann]; |
|
-- Ownership Chaining: SP und Tabelle im selben Schema (dbo) → -- Benutzer braucht NUR EXECUTE auf die SP, KEINE SELECT-Rechte auf die Tabelle -- Das ist die sicherste und empfohlene Architektur
-- Prüfen ob Ownership Chain intakt ist SELECT o1.name AS AufrufendesObjekt, o2.name AS AufgerufeneTabelle, SCHEMA_NAME(o1.schema_id) AS Schema_Aufrufer, SCHEMA_NAME(o2.schema_id) AS Schema_Ziel, CASE WHEN o1.principal_id = o2.principal_id THEN 'CHAIN INTAKT — kein extra Recht nötig' ELSE 'CHAIN UNTERBROCHEN — extra Rechte prüfen!' END AS OwnershipChain_Status FROM sys.sql_expression_dependencies d JOIN sys.objects o1 ON d.referencing_id = o1.object_id JOIN sys.objects o2 ON d.referenced_id = o2.object_id WHERE o1.type = 'P' -- nur Stored Procedures AND o2.type = 'U'; -- auf User Tables |
|
-- Security Policy: Vertreter sieht nur eigene Aufträge -- Schritt 1: Filterfunktion CREATE OR ALTER FUNCTION sec.FN_Auftraege_Filter(@VertreterNr INT) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS FilterErgebnis WHERE @VertreterNr = ( SELECT MitarbeiterNr FROM dbo.Mitarbeiter WHERE LoginName = USER_NAME() ) OR IS_MEMBER('db_owner') = 1 -- Admins sehen alles OR IS_MEMBER('Rolle_Teamleiter') = 1;
-- Schritt 2: Security Policy auf Tabelle anwenden CREATE SECURITY POLICY sec.Policy_Auftraege ADD FILTER PREDICATE sec.FN_Auftraege_Filter(VertreterNr) ON dbo.Auftraege WITH (STATE = ON);
-- Jetzt sieht jeder Vertriebsmitarbeiter nur seine eigenen Aufträge — -- automatisch, in allen Views und Stored Procedures, ohne Code-Änderungen |
09
|
Objekttyp |
Präfix/Schema |
Beispiel |
Begründung |
|
Tabellen |
dbo. (kein Präfix) |
dbo.Kunden |
Basistabellen ohne Präfix — immer im dbo-Schema |
|
Einfache Views |
rpt.V_ |
rpt.V_Umsatz_Monatlich |
Schema trennt Zweck, V_ markiert den Typ |
|
Security Views |
sec.V_ |
sec.V_Kunden_Oeffentlich |
Eigenes Schema für sicherheitsrelevante Views |
|
Basis-Views |
base.V_ |
base.V_Artikel_Aktiv |
Interne Schicht, nicht für direktes Reporting |
|
Inline TVFs |
dbo.FN_ |
dbo.FN_Auftraege_Je_Kunde |
FN_ für alle Functions |
|
Scalar Functions |
dbo.FN_ |
dbo.FN_Deckungsbeitrag |
Gleicher Präfix, Typ aus Kontext erkennbar |
|
Stored Procedures |
dbo.SP_ |
dbo.SP_Auftrag_Anlegen |
SP_ immer, kein usp_ oder proc_ |
|
ETL Procedures |
etl.SP_ |
etl.SP_Lade_Auftraege |
Eigenes Schema für ETL-Prozesse |
|
-- Jedes neue Datenbankobjekt bekommt diesen Kommentarblock CREATE OR ALTER PROCEDURE dbo.SP_Auftrag_Anlegen /* ======================================================================= Objekt: dbo.SP_Auftrag_Anlegen Typ: Stored Procedure Erstellt: 2025-03-01 Sascha Hess Geaendert: 2026-01-15 M. Müller — Audit-Log ergänzt ----------------------------------------------------------------------- Zweck: Legt einen neuen Auftrag an. Prüft Kundenstatus und Lieferdatum. Schreibt Audit-Log. Gibt neue AuftragsNr zurück.
Parameter: @KundenNr INT — Muss in dbo.Kunden existieren @VertreterNr INT — Muss in dbo.Mitarbeiter existieren @Lieferdatum DATE — Muss >= heute sein @AuftragsNr INT OUTPUT — Neue AuftragsNr bei Erfolg @Fehlermeldung NVARCHAR(500) — Fehlerbeschreibung bei Fehler
Rückgabewerte: 0 = Erfolg -1 = Kunde nicht vorhanden oder gesperrt -2 = Lieferdatum in der Vergangenheit -99 = Unerwarteter Fehler (siehe @Fehlermeldung)
Verwendung: EXEC dbo.SP_Auftrag_Anlegen @KundenNr=42, @VertreterNr=7, @Lieferdatum='2026-04-01', @AuftragsNr=@id OUTPUT, @Fehlermeldung=@err OUTPUT;
Abhängigkeiten: Liest: dbo.Kunden, dbo.Mitarbeiter Schreibt: dbo.Auftraege, dbo.AuditLog ======================================================================= */ @KundenNr INT, -- [...] |
|
-- Vollständiges Objekt-Inventar der Datenbank mit Metadaten SELECT SCHEMA_NAME(o.schema_id) AS Schema, o.name AS ObjektName, o.type_desc AS ObjektTyp, o.create_date AS Erstellt, o.modify_date AS GeaendertAm, -- Letzter Aufruf aus Statistik MAX(qs.last_execution_time) AS LetzterAufruf, SUM(qs.execution_count) AS AufrufeSeit_Neustart, -- Kommentarblock vorhanden? CASE WHEN sm.definition LIKE '%======%' THEN 'JA' ELSE 'FEHLT' END AS Dokumentation FROM sys.objects o LEFT JOIN sys.sql_modules sm ON o.object_id = sm.object_id LEFT JOIN sys.dm_exec_procedure_stats qs ON o.object_id = qs.object_id WHERE o.type IN ('V','P','IF','FN','TF') -- Views, Procs, Functions AND o.is_ms_shipped = 0 GROUP BY SCHEMA_NAME(o.schema_id), o.name, o.type_desc, o.create_date, o.modify_date, sm.definition ORDER BY ObjektTyp, Schema, ObjektName; |
10
|
|
VOR DEM START Führen Sie ein vollständiges Backup durch. Erstellen Sie einen Snapshot aller bestehenden Objekte mit dem Inventar-Skript aus Kapitel 9.3. Kommunizieren Sie geplante Änderungen an alle Anwendungsteams — auch kleine Umbenennungen können Anwendungen brechen. |
■ TAG 1-2: VOLLSTÄNDIGES OBJEKT-INVENTAR
■ Inventar-Skript aus Kapitel 9.3 ausführen — alle Views, Procs und Functions erfassen
■ Objekte ohne Dokumentationsblock identifizieren (Kommentarblock fehlt)
■ Views ohne SCHEMABINDING auflisten — das ist Ihre Risiko-Shortlist
■ Objekte, die seit > 1 Jahr nicht aufgerufen wurden, als Kandidaten für Archivierung markieren
■ TAG 3-4: ABHÄNGIGKEITEN KARTIEREN
■ Abhängigkeitsanalyse mit sys.sql_expression_dependencies für alle Top-50-Objekte
■ Views, die andere Views aufrufen — Verschachtelungstiefe messen
■ Welche Views werden direkt von Power BI abgefragt? (Connection Strings prüfen)
■ Welche Stored Procedures werden von Anwendungen aufgerufen? (App-Team befragen)
■ TAG 5-7: PRIORISIERUNG
■ Objekte in drei Kategorien einteilen: KRITISCH (täglich genutzt), AKTIV, HISTORISCH
■ Quick Wins identifizieren: SELECT * in Views → explizite Spaltenliste (risikoarme Änderung)
■ Refactoring-Reihenfolge festlegen: immer von innen nach außen (Basistabellen zuerst, Reports zuletzt)
■ Änderungsprotokoll-Tabelle anlegen: wer ändert was wann — Rollback-Strategie festlegen
■ TAG 8-10: SELECT * ELIMINIEREN
■ Alle Views mit SELECT * auflisten und explizite Spaltenlisten ergänzen
■ Nach jeder Änderung: betroffene Views mit sp_refreshview testen
■ Power BI Berichte nach View-Änderung auf korrekte Spalten prüfen
■ SCHEMABINDING für alle neu geänderten Views aktivieren
■ TAG 11-13: FEHLERBEHANDLUNG NACHRÜSTEN
■ Alle Stored Procedures ohne TRY/CATCH identifizieren
■ SP-Template aus Kapitel 6.1 auf die kritischsten Prozeduren anwenden
■ SET NOCOUNT ON in allen Prozeduren ergänzen, die es noch nicht haben
■ XACT_STATE()-basierter Rollback überall dort einbauen, wo Transaktionen genutzt werden
■ TAG 14: SICHERHEITS-AUDIT
■ Alle direkten Tabellen-Grants an Benutzer und Rollen auflisten
■ Berechtigungsmodell aus Kapitel 8.1 als Zielzustand definieren
■ Low-Risk: neue Security Views anlegen ohne bestehende Rechte zu entziehen
■ Migration-Plan: schrittweise Umstellung auf View/SP-basierte Berechtigungen
■ TAG 15-17: VERSCHACHTELTE VIEWS AUFLÖSEN
■ Views mit Verschachtelungstiefe > 2 identifizieren
■ Tiefste Schicht zuerst in direkte Tabellenreferenzen umschreiben
■ Execution Plans vor und nach dem Refactoring vergleichen
■ Jeden aufgelösten View mit SCHEMABINDING neu anlegen
■ TAG 18-20: NAMENSKONVENTIONEN EINFÜHREN
■ Schema-Struktur (rpt, sec, base, etl) anlegen falls nicht vorhanden
■ Neue Objekte ab sofort nach Konvention aus Kapitel 9.1 benennen
■ Bestehende kritische Objekte per Synonym umleiten (alten Namen erhalten, neues Objekt anlegen)
■ Dokumentationsblock-Template für alle neuen und geänderten Objekte verpflichtend einführen
■ TAG 21: POWER BI REPORTING-SCHICHT
■ Alle Power BI DirectQuery-Verbindungen auf rpt-Schema-Views umstellen
■ Stored Procedures im Power BI durch Views oder Inline TVFs ersetzen (Kapitel 4, Punkt 7)
■ Fehlende Reporting-Views für die meistgenutzten Power BI Datasets anlegen
■ Query Folding für jede neue View in Power BI Desktop prüfen
■ TAG 22-25: PERFORMANCE-ANALYSE
■ Langsame Views aus Kapitel 7.3 identifizieren — Top 20 nach Gesamtlaufzeit
■ Execution Plans der langsamsten Views analysieren: Table Scans, Key Lookups, Hash Joins
■ Indexed Views für die zwei frequentiertesten Aggregations-Views anlegen (Kapitel 4, Punkt 10)
■ Parameter Sniffing in Stored Procedures prüfen und mit OPTIMIZE FOR UNKNOWN beheben
■ TAG 26-28: MONITORING & GOVERNANCE
■ Inventar-Abfrage als wöchentlichen SQL Agent Job einrichten
■ Alert: neue Views ohne SCHEMABINDING → Database Mail an DBA
■ Alert: Objekte ohne Dokumentationsblock → in Weekly Report aufnehmen
■ Code-Review-Checkliste für neue Datenbankobjekte erstellen und im Team einführen
■ TAG 29-30: ABSCHLUSS & DOKUMENTATION
■ Abschluss-Inventar erstellen und mit Ausgangszustand vergleichen
■ Anzahl behobener Fallen aus Kapitel 4 dokumentieren
■ Architektur-Dokumentation: Schema-Diagramm mit View/SP-Schichten aktualisieren
■ Coding Guidelines für neue Entwickler und Berater schriftlich festhalten
■ Ergebnis feiern — eine wartbare Datenbankarchitektur spart täglich Stunden! ■
|
|
ERGEBNIS NACH 30 TAGEN Ihr Ergebnis nach 30 Tagen: Dokumentierte Objekte mit klaren Zuständigkeiten, SCHEMABINDING auf allen produktiven Views, robuste Fehlerbehandlung in allen kritischen Stored Procedures, eine saubere Berechtigungsstruktur und ein Power BI Stack, der ausschließlich auf definierten Reporting-Views aufsetzt. |
Die in diesem Dokument enthaltenen Informationen, Skripte und Empfehlungen wurden nach bestem Wissen und Gewissen auf der Grundlage langjähriger praktischer Erfahrung erstellt. Da jede Datenbankumgebung individuell ist, übernimmt der Autor keinerlei Haftung für Datenverlust, Systemausfälle, Produktionsunterbrechungen oder sonstige unmittelbare oder mittelbare Schäden.
Die Nutzung der bereitgestellten Skripte erfolgt ausschließlich auf eigenes Risiko des Anwenders. Es wird dringend empfohlen, vor jeder Änderung an Produktionsdatenbanken ein vollständiges Backup zu erstellen und alle Änderungen zuerst in einer Testumgebung zu validieren.
Alle Skripte wurden in realen KMU-Umgebungen auf SQL Server 2022/2025 erprobt. Dennoch können aufgrund unterschiedlicher Systemkonfigurationen, Datenbankversionen, Kollationen und Berechtigungsstrukturen unerwartete Verhaltensweisen auftreten. Besondere Vorsicht gilt bei Änderungen an Berechtigungsstrukturen — immer im Vier-Augen-Prinzip durchführen.
Genannte Performance-Gewinne durch Indexed Views, Plan Caching oder SCHEMABINDING sind Erfahrungswerte. Tatsächliche Ergebnisse hängen von Hardware, SQL Server Version, Datenvolumen, Indexstruktur und Abfragemustern ab.
Die Inhalte beziehen sich auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x) sowie Azure SQL Database, Stand März 2026.
© 2026 Sascha Hess, xenosystems.de. Alle Rechte vorbehalten. Dieses Dokument ist für den persönlichen oder betriebsinternen Gebrauch des Käufers lizenziert.
SQL Server, Azure SQL, Power BI und weitere Microsoft-Produktnamen sind eingetragene Marken der Microsoft Corporation.
Es gilt ausschließlich deutsches Recht. Gerichtsstand für alle Streitigkeiten ist, soweit gesetzlich zulässig, Weimar, Thüringen, Deutschland.
Sascha Hess ist Diplom-Biologe und IT-Professional mit über 20 Jahren Erfahrung in der Administration von ERP-, BI- und Datenbanksystemen. Er hat mehr als 300 Oracle- und SQL-Server-Instanzen administriert und betreut — von mittelständischen KMU bis zu Universitäten und Energieversorgern.
Sein Ansatz verbindet naturwissenschaftliche Präzision mit hochgradiger IT-Spezialisierung. Schwerpunkte: SQL Server Performance-Tuning, Datenbankdesign, ERP-Einführungen, Business Intelligence (Power BI, DeltaMaster), Prozessdigitalisierung und IT-Interim-Management.
Web: www.xenosystems.de | E-Mail: info@xenosystems.de | Standort: Weimar, Thüringen / Remote
|
Service |
Beschreibung |
|
SQL Server DB Health Check |
Professioneller Audit Ihres SQL Servers — Managementreport, Risikobewertung, Architektur-Review und Maßnahmenplan. Scope: 3-5 Tage. |
|
Datenbankdesign & Refactoring |
Strukturiertes Refactoring gewachsener Datenbanken — Views, Stored Procedures, Berechtigungen, Namenskonventionen. Scope: ab 1 Woche. |
|
BI-Dashboard-Aufbau |
Power BI / DeltaMaster Dashboards inkl. ETL-Strecken, Data-Warehouse-Aufbau und sauberem View-Layer — für KMU ab 1 Woche Projektumfang. |
|
ERP-Einführungsberatung |
Begleitung von ERP-Projekten (MACH, Dynamics NAV, APplus) — Datenmigration, Schnittstellenentwicklung, Go-Live-Support. |
|
Interim IT-Management |
Übernahme der IT-Steuerung auf Zeit — Budgetplanung, Dienstleister-Management, strategische IT-Ausrichtung. |
Vollständiges Dokument
Strategische Wissens-Roadmap · Checklisten · Praxisbeispiele
49,90 €
Sichere Zahlung über PayPal · Sofort-Download nach Zahlungseingang