Xenosystems Logo
Sascha Hess

Diplom-Biologe | Senior IT-Consultant

SH

Sascha Hess

xenosystems.de - IT-Consulting & Data Management

www.xenosystems.de

 

 

NOTFALL-KIT – SQL SERVER 2026

 

SQL Server

 

Normalisierung vs. Denormalisierung

 

Das richtige Datenbankdesign für OLTP, DWH und BI — fundiert entschieden

WAS SIE IN DIESEM KIT ERHALTEN:

 

1

10 Design-Fallen

Die häufigsten Fehler beim Datenbankdesign — mit Sofort-Korrekturen

 

 

2

Normalformen kompakt

1NF bis BCNF — verständlich, mit Vorher-Nachher-Beispielen

 

 

3

Entscheidungsrahmen

Wann normalisieren, wann denormalisieren — der Entscheidungsbaum

 

 

4

Star Schema & Data Vault

Denormalisierungsstrategien für DWH und BI — praxisnah erklärt

 

 

5

30-Tage-Redesign-Plan

Bestehende Schemas systematisch analysieren und gezielt verbessern

 


 

 

Rechtliche Hinweise und Haftungsausschluss

 

HAFTUNGSAUSSCHLUSS

 

Alle Skripte, Designempfehlungen und Methoden wurden sorgfältig erarbeitet und in realen SQL-Server-Umgebungen erprobt. Da jede Datenbankumgebung individuell ist, übernimmt der Autor keinerlei Haftung für Datenverlust, Systemausfälle oder sonstige Schäden. Testen Sie alle Schema-Änderungen zunächst in einer Nicht-Produktionsumgebung und erstellen Sie vor jedem Eingriff ein vollständiges Backup.

 

KEINE ERGEBNISGARANTIE

 

Genannte Performance-Verbesserungen sind Erfahrungswerte aus realen Projekten und keine verbindliche Zusicherung. Tatsächliche Ergebnisse hängen von Hardware, Datenvolumen, Abfragemustern und Anwendungsarchitektur ab.

 

VERSIONSHINWEIS

 

Die Inhalte beziehen sich auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x), Stand März 2026. Konzepte zur Normalisierung und zu Datenbankdesign-Prinzipien sind standardbasiert (ISO/IEC 9075) und weitgehend versionsneutral. Tool-spezifische Features (Columnstore, In-Memory OLTP) können je nach Version abweichen.

 

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.

 

MARKENRECHTE

 

SQL Server, Power BI und Azure Synapse sind eingetragene Marken der Microsoft Corporation. Data Vault ist eine Methodik von Dan Linstedt. Alle anderen genannten Produkt- und Unternehmensnamen sind Eigentum ihrer jeweiligen Inhaber.

 

Eine ausführliche Version dieses Haftungsausschlusses befindet sich am Ende dieses Dokuments.

 


 

 

Inhaltsverzeichnis

 

 

01  Einleitung

Warum das Datenbankdesign über Performance und Wartbarkeit entscheidet

 

02  Die 10 Design-Fallen

Häufigste Fehler im Datenbankschema — mit Diagnose und Fix

 

03  Normalformen kompakt

1NF bis BCNF — verständlich, mit Vorher-Nachher-Beispielen

 

04  Anomalien und ihre Kosten

Insert-, Update- und Delete-Anomalien im Alltag

 

05  Der Entscheidungsrahmen

Wann normalisieren, wann denormalisieren — systematisch entscheiden

 

06  Denormalisierung gezielt einsetzen

Abgeleitete Spalten, Redundanz und Precalculation — sicher und kontrolliert

 

07  Star Schema & Snowflake

Das Denormalisierungsmodell für BI und Data Warehouse

 

08  Data Vault als Mittelweg

Normalisierte Historisierung mit denormalisierter Auswertbarkeit

 

09  Moderne Optimierungen

Columnstore, Partitionierung, In-Memory OLTP — wann was hilft

 

10  30-Tage-Redesign-Plan

Bestehende Schemas analysieren und gezielt verbessern

 

 


 

 

01

Einleitung

 

Warum das Datenbankdesign über Performance und Wartbarkeit entscheidet

 

Zwei Datenbanken, dieselben Daten, dasselbe ERP-System. Die eine liefert Reports in drei Sekunden, hat keine Dateninkonsistenzen und lässt sich problemlos erweitern. Die andere braucht 45 Sekunden für denselben Report, enthält widersprüchliche Kundenadressen in drei Tabellen gleichzeitig, und jede Schemaerweiterung zieht eine Kaskade von Anpassungen nach sich.

 

Der Unterschied liegt fast nie in der Hardware, selten im Query-Tuning — und fast immer im Datenbankdesign.

 

Normalisierung und Denormalisierung sind keine akademischen Konzepte. Sie sind Designentscheidungen mit direkten, messbaren Auswirkungen auf Performance, Datenintegrität und Entwicklungskosten.

 

Und doch werden diese Entscheidungen in der Praxis mittelständischer IT-Projekte erschreckend selten bewusst getroffen. Schemas wachsen organisch: eine Tabelle hier, ein Extrafeld dort, eine Redundanz, weil der JOIN zu aufwendig wirkt. Nach drei Jahren hat niemand mehr einen vollständigen Überblick — und der erste Entwickler, der eine Änderung vornehmen will, braucht eine halbe Woche, um die Nebenwirkungen zu verstehen.

 

Das zentrale Spannungsfeld ist dabei kein Fehler — es ist eine fundamentale Eigenschaft relationaler Datenbanken:

 

  Normalisierung eliminiert Redundanzen, schützt die Datenintegrität und reduziert den Speicheraufwand. Sie optimiert für Schreiboperationen und Konsistenz — ideal für transaktionale Systeme (OLTP).

 

  Denormalisierung reduziert JOINs, verbessert Leseperformance und vereinfacht Abfragen auf Kosten von Redundanz und Wartungsaufwand. Sie optimiert für Leseoperationen — ideal für analytische Systeme (OLAP, BI, DWH).

 

Die Kunst liegt nicht darin, immer zu normalisieren oder immer zu denormalisieren. Die Kunst liegt darin, für jeden Kontext — OLTP, DWH, BI, Reporting — die richtige Strategie zu wählen und diese Entscheidung bewusst, dokumentiert und wartbar umzusetzen.

 

 

WAS SIE IN DIESEM BUCH ERWARTEN DÜRFEN

  10 Design-Fallen — Die häufigsten Schema-Fehler in KMU-Datenbanken mit konkreter Diagnose und Fix.

  Normalformen kompakt — 1NF bis BCNF mit echten Vorher-Nachher-Beispielen statt abstrakter Theorie.

  Entscheidungsrahmen — Wann normalisieren, wann denormalisieren — mit Entscheidungsbaum je Anwendungsfall.

  Star Schema und Data Vault — Denormalisierungsstrategien für DWH und BI, mit konkreten SQL-Beispielen.

  30-Tage-Redesign-Plan — Bestehende Schemas systematisch analysieren, priorisieren und gezielt verbessern.

 

 

DIE GRUNDREGEL

Normalisieren Sie zuerst — vollständig bis mindestens 3NF. Denormalisieren Sie danach — gezielt, kontrolliert und dokumentiert, dort wo Messung (nicht Intuition) einen konkreten Bedarf belegt. Nie umgekehrt.

 


 

 

02

Die 10 Design-Fallen

 

Häufigste Fehler im Datenbankschema — mit Diagnose und Fix

 

01 Mehrere Werte in einer Spalte

 

Eine Spalte enthält kommaseparierte Listen: Kategorien = "Elektronik,Haushalt,Garten". Das ist keine Datenbank — das ist eine versteckte Tabelle in einer Textspalte. Kein Index, keine referenzielle Integrität, kein effizienter Filter möglich.

 

DIAGNOSE:

-- Spalten mit kommaseparierten Werten finden:

SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE

FROM INFORMATION_SCHEMA.COLUMNS

WHERE DATA_TYPE IN ('varchar','nvarchar','text')

  AND TABLE_SCHEMA = 'dbo'

ORDER BY TABLE_NAME, COLUMN_NAME;

-- Dann manuell prüfen: gibt es Spalten mit trennzeichengetrennten Werten?

 

 

FIX:

-- VORHER: Mehrere Werte in einer Spalte (verletzt 1NF)

-- Artikel: ArtikelID=1, Kategorien='Elektronik,Haushalt,Garten'

 

-- NACHHER: Auflösung in Relationstabelle (1NF erfüllt)

CREATE TABLE Kategorien (

    KategorieID   INT IDENTITY PRIMARY KEY,

    KategorieName NVARCHAR(100) NOT NULL UNIQUE

);

 

CREATE TABLE Artikel_Kategorien (

    ArtikelID   INT NOT NULL REFERENCES Artikel(ArtikelID),

    KategorieID INT NOT NULL REFERENCES Kategorien(KategorieID),

    PRIMARY KEY (ArtikelID, KategorieID)

);

 

 

02 Wiederholende Spaltengruppen

 

Telefon1, Telefon2, Telefon3 in einer Kundentabelle. Was wenn ein Kunde vier Nummern hat? Was wenn 90 % der Kunden nur eine Nummer haben und die anderen Spalten leer sind? Die Tabelle ist unnötig breit, schlecht erweiterbar und schwer abzufragen.

 

FIX:

-- VORHER: Wiederholende Spaltengruppen (verletzt 1NF)

-- Kunden: KundeID, Name, Telefon1, Telefon2, Telefon3

 

-- NACHHER: Ausgelagerte Entität mit Typkennzeichen

CREATE TABLE Kunden_Kontakte (

    KontaktID  INT IDENTITY PRIMARY KEY,

    KundeID    INT NOT NULL REFERENCES Kunden(KundeID),

    KontaktTyp NVARCHAR(20)  NOT NULL  -- 'Mobil','Festnetz','Fax'

                CHECK (KontaktTyp IN ('Mobil','Festnetz','Fax','Sonstig')),

    Nummer     NVARCHAR(30)  NOT NULL,

    IstPrimär  BIT           NOT NULL DEFAULT 0

);

CREATE INDEX IX_Kunden_Kontakte_KundeID

    ON Kunden_Kontakte (KundeID);

 

 

03 Transitiver Abhängigkeit — Nicht-Schlüsselattribute hängen aneinander

 

In einer Bestelltabelle: BestellID, KundeID, KundeName, KundenPLZ, KundenStadt. KundenPLZ und KundenStadt hängen von KundeID ab — nicht von BestellID. Wenn der Kunde umzieht, müssen alle Bestellzeilen aktualisiert werden — oder die Daten werden inkonsistent.

 

FIX:

-- VORHER: Transitive Abhängigkeit (verletzt 3NF)

-- Bestellungen: BestellID, KundeID, KundeName, KundenPLZ, KundenStadt

 

-- NACHHER: Kunden-Stammdaten in eigener Tabelle

CREATE TABLE Kunden (

    KundeID    INT IDENTITY PRIMARY KEY,

    KundeName  NVARCHAR(100) NOT NULL,

    PLZ        NVARCHAR(10)  NOT NULL,

    Stadt      NVARCHAR(100) NOT NULL

);

 

CREATE TABLE Bestellungen (

    BestellID  INT IDENTITY PRIMARY KEY,

    KundeID    INT NOT NULL REFERENCES Kunden(KundeID),

    Bestelldatum DATE NOT NULL,

    -- KundeName, PLZ, Stadt: NICHT hier -- kommen per JOIN

);

 

 

04 NULL als Universalwert missbraucht

 

NULL wird verwendet um gleichzeitig "unbekannt", "nicht zutreffend", "nicht ausgefüllt" und "gelöscht" auszudrücken. JOIN-Logik wird unvorhersehbar. Aggregationen verhalten sich überraschend. WHERE-Bedingungen liefern unerwartete Ergebnisse.

 

FIX:

-- VORHER: NULL für mehrere Bedeutungen

-- Lieferdatum NULL kann bedeuten: noch nicht geliefert ODER kein Versand nötig ODER unbekannt

 

-- NACHHER: Explizite Statusmodellierung

CREATE TABLE Bestellungen (

    BestellID         INT  IDENTITY PRIMARY KEY,

    Lieferstatus      NVARCHAR(20) NOT NULL

                      DEFAULT 'Ausstehend'

                      CHECK (Lieferstatus IN

                        ('Ausstehend','Versendet','Geliefert','Kein Versand')),

    Lieferdatum       DATE NULL,   -- NULL nur wenn Lieferstatus = 'Ausstehend'

    Lieferkommentar   NVARCHAR(500) NULL  -- Freitext, darf NULL sein

    CONSTRAINT CK_Lieferdatum CHECK (

        (Lieferstatus IN ('Ausstehend','Kein Versand') AND Lieferdatum IS NULL)

        OR (Lieferstatus IN ('Versendet','Geliefert') AND Lieferdatum IS NOT NULL)

    )

);

 

 

05 Natürliche Schlüssel als Primärschlüssel

 

Kundennummer, EAN-Code, Steuernummer als Primärschlüssel. Das funktioniert — bis sich die Kundennummer ändert, der EAN-Standard aktualisiert wird oder ein Unternehmen seine Steuernummer erhält. Dann müssen Hunderte von Fremdschlüsselreferenzen kaskadierend aktualisiert werden.

 

FIX:

-- VORHER: Natürlicher Schlüssel als PK

CREATE TABLE Artikel (

    EAN          NVARCHAR(13) PRIMARY KEY,  -- Kann sich ändern!

    Bezeichnung  NVARCHAR(200) NOT NULL

);

 

-- NACHHER: Surrogate Key als PK, natürlicher Schlüssel als UNIQUE

CREATE TABLE Artikel (

    ArtikelID    INT IDENTITY PRIMARY KEY,  -- Surrogate Key: stabil

    EAN          NVARCHAR(13) NOT NULL UNIQUE,  -- Business Key: eindeutig

    Bezeichnung  NVARCHAR(200) NOT NULL

);

-- Fremdschlüssel referenzieren ArtikelID — nie EAN

 

 

06 Fehlende oder falsch gesetzte Fremdschlüssel

 

Bestellzeilen referenzieren ArtikelIDs, die in der Artikeltabelle nicht existieren — weil kein Fremdschlüssel definiert wurde. Datenimporte bringen Waisen-Datensätze ein. Reports geben falsche Aggregate zurück. JOINs verlieren Zeilen ohne Fehlermeldung.

 

FIX:

-- Bestehende referenzielle Integrität prüfen:

-- Verwaiste Fremdschlüsselwerte finden (ohne FK-Constraint)

SELECT COUNT(*) AS Waisen_Bestellzeilen

FROM dbo.Bestellzeilen bz

WHERE NOT EXISTS (

    SELECT 1 FROM dbo.Artikel a

    WHERE a.ArtikelID = bz.ArtikelID

);

 

-- FK-Constraint nachträglich hinzufügen:

ALTER TABLE dbo.Bestellzeilen

ADD CONSTRAINT FK_Bestellzeilen_Artikel

    FOREIGN KEY (ArtikelID) REFERENCES dbo.Artikel(ArtikelID);

-- Bei vorhandenen Waisenzeilen: erst bereinigen, dann Constraint anlegen

 

 

07 Catch-All-Tabellen und EAV-Anti-Pattern

 

Eine Tabelle mit Spalten EntityID, AttributName, AttributWert für alle möglichen Entitäten und Attribute. Das Entity-Attribute-Value-Muster (EAV) ist verlockend flexibel — und eine Performance- und Integritäts-Katastrophe. Keine Typsicherheit, keine Fremdschlüssel, keine Indizes auf Werte, katastrophale Abfragebarkeit.

 

FIX:

-- VORHER: EAV-Anti-Pattern

-- EntityAttributes: EntityID=1, Attribut='Preis', Wert='29.99'

-- EntityAttributes: EntityID=1, Attribut='Gewicht', Wert='0.5'

 

-- NACHHER A: Typisierte Tabellen (wenn Entitäten bekannt)

CREATE TABLE Artikel (

    ArtikelID  INT IDENTITY PRIMARY KEY,

    Bezeichnung NVARCHAR(200) NOT NULL,

    Preis      DECIMAL(10,2) NOT NULL,

    Gewicht_kg DECIMAL(8,3)  NULL

);

 

-- NACHHER B: JSON-Spalte (wenn wirklich flexible Attribute nötig sind)

-- Verfügbar ab SQL Server 2016 — typisiert, indexierbar

CREATE TABLE Artikel (

    ArtikelID    INT IDENTITY PRIMARY KEY,

    Bezeichnung  NVARCHAR(200) NOT NULL,

    Preis        DECIMAL(10,2) NOT NULL,

    Eigenschaften NVARCHAR(MAX) NULL  -- JSON: {"Gewicht":0.5,"Farbe":"Blau"}

    CONSTRAINT CK_Eigenschaften CHECK (

        Eigenschaften IS NULL OR ISJSON(Eigenschaften) = 1

    )

);

-- JSON-Index für häufig genutzte Attribute:

CREATE INDEX IX_Artikel_Gewicht ON dbo.Artikel

    (CAST(JSON_VALUE(Eigenschaften,'$.Gewicht') AS DECIMAL(8,3)));

 

 

08 Zu breite Tabellen durch fehlende Vertikale Partitionierung

 

Eine Tabelle mit 80 Spalten. 60 davon werden in 95 % aller Abfragen nie benötigt. SQL Server muss trotzdem alle Spalten in den Buffer Pool laden — weil das Row-Store-Format keine selektive Spaltenladung kennt. Unnötige I/O-Last, verschwendeter Speicher.

 

FIX:

-- Breite Tabelle vertikal aufteilen: häufig genutzter Kern + selten genutzte Erweiterung

-- VORHER: Eine breite Tabelle mit 80 Spalten

-- NACHHER: Kern-Tabelle + Erweiterungs-Tabelle (1:1-Beziehung)

 

CREATE TABLE Kunden_Kern (

    KundeID       INT IDENTITY PRIMARY KEY,

    KundeName     NVARCHAR(100) NOT NULL,

    Email         NVARCHAR(200) NOT NULL,

    PLZ           NVARCHAR(10)  NOT NULL,

    -- 5–10 häufig genutzte Stammspalten

);

 

CREATE TABLE Kunden_Erweiterung (

    KundeID           INT PRIMARY KEY REFERENCES Kunden_Kern(KundeID),

    Geburtstag        DATE          NULL,

    Kreditlimit       DECIMAL(12,2) NULL,

    Notizen           NVARCHAR(MAX) NULL,

    MarketingEinwilligung BIT       NULL,

    -- Weitere selten genutzte Spalten

);

-- Abfragen auf Kern sind schlank, voller Join nur wenn nötig

 

 

09 Fehlende Historisierung veränderlicher Stammdaten

 

Ein Kunde ändert seine Adresse. Alle alten Bestellungen zeigen jetzt die neue Adresse. War die Lieferadresse damals richtig? Niemand weiß es mehr. Für Buchhaltung, Revision und DSGVO-Auskunft ein reales Problem.

 

FIX:

-- Slowly Changing Dimension Typ 2 (SCD2): Historisierung durch Versionierung

CREATE TABLE Kunden_Historisch (

    KundeHistID   INT IDENTITY PRIMARY KEY,

    KundeID       INT          NOT NULL,   -- Business Key, stabil

    KundeName     NVARCHAR(100) NOT NULL,

    Strasse       NVARCHAR(200) NOT NULL,

    PLZ           NVARCHAR(10)  NOT NULL,

    Stadt         NVARCHAR(100) NOT NULL,

    GueltigVon    DATE         NOT NULL,

    GueltigBis    DATE         NULL,       -- NULL = aktueller Datensatz

    IstAktuell    BIT          NOT NULL DEFAULT 1,

    CONSTRAINT UQ_Kunden_Aktuell

        UNIQUE (KundeID, GueltigVon)

);

 

-- Aktuellen Datensatz lesen:

SELECT * FROM Kunden_Historisch

WHERE KundeID = 42 AND IstAktuell = 1;

 

-- Historischen Zustand zu einem Datum lesen:

SELECT * FROM Kunden_Historisch

WHERE KundeID = 42

  AND GueltigVon <= '2024-06-15'

  AND (GueltigBis IS NULL OR GueltigBis > '2024-06-15');

 

 

10 Denormalisierung ohne Konsistenzsicherung

 

Eine berechnete Spalte BestellungGesamtBetrag wird redundant in der Bestellkopftabelle gespeichert — und bei Änderungen in Bestellzeilen nicht aktualisiert. Nach drei Monaten divergieren Kopf- und Zeilensumme. Reports zeigen je nach Abfrage unterschiedliche Zahlen.

 

FIX:

-- Option A: Computed Column (SQL Server berechnet automatisch, nie inkonsistent)

ALTER TABLE dbo.Bestellungen

ADD GesamtBetrag_Berechnet AS (

    SELECT SUM(Menge * Einzelpreis)

    FROM dbo.Bestellzeilen bz

    WHERE bz.BestellID = BestellID

);

-- Achtung: Subquery in computed column ist in SQL Server nicht direkt möglich

-- → stattdessen: Indexed View oder Trigger

 

-- Option B: Indexed View (materialisiert, automatisch aktuell, indexierbar)

CREATE VIEW vw_Bestellungen_Summen WITH SCHEMABINDING AS

SELECT

    bz.BestellID,

    SUM(bz.Menge * bz.Einzelpreis) AS GesamtBetrag,

    COUNT_BIG(*)                    AS Zeilenanzahl

FROM dbo.Bestellzeilen bz

GROUP BY bz.BestellID;

 

CREATE UNIQUE CLUSTERED INDEX IX_vw_Bestellungen_Summen

    ON vw_Bestellungen_Summen (BestellID);

-- SQL Server hält die View automatisch aktuell — keine Inkonsistenz möglich

 

 


 

 

03

Normalformen kompakt

 

1NF bis BCNF — verständlich, mit Vorher-Nachher-Beispielen

 

Ausgangstabelle — das Chaos vor der Normalisierung

 

Als durchgängiges Beispiel dient eine typische gewachsene Bestelltabelle aus einem KMU:

 

AUSGANGSTABELLE: Bestellungen_Roh (nicht normalisiert)

══════════════════════════════════════════════════════════════

BestellNr │ KundeID │ KundeName │ KundenPLZ │ KundenStadt │ ArtikelIDs     │ ArtikelPreise  │ Lieferant │ LiefAdr

──────────┼─────────┼───────────┼───────────┼─────────────┼────────────────┼────────────────┼───────────┼────────

B-001     │ K1      │ Müller AG │ 99423     │ Weimar      │ A10,A12        │ 29.99,14.50    │ Liefex    │ Leipzig

B-002     │ K2      │ Schmidt   │ 80333     │ München     │ A10            │ 29.99          │ Liefex    │ Berlin

B-003     │ K1      │ Müller AG │ 99423     │ Weimar      │ A15,A10,A20    │ 5.00,29.99,8.75│ FastShip  │ Leipzig

══════════════════════════════════════════════════════════════

Probleme: Mehrere Werte in ArtikelIDs/Preise, KundenPLZ/Stadt

          von KundeID abhängig, nicht von BestellNr

 

 

Erste Normalform (1NF)

 

Anforderung: Alle Attributwerte sind atomar (unteilbar). Keine Mengen, keine Listen, keine Wiederholungsgruppen in einer Zelle.

 

-- NACH 1NF: Bestellzeilen aus der Kopftabelle herauslösen

-- Jede Zeile ist atomar — keine Listen mehr

 

-- Tabelle 1: Bestellkopf

CREATE TABLE Bestellkopf_1NF (

    BestellNr    NVARCHAR(10) NOT NULL,

    KundeID      NVARCHAR(10) NOT NULL,

    KundeName    NVARCHAR(100) NOT NULL,

    KundenPLZ    NVARCHAR(10)  NOT NULL,

    KundenStadt  NVARCHAR(100) NOT NULL,

    Lieferant    NVARCHAR(100) NOT NULL,

    LieferAdr    NVARCHAR(100) NOT NULL,

    PRIMARY KEY (BestellNr)

);

 

-- Tabelle 2: Bestellzeilen (eine Zeile je Artikel)

CREATE TABLE Bestellzeilen_1NF (

    BestellNr    NVARCHAR(10)  NOT NULL,

    ArtikelID    NVARCHAR(10)  NOT NULL,

    Einzelpreis  DECIMAL(10,2) NOT NULL,

    PRIMARY KEY (BestellNr, ArtikelID)

);

-- 1NF erfüllt: jede Zelle enthält genau einen Wert

 

 

Zweite Normalform (2NF)

 

Anforderung: 1NF erfüllt + jedes Nicht-Schlüsselattribut ist vollständig vom gesamten Primärschlüssel abhängig. Keine partiellen Abhängigkeiten (nur relevant bei zusammengesetzten Schlüsseln).

 

PROBLEM IN Bestellzeilen_1NF:

  PK = (BestellNr, ArtikelID)

  Einzelpreis hängt von BEIDEN Schlüsselteilen ab → OK

  Artikelbezeichnung (falls vorhanden) hängt nur von ArtikelID ab → Verletzung!

 

 

-- NACH 2NF: Artikelinformationen auslagern

CREATE TABLE Artikel_2NF (

    ArtikelID      NVARCHAR(10)  NOT NULL PRIMARY KEY,

    Bezeichnung    NVARCHAR(200) NOT NULL,

    -- Weitere artikelspezifische Attribute hier

);

 

CREATE TABLE Bestellzeilen_2NF (

    BestellNr    NVARCHAR(10)  NOT NULL,

    ArtikelID    NVARCHAR(10)  NOT NULL REFERENCES Artikel_2NF(ArtikelID),

    Einzelpreis  DECIMAL(10,2) NOT NULL,  -- Preis zum Bestellzeitpunkt

    Menge        INT           NOT NULL DEFAULT 1,

    PRIMARY KEY (BestellNr, ArtikelID)

);

-- 2NF erfüllt: alle Nicht-Schlüsselattribute hängen vom vollständigen PK ab

 

 

Dritte Normalform (3NF)

 

Anforderung: 2NF erfüllt + keine transitiven Abhängigkeiten. Kein Nicht-Schlüsselattribut darf von einem anderen Nicht-Schlüsselattribut abhängen.

 

PROBLEM IN Bestellkopf_1NF:

  BestellNr → KundeID → KundeName, KundenPLZ, KundenStadt

  KundeName, PLZ, Stadt hängen von KundeID ab (nicht von BestellNr) → transitiv!

  Außerdem: KundenPLZ → KundenStadt (PLZ bestimmt die Stadt) → ebenfalls transitiv!

 

 

-- NACH 3NF: alle transitiven Abhängigkeiten aufgelöst

CREATE TABLE Staedte_3NF (

    PLZ    NVARCHAR(10)  NOT NULL PRIMARY KEY,

    Stadt  NVARCHAR(100) NOT NULL

);

 

CREATE TABLE Kunden_3NF (

    KundeID   NVARCHAR(10)  NOT NULL PRIMARY KEY,

    KundeName NVARCHAR(100) NOT NULL,

    PLZ       NVARCHAR(10)  NOT NULL REFERENCES Staedte_3NF(PLZ)

);

 

CREATE TABLE Bestellkopf_3NF (

    BestellNr  NVARCHAR(10)  NOT NULL PRIMARY KEY,

    KundeID    NVARCHAR(10)  NOT NULL REFERENCES Kunden_3NF(KundeID),

    Lieferant  NVARCHAR(100) NOT NULL,

    LieferAdr  NVARCHAR(100) NOT NULL

    -- KundeName, PLZ, Stadt: weg! kommen per JOIN aus Kunden_3NF

);

-- 3NF erfüllt: keine transitiven Abhängigkeiten mehr

 

 

Boyce-Codd-Normalform (BCNF)

 

Anforderung: Verschärfung der 3NF — jede Abhängigkeit muss von einem Superschlüssel ausgehen. Relevant wenn mehrere überlappende Kandidatenschlüssel existieren.

 

BEISPIEL FÜR BCNF-VERLETZUNG:

  Tabelle: Kurs_Raum_Zeitraum(Kurs, Raum, Zeitraum)

  Abhängigkeiten:

    (Kurs, Zeitraum) → Raum     (ein Kurs hat zu einer Zeit genau einen Raum)

    (Raum, Zeitraum) → Kurs     (ein Raum hat zu einer Zeit genau einen Kurs)

  Beide sind Kandidatenschlüssel → 3NF erfüllt

  Zusätzlich: Raum → Gebäude   (Raum bestimmt das Gebäude)

  Gebäude hängt von Raum ab, nicht von einem Superschlüssel → BCNF-Verletzung!

 

 

-- NACH BCNF: Raum-Gebäude-Abhängigkeit auslagern

CREATE TABLE Räume (

    RaumID  INT IDENTITY PRIMARY KEY,

    Raum    NVARCHAR(20)  NOT NULL UNIQUE,

    Gebäude NVARCHAR(50)  NOT NULL

);

CREATE TABLE Kurs_Belegung (

    Kurs     NVARCHAR(50) NOT NULL,

    Zeitraum NVARCHAR(50) NOT NULL,

    RaumID   INT NOT NULL REFERENCES Räume(RaumID),

    PRIMARY KEY (Kurs, Zeitraum),

    UNIQUE (RaumID, Zeitraum)

);

 

 

 

WIE WEIT NORMALISIEREN?

Für produktive OLTP-Datenbanken ist 3NF der pragmatische Zielzustand. BCNF und höhere Normalformen (4NF, 5NF) sind in akademischen Kontexten relevant — in KMU-Produktivumgebungen überwiegt meist der Aufwand den Nutzen. Fokussieren Sie auf 3NF und behandeln Sie spezifische Anomalien gezielt.

 


 

 

04

Anomalien und ihre Kosten

 

Insert-, Update- und Delete-Anomalien im Alltag

 

Anomalien sind die direkte, sichtbare Konsequenz unnormalisierter Schemas. Sie entstehen immer dort, wo Daten redundant gespeichert sind — und machen sich im täglichen Betrieb als Inkonsistenzen, Fehler und Mehraufwand bemerkbar.

 

Die drei Anomalie-Typen

 

BEISPIELTABELLE (nicht normalisiert — 2NF verletzt):

══════════════════════════════════════════════════════════════

BestellID │ ArtikelID │ ArtikelName   │ KategorieID │ KatName

──────────┼───────────┼───────────────┼─────────────┼────────

1001      │ A10       │ Kaffeemaschine│ K3          │ Küche

1002      │ A10       │ Kaffeemaschine│ K3          │ Küche

1003      │ A15       │ USB-Kabel     │ K7          │ Elektro

══════════════════════════════════════════════════════════════

 

 

Insert-Anomalie: Eine neue Kategorie K9 "Garten" kann nicht eingefügt werden — solange kein Artikel dieser Kategorie bestellt wurde. Die Kategorie existiert als Konzept, aber die Tabelle erlaubt keinen Eintrag ohne BestellID und ArtikelID.

 

Update-Anomalie: Der Name von Kategorie K3 soll von "Küche" auf "Haushalt & Küche" geändert werden. Die Änderung muss in jeder Zeile vorgenommen werden, die K3 enthält — bei 50.000 Bestellzeilen für Artikel A10 sind das 50.000 Updates. Wird auch nur eine Zeile vergessen, ist die Datenbank inkonsistent.

 

Delete-Anomalie: Wird Bestellung 1003 storniert und gelöscht, verschwindet auch die Information, dass Kategorie K7 "Elektro" heißt — weil diese Information nur in der gelöschten Zeile existiert.

 

Anomalie-Kosten in der Praxis

 

Anomalie

Direkte Kosten

Indirekte Kosten

Insert-Anomalie

Manuelle Workarounds, Dummy-Datensätze

Unvollständige Stammdaten, falsche Auswertungen

Update-Anomalie

Massenupdate-Aufwand, Batch-Jobs

Inkonsistente Reports, Vertrauensverlust in Daten

Delete-Anomalie

Datenverlust beim Löschen

Fehlende Referenzinformationen, defekte Historien

 

Anomalie-Diagnose-Skript

 

-- Update-Anomalie-Risiko: Spalten mit identischen Werten je Fremdschlüssel prüfen

-- (Zeigt, wo Redundanz Update-Anomalien erzeugt)

SELECT

    ArtikelID,

    COUNT(DISTINCT ArtikelName) AS Namens_Varianten,

    COUNT(DISTINCT KategorieID) AS Kategorie_Varianten,

    COUNT(*)                    AS Zeilen_Gesamt

FROM dbo.Bestellzeilen_Denorm

GROUP BY ArtikelID

HAVING COUNT(DISTINCT ArtikelName) > 1   -- Inkonsistente Namen!

    OR COUNT(DISTINCT KategorieID) > 1;  -- Inkonsistente Kategorien!

 

 


 

 

05

Der Entscheidungsrahmen

 

Wann normalisieren, wann denormalisieren — systematisch entscheiden

 

Der Entscheidungsbaum

 

ENTSCHEIDUNGSBAUM: NORMALISIEREN ODER DENORMALISIEREN?

══════════════════════════════════════════════════════════════

Schritt 1: WAS IST DAS PRIMÄRE ZUGRIFFSMMUSTER?

 

  → Überwiegend SCHREIBEN (INSERT/UPDATE/DELETE):

    Transaktionales System, ERP, CRM, Applikationsdatenbank

    → NORMALISIEREN bis 3NF (OLTP-Empfehlung)

 

  → Überwiegend LESEN (SELECT, Reports, Dashboards):

    Data Warehouse, Reporting-Datenbank, BI-Layer

    → Weiter zu Schritt 2

 

Schritt 2: WIE KOMPLEX SIND DIE LESEABFRAGEN?

 

  → Einfache Abfragen, wenige JOINs, überschaubare Datenmenge:

    Normalisiertes Schema oft ausreichend — zuerst messen!

 

  → Viele JOINs über 5+ Tabellen, komplexe Aggregationen,

    große Datenmenge (>10M Zeilen):

    → Denormalisierung erwägen (Schritt 3)

 

Schritt 3: WIE HÄUFIG ÄNDERN SICH DIE DATEN?

 

  → Daten ändern sich häufig (täglich oder öfter):

    Denormalisierung mit Konsistenzmechanismus (Trigger,

    Indexed View) oder mäßige Denormalisierung

    → Star Schema mit täglichem ETL

 

  → Daten ändern sich selten oder werden nur hinzugefügt:

    Aggressive Denormalisierung möglich

    → Flache Tabellen, Wide Tables, Columnstore

 

Schritt 4: IST DATENINTEGRITÄT KRITISCH?

 

  → Ja (Finanzdaten, Medizin, Recht):

    Normalisierung bevorzugen — Integrität > Performance

    Denormalisierung nur mit strengen Constraints

 

  → Nein (Logs, Analysedaten, historische Snapshots):

    Denormalisierung uneingeschränkt einsetzbar

══════════════════════════════════════════════════════════════

 

 

Faustregel je Systemtyp

 

Systemtyp

Empfehlung

Begründung

OLTP (ERP, CRM, App)

3NF — vollständig normalisiert

Datenintegrität, Schreibperformance, Erweiterbarkeit

Data Warehouse (DWH)

Star Schema (denormalisiert)

Leseperformance, BI-Tool-Kompatibilität, Einfachheit

Operational Data Store (ODS)

3NF mit ausgewählter Denorm.

Nahe an OLTP-Quelle, leichte Auswertbarkeit

Data Mart

Star Schema oder flach

Optimiert für spezifische Auswertungsdomäne

Data Vault

Normalisiert (Hub/Link/Sat)

Flexibilität, Historisierung, Integration

Reporting Layer

Denormalisiert / Wide Tables

Maximale Leseperformance, minimale JOIN-Komplexität

Log-Datenbank

Denormalisiert oder append-only

Schreibgeschwindigkeit, keine Updates

 

Messen statt raten — Performance-Baseline vor Designentscheidung

 

-- Vor jeder Denormalisierungsentscheidung:

-- Aktuelle Abfragekosten des normalisierten Schemas messen

 

SET STATISTICS TIME ON;

SET STATISTICS IO ON;

 

-- Normalisierte Abfrage (mit JOINs):

SELECT

    b.BestellID,

    k.KundeName,

    a.Bezeichnung,

    bz.Menge * bz.Einzelpreis AS Umsatz

FROM dbo.Bestellkopf b

JOIN dbo.Kunden     k  ON b.KundeID    = k.KundeID

JOIN dbo.Bestellzeilen bz ON b.BestellID = bz.BestellID

JOIN dbo.Artikel    a  ON bz.ArtikelID  = a.ArtikelID

WHERE b.Bestelldatum >= '2025-01-01';

 

SET STATISTICS TIME OFF;

SET STATISTICS IO OFF;

-- Ergebnis notieren: elapsed time, logical reads

-- DANN Denormalisierung umsetzen und erneut messen

-- Nur wenn Verbesserung > 20 %: Denormalisierung behalten

 

 


 

 

06

Denormalisierung gezielt einsetzen

 

Abgeleitete Spalten, Redundanz und Precalculation — sicher und kontrolliert

 

Die vier Denormalisierungsstrategien

 

Strategie 1 — Abgeleitete Spalten materialisieren

 

Statt jede Abfrage Menge * Einzelpreis zu berechnen, wird der Zeilenbetrag als Spalte gespeichert. Spart Rechenaufwand bei häufigen Aggregationen — erfordert Konsistenzsicherung.

 

-- Mit Trigger sichergestellt (Konsistenz garantiert):

ALTER TABLE dbo.Bestellzeilen

ADD ZeilenBetrag AS (Menge * Einzelpreis) PERSISTED;

-- PERSISTED: SQL Server speichert den Wert physisch und hält ihn aktuell

-- Kein Trigger nötig — SQL Server ist für Konsistenz verantwortlich

 

-- Für komplexere Berechnungen: Trigger

CREATE OR ALTER TRIGGER trg_Bestellzeilen_ZeilenBetrag

ON dbo.Bestellzeilen AFTER INSERT, UPDATE AS

BEGIN

    UPDATE bz

    SET ZeilenBetrag = bz.Menge * bz.Einzelpreis

    FROM dbo.Bestellzeilen bz

    JOIN inserted i ON bz.BestellZeilID = i.BestellZeilID;

END;

 

 

Strategie 2 — Häufig genutzte JOINs vorberechnen (Denorm-Tabelle)

 

-- Denormalisierte Reporting-Tabelle: täglich per ETL befüllt

CREATE TABLE dbo.Reporting_Umsatz_Flat (

    -- Alle relevanten Spalten aus 4 normalisierten Tabellen:

    BestellID     INT           NOT NULL,

    Bestelldatum  DATE          NOT NULL,

    KundeID       INT           NOT NULL,

    KundeName     NVARCHAR(100) NOT NULL,

    KundenRegion  NVARCHAR(50)  NOT NULL,

    ArtikelID     INT           NOT NULL,

    ArtikelName   NVARCHAR(200) NOT NULL,

    Kategorie     NVARCHAR(100) NOT NULL,

    Menge         INT           NOT NULL,

    Einzelpreis   DECIMAL(10,2) NOT NULL,

    ZeilenBetrag  DECIMAL(12,2) NOT NULL,

    -- Zeitdimensionen für BI-Aggregationen:

    Jahr          SMALLINT      NOT NULL,

    Quartal       TINYINT       NOT NULL,

    Monat         TINYINT       NOT NULL,

    INDEX CCI_Umsatz_Flat CLUSTERED COLUMNSTORE  -- Optimal für BI-Abfragen

);

-- ETL füllt täglich: alle Änderungen des Vortages

-- Power BI greift auf diese Tabelle zu — nie auf OLTP-Tabellen direkt

 

 

Strategie 3 — Lookup-Werte inline speichern (historische Korrektheit)

 

-- Preis zum Bestellzeitpunkt speichern — kein JOIN auf Preistabelle nötig

-- UND historisch korrekt: Preisänderungen beeinflussen alte Bestellungen nicht

CREATE TABLE dbo.Bestellzeilen (

    BestellZeilID  INT IDENTITY PRIMARY KEY,

    BestellID      INT           NOT NULL REFERENCES Bestellkopf(BestellID),

    ArtikelID      INT           NOT NULL REFERENCES Artikel(ArtikelID),

    ArtikelName    NVARCHAR(200) NOT NULL,  -- Snapshot zum Bestellzeitpunkt

    Einzelpreis    DECIMAL(10,2) NOT NULL,  -- Snapshot zum Bestellzeitpunkt

    Menge          INT           NOT NULL,

    ZeilenBetrag   AS (Menge * Einzelpreis) PERSISTED

);

-- Doppelspeicherung von ArtikelName ist hier KEIN Fehler — sondern Absicht:

-- Historische Korrektheit ist wertvoller als Normalisierungsreinheit

 

 

Strategie 4 — Aggregationstabellen (Summary Tables)

 

-- Vorberechnete Monatssummen: ersetzt teure Aggregationsabfragen

CREATE TABLE dbo.Umsatz_Monats_Aggregat (

    Jahr          SMALLINT      NOT NULL,

    Monat         TINYINT       NOT NULL,

    KundeID       INT           NOT NULL,

    ArtikelID     INT           NOT NULL,

    Umsatz_Summe  DECIMAL(14,2) NOT NULL,

    Zeilen_Anzahl INT           NOT NULL,

    LetzteAktualisierung DATETIME2 NOT NULL DEFAULT SYSDATETIME(),

    PRIMARY KEY (Jahr, Monat, KundeID, ArtikelID)

);

-- Befüllung: SQL Agent Job täglich nach Geschäftsschluss

-- Power BI lädt diese Tabelle in Sekunden statt Minuten

 

 


 

 

07

Star Schema & Snowflake

 

Das Denormalisierungsmodell für BI und Data Warehouse

 

Das Star Schema ist das am häufigsten eingesetzte Datenbankdesign für analytische Systeme — und das bewährteste Denormalisierungsmuster für BI und Data Warehouses.

 

Anatomie des Star Schemas

 

STAR SCHEMA: UMSATZ-ANALYSE

══════════════════════════════════════════════════════════════

                    ┌─────────────────┐

                       Dim_Zeit     

                    │ DatumID (PK)   

                    │ Datum          

                    │ Jahr, Quartal  

                    │ Monat, Woche   

                    │ Wochentag      

                    │ IstFeiertag    

                    └────────┬────────┘

                             

  ┌──────────────┐    ┌──────▼──────────────┐    ┌─────────────────┐

    Dim_Kunde          Fact_Umsatz              Dim_Artikel    

  │ KundeID (PK) ├───►│ DatumID (FK)         │◄───┤ ArtikelID (PK) 

  │ KundeName        │ KundeID (FK)             │ ArtikelName     

  │ Segment          │ ArtikelID (FK)           │ Kategorie       

  │ Region           │ RegionID (FK)            │ Unterkategorie  

  │ Land             │ ── Maßzahlen ──          │ Marke           

  └──────────────┘    │ Umsatz_EUR               │ EAN             

                      │ Menge                    └─────────────────┘

  ┌──────────────┐    │ Deckungsbeitrag     

    Dim_Region      │ Kosten              

  │ RegionID (PK)├───►│                      

  │ Region           └─────────────────────┘

  │ Bundesland  

  │ Land        

  └──────────────┘

══════════════════════════════════════════════════════════════

 

 

Star Schema SQL-Implementierung

 

-- Dimensionstabellen: denormalisiert (alle Attribute flach)

CREATE TABLE dbo.Dim_Kunde (

    KundeID      INT IDENTITY PRIMARY KEY,    -- Surrogate Key

    KundeBizKey  NVARCHAR(20)  NOT NULL,      -- Business Key aus Quelle

    KundeName    NVARCHAR(100) NOT NULL,

    Segment      NVARCHAR(50)  NOT NULL,

    Region       NVARCHAR(50)  NOT NULL,

    Bundesland   NVARCHAR(50)  NOT NULL,

    Land         NVARCHAR(50)  NOT NULL DEFAULT 'Deutschland',

    -- SCD2-Historisierung:

    GueltigVon   DATE          NOT NULL,

    GueltigBis   DATE          NULL,

    IstAktuell   BIT           NOT NULL DEFAULT 1

);

 

CREATE TABLE dbo.Dim_Artikel (

    ArtikelID    INT IDENTITY PRIMARY KEY,

    ArtikelBizKey NVARCHAR(20) NOT NULL,

    ArtikelName  NVARCHAR(200) NOT NULL,

    -- Bewusst denormalisiert: Kategorie, Unterkategorie, Marke

    -- Im OLTP wären das separate Tabellen — hier flach für JOIN-freie Abfragen

    Kategorie    NVARCHAR(100) NOT NULL,

    Unterkategorie NVARCHAR(100) NULL,

    Marke        NVARCHAR(100) NULL,

    EAN          NVARCHAR(13)  NULL

);

 

-- Faktentabelle: nur Fremdschlüssel und Maßzahlen

CREATE TABLE dbo.Fact_Umsatz (

    -- Composite PK aus Fremdschlüsseln (kein Surrogate Key nötig):

    DatumID      INT           NOT NULL REFERENCES dbo.Dim_Zeit(DatumID),

    KundeID      INT           NOT NULL REFERENCES dbo.Dim_Kunde(KundeID),

    ArtikelID    INT           NOT NULL REFERENCES dbo.Dim_Artikel(ArtikelID),

    RegionID     INT           NOT NULL REFERENCES dbo.Dim_Region(RegionID),

    PRIMARY KEY (DatumID, KundeID, ArtikelID, RegionID),

    -- Additive Maßzahlen (können summiert werden):

    Umsatz_EUR         DECIMAL(14,2) NOT NULL,

    Menge              INT           NOT NULL,

    Kosten_EUR         DECIMAL(14,2) NOT NULL,

    Deckungsbeitrag_EUR DECIMAL(14,2) NOT NULL,

    -- Non-additive Maßzahlen (können NICHT summiert werden):

    Marge_Pct          DECIMAL(6,3)  NULL   -- DB/Umsatz — nie summieren!

    -- Columnstore Index für BI-Performance:

    INDEX CCI_Fact_Umsatz CLUSTERED COLUMNSTORE

);

 

 

Snowflake Schema — wann sinnvoll?

 

Das Snowflake Schema normalisiert die Dimensionstabellen weiter. Statt Kategorie, Unterkategorie direkt in Dim_Artikel werden separate Dim_Kategorie- und Dim_Unterkategorie-Tabellen angelegt.

 

  Vorteil: Weniger Redundanz in Dimensionstabellen, einfachere Stammdatenpflege.

 

  Nachteil: Mehr JOINs in BI-Abfragen, schlechtere Power-BI-Kompatibilität, höhere Abfragekosten.

 

  Empfehlung für KMU: Star Schema bevorzugen. Snowflake nur wenn Dimensionstabellen sehr groß (> 1 Mio. Zeilen) und Redundanz signifikant ist.

 


 

 

08

Data Vault als Mittelweg

 

Normalisierte Historisierung mit denormalisierter Auswertbarkeit

 

Data Vault ist eine Datenbankarchitektur, die die Stärken der Normalisierung (Flexibilität, Erweiterbarkeit, historische Korrektheit) mit der Auswertbarkeit des Star Schemas kombiniert.

 

Die drei Data-Vault-Bausteine

 

DATA VAULT: GRUNDSTRUKTUR

══════════════════════════════════════════════════════════════

HUB (normalisiert — Business Keys)

  Hub_Kunde:    HubKundeID, KundeBizKey, LoadDate, RecordSource

  Hub_Artikel:  HubArtikelID, ArtikelBizKey, LoadDate, RecordSource

 

LINK (normalisiert — Beziehungen zwischen Hubs)

  Link_Bestellung: LinkBestellID, HubKundeID, HubArtikelID,

                   BestellDatum, LoadDate, RecordSource

 

SATELLITE (historisiert — Attribute und Kontext)

  Sat_Kunde_Stamm:  HubKundeID, LoadDate, LoadEndDate,

                    KundeName, PLZ, Stadt, HashDiff

  Sat_Artikel_Preis: HubArtikelID, LoadDate, LoadEndDate,

                     Preis, Waehrung, HashDiff

 

→ INFORMATION MART (denormalisiert — Star Schema für BI)

  Fact_Umsatz, Dim_Kunde, Dim_Artikel: per ETL aus Data Vault

══════════════════════════════════════════════════════════════

 

 

Wann Data Vault sinnvoll ist

 

  Viele Quellsysteme (ERP + CRM + externe Daten): Data Vault integriert heterogene Quellen ohne Konflikte.

  Häufige Schemaänderungen: Neue Attribute werden als neue Satellites hinzugefügt — ohne bestehende Strukturen zu ändern.

  Strikte Auditierbarkeit: Jeder Datensatz enthält Ladedatum und Quelle — vollständige Nachvollziehbarkeit.

  Nicht geeignet: Kleine Umgebungen (< 5 Mio. Datensätze, 1–2 Quellsysteme) — Overhead überwiegt den Nutzen.

 


 

 

09

Moderne Optimierungen

 

Columnstore, Partitionierung und In-Memory OLTP — wann was hilft

 

Columnstore-Index als Denormalisierungsersatz

 

-- Columnstore auf normalisierter Tabelle: oft besser als Denormalisierung

-- SQL Server komprimiert Spalten einzeln → hohe Kompressionsrate

-- Batch Mode Processing: Aggregationen über Millionen Zeilen in Sekunden

 

-- Nicht-geclusterter Columnstore (NCI) — OLTP + Analyse gleichzeitig:

CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_Bestellzeilen

ON dbo.Bestellzeilen (ArtikelID, Menge, Einzelpreis, Bestelldatum);

-- OLTP-Operationen nutzen den Row-Store → kein Konflikt

-- BI-Abfragen nutzen den Columnstore → schnelle Aggregationen

 

-- Geclusterter Columnstore (CCI) — reine Analysetabellen:

CREATE CLUSTERED COLUMNSTORE INDEX CCI_Fact_Umsatz

ON dbo.Fact_Umsatz;

-- Alle Daten im Columnstore-Format → maximale Kompression und Leseperformance

-- INSERT/UPDATE/DELETE möglich, aber langsamer als Row-Store

 

 

Tabellenpartitionierung für historische Daten

 

-- Partitionierung: ältere Daten auf günstigeren Storage auslagern

-- Ohne Schema-Änderung — Abfragen laufen transparent

 

-- Partitionsfunktion: nach Jahr

CREATE PARTITION FUNCTION pf_Jahr (DATE)

AS RANGE RIGHT FOR VALUES

    ('2022-01-01','2023-01-01','2024-01-01','2025-01-01','2026-01-01');

 

-- Partitionsschema: Dateigruppen je Jahr

CREATE PARTITION SCHEME ps_Jahr

AS PARTITION pf_Jahr

TO (FG_2021_Alt, FG_2022, FG_2023, FG_2024, FG_2025, FG_2026_Aktuell);

 

-- Tabelle partitioniert anlegen:

CREATE TABLE dbo.Fact_Umsatz_Partitioniert (

    Bestelldatum  DATE          NOT NULL,

    KundeID       INT           NOT NULL,

    ArtikelID     INT           NOT NULL,

    Umsatz_EUR    DECIMAL(14,2) NOT NULL,

    INDEX CCI CLUSTERED COLUMNSTORE

) ON ps_Jahr (Bestelldatum);

 

-- Partition Elimination: Abfragen auf 2025 lesen nur FG_2025 → viel schneller

SELECT SUM(Umsatz_EUR) FROM dbo.Fact_Umsatz_Partitioniert

WHERE Bestelldatum >= '2025-01-01' AND Bestelldatum < '2026-01-01';

 

 

In-Memory OLTP (Hekaton) — für extrem schreiblastige OLTP-Tabellen

 

-- In-Memory-Tabelle: bis zu 30× schneller für hochfrequente Transaktionen

-- Sinnvoll für: Session-Tabellen, Warteschlangen, Echtzeit-Logging

 

-- Filegroup für In-Memory vorbereiten (einmalig je Datenbank):

ALTER DATABASE IhreDatenbank

ADD FILEGROUP FG_InMemory CONTAINS MEMORY_OPTIMIZED_DATA;

ALTER DATABASE IhreDatenbank

ADD FILE (NAME='InMemory', FILENAME='D:\InMemory\IhrDB_InMem')

TO FILEGROUP FG_InMemory;

 

-- In-Memory-Tabelle (Schema nur minimal — nicht für komplexe OLTP):

CREATE TABLE dbo.Auftrags_Queue (

    QueueID    BIGINT IDENTITY NOT NULL

               CONSTRAINT PK_Queue PRIMARY KEY NONCLUSTERED,

    ArtikelID  INT            NOT NULL,

    Menge      INT            NOT NULL,

    Eingang    DATETIME2      NOT NULL DEFAULT SYSDATETIME(),

    Status     TINYINT        NOT NULL DEFAULT 0

    INDEX IX_Status HASH (Status) WITH (BUCKET_COUNT = 1024)

) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);

-- DURABILITY = SCHEMA_ONLY: Daten nach Restart weg (für reine Puffer)

-- DURABILITY = SCHEMA_AND_DATA: Daten dauerhaft (mit Write-Ahead-Log)

 

 


 

 

10

30-Tage-Redesign-Plan

 

Bestehende Schemas analysieren und gezielt verbessern

 

Dieser Plan führt ein DBA-Team oder einen IT-Verantwortlichen in 30 Tagen durch eine vollständige Schema-Analyse eines bestehenden SQL-Server-Systems — von der Bestandsaufnahme über die Priorisierung bis zur ersten messbaren Verbesserung.

 

 

VOR DEM START

Erstellen Sie ein vollständiges Backup aller betroffenen Datenbanken. Aktivieren Sie den Query Store auf allen analysierten Datenbanken — er ist die wichtigste Messbasis für Vorher-Nachher-Vergleiche. Vereinbaren Sie mit den Fachbereichen: Schema-Redesign kann sich auf Applikationsverhalten auswirken — Tests in einer Staging-Umgebung sind Pflicht, nicht optional.

 

WOCHE 1: BESTANDSAUFNAHME & DIAGNOSE

 

■ TAG 1–2: SCHEMA-INVENTUR

 

  Alle Tabellen mit Zeilenanzahl, Spaltenanzahl und Größe inventarisieren

  Tabellen ohne Primärschlüssel identifizieren — kritisch, sofort melden

  Tabellen ohne Fremdschlüssel-Constraints identifizieren — Integritätsrisiko prüfen

  Tabellen mit > 50 Spalten identifizieren — Kandidaten für vertikale Partitionierung

 

-- Schema-Übersicht: alle Tabellen mit Größe und Zeilenanzahl

SELECT

    SCHEMA_NAME(t.schema_id)       AS Schema_Name,

    t.name                          AS Tabelle,

    p.rows                          AS Zeilen_Anzahl,

    SUM(a.total_pages) * 8 / 1024   AS Groesse_MB,

    COUNT(c.column_id)              AS Spalten_Anzahl,

    CASE WHEN pk.object_id IS NULL THEN 'KEIN PK!' ELSE 'OK' END AS PK_Status

FROM sys.tables t

JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0,1)

JOIN sys.allocation_units a ON p.partition_id = a.container_id

JOIN sys.columns c ON t.object_id = c.object_id

LEFT JOIN (

    SELECT i.object_id FROM sys.indexes i WHERE i.is_primary_key = 1

) pk ON t.object_id = pk.object_id

WHERE SCHEMA_NAME(t.schema_id) <> 'sys'

GROUP BY SCHEMA_NAME(t.schema_id), t.name, p.rows, pk.object_id

ORDER BY Groesse_MB DESC;

 

 

■ TAG 3–4: ANOMALIE-ANALYSE

 

  Update-Anomalie-Diagnose-Skript (Kapitel 4) auf Top-20-Tabellen ausführen

  Spalten mit kommaseparierten Werten manuell identifizieren (Kapitel 2, Falle 01)

  NULL-Nutzung analysieren: Spalten mit > 80 % NULL-Werten dokumentieren

  Fehlende FK-Constraints: Referenzielle Integrität manuell prüfen

 

■ TAG 5–7: ABFRAGEMUSTER ANALYSIEREN

 

  Top-20-Abfragen aus Query Store (DMV 1 aus Query-Kit) exportieren

  JOINs pro Abfrage zählen: Abfragen mit > 5 JOINs sind Denormalisierungskandidaten

  Häufig gemeinsam abgefragte Spalten identifizieren — Basis für Denorm-Entscheidung

  Abfragen mit hohen Logical Reads bei gleichzeitig hoher Ausführungsfrequenz priorisieren

 

WOCHE 2: NORMALISIERUNGSLÜCKEN SCHLIESSEN

 

■ TAG 8–10: KRITISCHE INTEGRITÄT HERSTELLEN

 

  Alle Tabellen ohne Primärschlüssel bekommen einen Surrogate-Key-PK (ArtikelID INT IDENTITY)

  Waisen-Datensätze bereinigen: referenzierte IDs ohne gültige Eltern-Zeile löschen oder korrigieren

  FK-Constraints für kritische Beziehungen (Bestellzeilen → Artikel, Bestellzeilen → Bestellkopf) anlegen

  NULL-Bedeutungen dokumentieren: Für jede nullable Spalte: Was bedeutet NULL hier?

 

■ TAG 11–13: ERSTE NORMALFORMEN ANWENDEN

 

  Kommaseparierte Spaltenwerte: Drei Kandidatentabellen zur Auflösung identifizieren und priorisieren

  Wiederholende Spaltengruppen (Telefon1/2/3): Auflösung in separate Entität planen

  Für den häufigsten Anomalie-Kandidaten: vollständige 3NF-Umstrukturierung in Staging-Umgebung

  Vorher-Nachher-Messung: Abfragezeit und Logical Reads vor und nach 3NF dokumentieren

 

■ TAG 14: UPDATE-ANOMALIE-TEST

 

  In Staging-Umgebung: Update-Anomalie provozieren (Adresse eines Kunden in OLTP-Roh-Tabelle ändern)

  Zeigen: wie viele Zeilen müssen aktualisiert werden? Wie viele sind nach Update inkonsistent?

  Danach 3NF-Version: Update der Adresse in einer Zeile — alle Referenzen automatisch korrekt

  Ergebnis als Management-Summary aufbereiten: "Das ist, was uns die fehlende Normalisierung kostet"

 

WOCHE 3: DENORMALISIERUNG FÜR ANALYTISCHE ABFRAGEN

 

■ TAG 15–17: REPORTING-LAYER AUFBAUEN

 

  Top-5 teuersten Report-Abfragen (aus Woche 1 Analyse) identifizieren

  Für jede: JOIN-Kette analysieren — welche Tabellen werden immer gemeinsam gelesen?

  Flache Reporting-Tabelle (Strategie 2, Kapitel 6) für die häufigste Abfragedomäne entwerfen

  ETL-Job erstellen: tägliche Befüllung der Reporting-Tabelle nach Geschäftsschluss

 

■ TAG 18–20: STAR SCHEMA FÜR BI

 

  Falls Power BI im Einsatz: Star-Schema-Design (Kapitel 7) für die wichtigste Auswertungsdomäne

  Fact-Tabelle und 3–4 Dimensionen in Staging-Umgebung anlegen und befüllen

  Power BI auf Star Schema umstellen: Measures, Beziehungen, RLS prüfen

  Performance-Messung: Dashboard-Ladezeit vorher vs. nachher dokumentieren

 

■ TAG 21: COLUMNSTORE EINFÜHREN

 

  Alle reinen Analyse-/Reporting-Tabellen auf Clustered Columnstore umstellen

  Für OLTP-Tabellen mit Analysebedarf: Non-Clustered Columnstore Index testen

  Performance-Vergleich: identische Abfrage mit und ohne Columnstore

  Typisches Ergebnis dokumentieren und mit Team teilen

 

WOCHE 4: STABILISIERUNG & DOKUMENTATION

 

■ TAG 22–25: HISTORISIERUNG EINRICHTEN

 

  Kritische veränderliche Stammdaten identifizieren: Kunden-Adressen, Preise, Artikel-Stamm

  SCD2-Historisierung (Kapitel 2, Falle 09) für die wichtigste Stammdatentabelle einrichten

  SQL Server Temporal Tables als Alternative zu manueller SCD2 evaluieren

  Temporal Table für eine Pilot-Tabelle einrichten:

 

-- SQL Server Temporal Table: automatische Historisierung

ALTER TABLE dbo.Kunden

ADD

    GueltigVon  DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,

    GueltigBis  DATETIME2 GENERATED ALWAYS AS ROW END   NOT NULL,

    PERIOD FOR SYSTEM_TIME (GueltigVon, GueltigBis);

 

ALTER TABLE dbo.Kunden

SET (SYSTEM_VERSIONING = ON (

    HISTORY_TABLE = dbo.Kunden_Historie

));

-- Ab jetzt: SQL Server schreibt automatisch jede Änderung in Kunden_Historie

-- Zeitreisende Abfrage:

SELECT * FROM dbo.Kunden

FOR SYSTEM_TIME AS OF '2024-06-15 00:00:00';

 

 

■ TAG 26–28: KONSISTENZMECHANISMEN ABSICHERN

 

  Alle denormalisierten Spalten und Tabellen auf Konsistenz prüfen: Gibt es Trigger oder Constraints?

  Fehlende Konsistenz-Sicherung nachziehen: PERSISTED Computed Columns oder Indexed Views

  Dokumentation aller Denormalisierungsentscheidungen: Warum, was, Konsistenzmechanismus, Review-Termin

  Schema-Dokumentation im Wiki aktualisieren: Lineage-Dokumente (Lineage-Kit) für neue Tabellen anlegen

 

■ TAG 29–30: ABSCHLUSS & MESSUNG

 

  Vorher-Nachher-Vergleich: Top-5-Abfragen Baseline vs. aktuell — Verbesserungsfaktor je Abfrage

  Schema-Qualitäts-KPIs dokumentieren: Anteil Tabellen mit PK, FK-Abdeckung, Anomalie-Score

  Offene Punkte in Backlog priorisieren: Welche weiteren Tabellen werden als nächstes normalisiert?

  Ergebnisse präsentieren: Zahlen, Fakten, Messbarkeit — der ROI des Schema-Redesigns ■

 

 

ERGEBNIS NACH 30 TAGEN

Ihr Ergebnis nach 30 Tagen: Vollständige Schema-Inventur mit dokumentierten Normalisierungslücken, behobene kritische Integritätsprobleme (fehlende PKs, FKs), eine denormalisierte Reporting-Tabelle mit messbarer Performance-Verbesserung, ein Star-Schema-Entwurf für die wichtigste BI-Domäne, Columnstore-Indizes auf Analyse-Tabellen — und eine dokumentierte Grundlage für alle weiteren Schema-Entscheidungen.

 


 

 

Ausführlicher Haftungsausschluss und Lizenzbestimmungen

 

1. Allgemeiner Haftungsausschluss

 

Die in diesem Dokument enthaltenen SQL-Skripte, Designmethoden und Architekturempfehlungen wurden nach bestem Wissen und Gewissen auf Basis langjähriger praktischer Erfahrung in der Datenbankarchitektur und -entwicklung erstellt. Dennoch kann keine Gewähr für Vollständigkeit, Richtigkeit oder universelle Anwendbarkeit übernommen werden.

 

Der Autor übernimmt ausdrücklich keinerlei Haftung für Schäden, die aus der Anwendung der beschriebenen Methoden und Skripte entstehen, einschließlich, aber nicht beschränkt auf: Datenverlust, Anwendungsfehler, Performance-Regression oder Systemausfälle. Schema-Änderungen an Produktionsdatenbanken sind stets mit vollständigem Backup und ausreichendem Test in einer Nicht-Produktionsumgebung vorzubereiten.

 

2. Versionsspezifische Hinweise

 

Features wie In-Memory OLTP (Hekaton), Clustered Columnstore Index, Temporal Tables, JSON-Unterstützung und PERSISTED Computed Columns sind teilweise versionsspezifisch. Prüfen Sie vor dem Einsatz die Verfügbarkeit für Ihre SQL-Server-Version. Alle Skripte wurden auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x) getestet.

 

3. Normalisierungstheorie und Praxis

 

Die in Kapitel 3 beschriebenen Normalformen basieren auf dem relationalen Datenbankmodell nach E. F. Codd und den ISO/IEC-9075-Standards. Die in diesem Kit beschriebenen Vereinfachungen und Praxisempfehlungen weichen in Details von der akademischen Theorie ab — bewusst, um die Anwendbarkeit im KMU-Kontext zu maximieren.

 

4. Urheberrecht und Nutzungsrechte

 

Dieses Dokument und alle Inhalte sind urheberrechtlich geschützt. © 2026 Sascha Hess, xenosystems.de. Alle Rechte vorbehalten.

 

5. Markenrechte

 

SQL Server, Power BI und Azure Synapse Analytics sind eingetragene Marken der Microsoft Corporation. Data Vault ist eine Methodik von Dan Linstedt. Alle anderen genannten Produkt- und Unternehmensnamen sind Eigentum ihrer jeweiligen Inhaber.

 

6. Anwendbares Recht und Gerichtsstand

 

Es gilt ausschließlich deutsches Recht. Gerichtsstand für alle Streitigkeiten ist, soweit gesetzlich zulässig, Weimar, Thüringen, Deutschland.

 


 

 

Über den Autor

 

Sascha Hess ist Diplom-Biologe und IT-Professional mit über 20 Jahren Erfahrung in der Administration und Architektur von SQL-Server-Systemen, ERP- und BI-Datenbanken. Er hat mehr als 50 Datenbankschemas analysiert, normalisiert und für analytische Workloads optimiert — von historisch gewachsenen Chaos-Schemas bis zu vollständig modellierten Data-Vault-Architekturen.

 

Sein Ansatz verbindet naturwissenschaftliche Präzision mit hochgradiger IT-Spezialisierung. Schwerpunkte: SQL Server Performance-Tuning, ERP-Einführungen, Business Intelligence (Power BI, DeltaMaster), Datenbankdesign, Data Governance und IT-Interim-Management.

 

Web: www.xenosystems.de | E-Mail: info@xenosystems.de | Standort: Weimar, Thüringen / Remote

 

Service

Beschreibung

Schema-Analyse & Redesign

Vollständige Analyse Ihres Datenbankschemas: Normalisierungslücken, Anomalien, Integritätsprobleme, Performance-Schwachstellen — mit priorisiertem Redesign-Plan und umsetzbaren Quick Wins. Scope: 2–4 Tage.

DWH-Design & Star Schema

Entwurf und Implementierung eines Star-Schema-Data-Warehouse auf SQL Server — Faktentabellen, Dimensionen, ETL-Prozesse, Columnstore-Indizes und Power-BI-Integration.

SQL Server DB Health Check

Professioneller Audit Ihres SQL Servers — Managementreport mit Performance-Analyse, Schema-Bewertung, Indexstrategie und priorisiertem Maßnahmenplan. Scope: 3–5 Tage.

Query-Tuning Session

1-Tages-Session: DMV-Diagnose, Execution-Plan-Analyse, Rewrite-Empfehlungen und Index-Strategie für Ihre Top-Performance-Probleme.

Interim IT-Management

Übernahme der IT-Steuerung auf Zeit — Budgetplanung, Dienstleister-Management, strategische IT-Ausrichtung.

 

Vollständiges Dokument

kit normalisierung denormalisierung 2026

Strategische Wissens-Roadmap · Checklisten · Praxisbeispiele

49,90 €

Sichere Zahlung über PayPal · Sofort-Download nach Zahlungseingang