Domain-Typen für wiederverwendbare Constraints
AI generated
SELECT
JOIN
SQL · Domain-Typen · Constraints
Domain-Typen für wiederverwendbare Constraints
Validierungslogik einmal definieren, überall im Schema nutzen

Ein Domain-Typ bündelt einen Basisdatentyp mit einem oder mehreren Constraints zu einem eigenen, benannten Typ, den beliebig viele Tabellen und Spalten wiederverwenden können. Dieser Beitrag zeigt CREATE DOMAIN und ALTER DOMAIN im Detail, praktische Beispiele für E-Mail-Adressen und positive Beträge, Migrationen bestehender Spalten und die Grenzen dieses Ansatzes gegenüber Constraints pro Spalte.

17 Min. Lesezeit CREATE DOMAIN · ALTER DOMAIN PostgreSQL · Standard-SQL

1. Was ein Domain-Typ ist und welches Problem er löst

Ein Domain-Typ ist ein benutzerdefinierter Datentyp, der auf einem bestehenden Basisdatentyp aufbaut und diesen um zusätzliche Regeln wie NOT NULL, einen Default-Wert und beliebig viele CHECK-Bedingungen erweitert. Sobald ein Domain-Typ definiert ist, kann er wie jeder eingebaute Datentyp für Spalten in beliebig vielen Tabellen verwendet werden, wobei alle daran gebundenen Regeln automatisch mitgelten, ohne dass sie an jeder einzelnen Stelle erneut ausgeschrieben werden müssen.

Das Problem, das ein Domain-Typ löst, ist die Duplikation identischer Constraints über viele Spalten und Tabellen hinweg. Ein Feld für eine E-Mail-Adresse taucht in der Praxis oft in mehreren Tabellen auf, etwa bei Kunden, Mitarbeitern und Lieferanten, und jede dieser Spalten braucht dieselbe Regex-Prüfung für ein gültiges Format. Ohne Domain-Typ muss dieselbe CHECK-Bedingung in jeder einzelnen Tabellendefinition wiederholt werden, mit dem Risiko, dass eine spätere Änderung der Regel nur an einigen, nicht an allen Stellen nachgezogen wird.

Ein Domain-Typ zentralisiert diese Regel an einer einzigen Stelle im Schema. Eine spätere Anpassung der Validierungslogik über ALTER DOMAIN wirkt sich automatisch auf jede Spalte aus, die diesen Domain-Typ verwendet, was Konsistenz garantiert, die bei einer manuell duplizierten CHECK-Bedingung pro Spalte nicht erreichbar wäre.

2. CREATE DOMAIN im Detail

Die Syntax CREATE DOMAIN domain_name AS basistyp definiert einen neuen Domain-Typ, der sich anschließend genauso wie ein eingebauter Typ in einer Spaltendefinition verwenden lässt. Der Basistyp kann jeder eingebaute Datentyp sein, etwa NUMERIC, VARCHAR oder INTEGER, und der Domain-Typ erbt dessen grundlegendes Verhalten vollständig, ergänzt um die zusätzlichen Regeln, die bei der Definition festgelegt werden.

Ein einfacher Domain-Typ für einen positiven Geldbetrag zeigt das Grundprinzip: NUMERIC als Basistyp, ergänzt um NOT NULL und eine CHECK-Bedingung, die negative Werte ablehnt. Sobald dieser Domain-Typ einmal definiert ist, kann jede Spalte in jeder Tabelle, die einen positiven Betrag benötigt, positive_amount statt NUMERIC(12,2) NOT NULL CHECK (VALUE >= 0) verwenden, was das Schema deutlich lesbarer und wartbarer macht.


-- PostgreSQL: a reusable domain type for monetary amounts
CREATE DOMAIN positive_amount AS NUMERIC(12,2)
    NOT NULL
    DEFAULT 0.00
    CHECK (VALUE >= 0);

-- Use the domain type exactly like a built-in type in any table
CREATE TABLE invoice (
    invoice_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    net_amount positive_amount,
    tax_amount positive_amount
);

CREATE TABLE refund (
    refund_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    -- same validation rule, reused without duplicating the CHECK expression
    refund_amount positive_amount
);

3. Domain-Typen mit Check Constraints kombinieren

Innerhalb der CHECK-Bedingung eines Domain-Typs bezieht sich das Schlüsselwort VALUE auf den Wert, der gerade validiert wird, analog zu NEW in einem Trigger. Diese Bedingung kann beliebig komplex sein, etwa ein Regex-Muster für ein E-Mail-Format oder eine Kombination aus Längenprüfung und Zeichensatzvalidierung für eine IBAN. Ein Domain-Typ kann außerdem mehrere benannte CHECK-Constraints gleichzeitig tragen, was die spätere gezielte Fehlerbehandlung erleichtert, weil jede Regel einen eigenen, sprechenden Namen bekommt.

Ein praktisches Beispiel ist ein Domain-Typ für eine normalisierte E-Mail-Adresse, der gleichzeitig ein grobes Formatmuster prüft und die maximale Länge nach RFC 5321 durchsetzt. Diese Kombination aus mehreren Regeln in einem einzigen, wiederverwendbaren Typ wäre bei einer Wiederholung über zehn verschiedene Tabellen ein erheblicher Wartungsaufwand, wenn jede Regel separat als CHECK-Constraint pro Spalte formuliert würde.


-- PostgreSQL: a domain type bundling two named rules for email columns
CREATE DOMAIN email_address AS VARCHAR(254)
    CONSTRAINT email_not_empty CHECK (length(trim(VALUE)) > 0)
    CONSTRAINT email_format CHECK (
        VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
    );

CREATE TABLE customer (
    customer_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email       email_address NOT NULL
);

CREATE TABLE supplier (
    supplier_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    -- identical validation reused, no copy-pasted regex
    contact_email email_address
);

4. Domain-Typ vs. eigene Funktion: der konzeptionelle Unterschied

Ein häufiges Missverständnis ist, einen Domain-Typ mit einer IMMUTABLE-Funktion zu verwechseln, die in einem CHECK Constraint aufgerufen wird. Der entscheidende Unterschied liegt in der Verantwortlichkeit: eine Funktion muss in jeder Tabelle explizit in einem eigenen CHECK Constraint referenziert werden, während ein Domain-Typ die Validierung an den Datentyp selbst bindet. Eine Spalte, die als email_address deklariert ist, kann fachlich niemals einen ungültigen Wert enthalten, weil die Prüfung untrennbar mit dem Typ verknüpft ist, nicht mit einer separat gepflegten Bedingung.

Dieser Unterschied wirkt sich auch auf die Lesbarkeit des Schemas aus: eine Spaltendefinition mit einem Domain-Typ zeigt die fachliche Bedeutung direkt im Typnamen, während eine Funktion in einem CHECK Constraint erst nachgeschlagen werden muss, um die Bedeutung zu verstehen. Für häufig wiederkehrende, einfache Validierungsmuster ist ein Domain-Typ deshalb die klarere und selbstdokumentierendere Wahl.

5. Validierungslogik zentral ändern mit Alter Domain

Der größte praktische Vorteil eines Domain-Typs zeigt sich, wenn sich eine fachliche Regel später ändert. Mit ALTER DOMAIN domain_name ADD CONSTRAINT lässt sich eine neue Regel zu einem bereits existierenden Domain-Typ hinzufügen, die dann automatisch für jede Spalte gilt, die diesen Typ verwendet, unabhängig davon, in wie vielen Tabellen er bereits eingesetzt wird. Eine Änderung an einer einzigen Stelle im Schema propagiert sich damit konsistent über das gesamte Datenmodell.

Wichtig dabei: genau wie beim nachträglichen Hinzufügen eines regulären CHECK Constraints scheitert ALTER DOMAIN ADD CONSTRAINT sofort, wenn auch nur ein einziger bestehender Wert in irgendeiner Spalte, die diesen Domain-Typ nutzt, die neue Regel verletzt. Vor einer solchen Änderung sollte deshalb systematisch geprüft werden, ob alle betroffenen Spalten in allen betroffenen Tabellen die neue Regel bereits erfüllen.


-- PostgreSQL: tighten validation for all columns using this domain at once
ALTER DOMAIN email_address
    ADD CONSTRAINT email_no_plus_alias
    CHECK (VALUE !~ '\+[^@]*@');

-- Check for violations across every table using the domain type first,
-- querying the system catalog to find them
SELECT c.relname AS table_name, a.attname AS column_name
FROM pg_attribute a
JOIN pg_class c ON c.oid = a.attrelid
JOIN pg_type t ON t.oid = a.atttypid
WHERE t.typname = 'email_address'
  AND a.attnum > 0;

6. Null-Handling und Default-Werte bei Domain-Typen

Ein Domain-Typ kann eine eigene NOT NULL-Regel und einen eigenen Default-Wert tragen, die beide beim Anlegen einer Spalte mit diesem Typ automatisch greifen, sofern die Spaltendefinition selbst keine abweichende Regel setzt. Eine explizite NOT NULL- oder DEFAULT-Angabe direkt in der Spaltendefinition einer Tabelle überschreibt dabei die Vorgabe des Domain-Typs für diese eine Spalte, was in seltenen Ausnahmefällen nützlich sein kann, aber die Konsistenzgarantie des Domain-Typs für diese Spalte lokal aufweicht.

Innerhalb der CHECK-Bedingung eines Domain-Typs gilt dieselbe Drei-Wert-Logik wie bei einem regulären CHECK Constraint: eine Bedingung, die auf einen NULL-Wert trifft, ergibt unbekannt und wird deshalb nicht als Verletzung gewertet. Ein Domain-Typ ohne explizites NOT NULL erlaubt also weiterhin NULL-Werte, selbst wenn die CHECK-Bedingung für jeden konkreten Wert eine strenge Regel durchsetzt, was bei der Definition oft übersehen wird.

7. Bestehende Spalten auf einen Domain-Typ migrieren

Eine bestehende Spalte, die bisher direkt als VARCHAR oder NUMERIC mit einem individuellen CHECK Constraint definiert war, lässt sich nachträglich auf einen Domain-Typ umstellen, um die Regel zu zentralisieren. Der Umstieg erfordert ein ALTER TABLE ... ALTER COLUMN ... TYPE, das den neuen Domain-Typ als Zieltyp angibt, wobei PostgreSQL automatisch prüft, ob alle bestehenden Werte in der Spalte die Regeln des neuen Domain-Typs erfüllen.

Bei dieser Migration ist es sinnvoll, den ursprünglichen, individuellen CHECK Constraint der Spalte erst nach erfolgreicher Umstellung zu entfernen, um Doppelprüfungen während der Übergangszeit zu vermeiden, aber auch keine Regel versehentlich vorzeitig zu verlieren. Ein systematischer Test in einer Staging-Umgebung mit einer Kopie der Produktionsdaten deckt zuverlässig auf, ob unerwartete Bestandsdaten die neue, zentralisierte Regel verletzen.


-- Existing column with an ad-hoc CHECK constraint
CREATE TABLE legacy_contact (
    contact_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email      VARCHAR(254) NOT NULL,
    CONSTRAINT chk_legacy_email_format
        CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
);

-- Migrate the column to the centralized domain type
ALTER TABLE legacy_contact
    ALTER COLUMN email TYPE email_address;

-- Drop the now-redundant per-column constraint after validation succeeds
ALTER TABLE legacy_contact
    DROP CONSTRAINT chk_legacy_email_format;

8. Vendor-Support: PostgreSQL, MySQL, Oracle

PostgreSQL bietet die vollständigste Implementierung von Domain-Typen unter den verbreiteten relationalen Datenbanken, mit CREATE DOMAIN, ALTER DOMAIN und voller Integration in den Systemkatalog. MySQL kennt das Konzept CREATE DOMAIN überhaupt nicht, es gibt keinen direkten Ersatz, der dieselbe zentrale Wiederverwendbarkeit über beliebig viele Tabellen bietet. Als Notbehelf bleibt in MySQL nur eine dokumentierte Namenskonvention für CHECK-Constraints, kombiniert mit generierten Migrationsskripten, die dieselbe Bedingung konsistent in mehreren Tabellen anlegen.

Oracle bietet mit Subtypes in PL/SQL ein verwandtes, aber deutlich eingeschränkteres Konzept, das primär für Variablen und Parameter in gespeicherten Prozeduren gedacht ist, nicht für Tabellenspalten selbst. Wer ein Schema plattformunabhängig zwischen PostgreSQL und MySQL portieren muss, sollte deshalb von Anfang an eine Fallback-Strategie einplanen, etwa ein Codegenerator, der aus einer zentralen YAML- oder JSON-Definition heraus die passenden CHECK-Constraints für jede Zieldatenbank erzeugt.

Datenbank Domain-Typen Alternative
PostgreSQL Voll unterstützt CREATE DOMAIN, ALTER DOMAIN
MySQL Nicht unterstützt Namenskonvention plus generierte CHECK-Constraints
Oracle Eingeschränkt (PL/SQL Subtypes) Nur für Variablen, nicht für Tabellenspalten
SQL Server Unterstützt (User-Defined Types) CREATE TYPE mit ähnlichem Funktionsumfang

9. Domain-Typ vs. Check Constraint pro Spalte vs. Funktion

Die Wahl zwischen einem Domain-Typ, einem individuellen CHECK Constraint pro Spalte und einer IMMUTABLE-Funktion hängt davon ab, wie oft dieselbe Regel im Schema wiederverwendet wird, und ob die Regel eher als eigenständiger Typ oder als aufrufbare Logik gedacht ist. Ein Domain-Typ lohnt sich ab der zweiten oder dritten Wiederverwendung derselben Regel über verschiedene Tabellen hinweg.

Mironsoft

Datenmodellierung, Schema-Design und Datenbankberatung

Validierungslogik zentral statt über zehn Tabellen verstreut?

Wir entwerfen Domain-Typen für wiederkehrende Validierungsmuster und planen Migrationen bestehender Spalten auf zentrale Typen, ohne Bestandsdaten zu gefährden.

Domain-Design

Wiederkehrende Constraints als zentrale, benannte Typen bündeln

Schema-Migration

Bestehende Spalten sicher auf zentrale Domain-Typen umstellen

Portabilität

Fallback-Strategien für Datenbanken ohne Domain-Typ-Unterstützung

10. Zusammenfassung

Ein Domain-Typ bündelt einen Basisdatentyp mit NOT NULL, Default-Werten und beliebig vielen CHECK-Bedingungen zu einem eigenen, wiederverwendbaren Typ, der in beliebig vielen Tabellen und Spalten genutzt werden kann. CREATE DOMAIN definiert den Typ einmalig, ALTER DOMAIN erlaubt spätere zentrale Anpassungen, die sich automatisch auf jede verwendende Spalte auswirken, ohne dass die Regel an jeder Stelle einzeln geändert werden muss.

PostgreSQL bietet die vollständigste Unterstützung für Domain-Typen, MySQL kennt das Konzept nicht und erfordert einen Ersatz über Namenskonventionen und Codegeneratoren. Ab der zweiten oder dritten Wiederverwendung derselben Regel über verschiedene Tabellen hinweg lohnt sich ein Domain-Typ gegenüber einem individuellen CHECK Constraint pro Spalte, weil er Konsistenz garantiert und die spätere Wartung erheblich vereinfacht.

Domain-Typen für wiederverwendbare Constraints, das Wichtigste auf einen Blick

CREATE DOMAIN

Bündelt Basistyp, NOT NULL, Default und CHECK-Bedingungen zu einem wiederverwendbaren Typ.

ALTER DOMAIN

Zentrale Regeländerung wirkt automatisch auf jede Spalte, die den Domain-Typ nutzt.

Migration

Bestehende Spalten per ALTER TABLE auf einen zentralen Domain-Typ umstellen, alte Constraints danach entfernen.

Vendor-Grenzen

Nur PostgreSQL und SQL Server bieten volle Unterstützung, MySQL benötigt eine Namenskonvention als Ersatz.

11. FAQ: Domain-Typen für wiederverwendbare Constraints

1Was ist ein Domain-Typ in SQL?
Ein benutzerdefinierter Typ, der einen Basistyp um NOT NULL, Default und CHECK-Bedingungen erweitert und über viele Tabellen wiederverwendbar ist.
2Domain-Typ vs. Check Constraint pro Spalte?
Ein Domain-Typ definiert die Regel einmal zentral, ein Check Constraint pro Spalte muss überall einzeln wiederholt werden.
3Wie ändere ich die Regel eines Domain-Typs?
Mit ALTER DOMAIN ADD CONSTRAINT, die neue Regel gilt sofort für alle Spalten, die den Typ verwenden.
4Was bedeutet VALUE in der Domain-Bedingung?
Der konkrete Wert, der gerade geprüft wird, vergleichbar mit NEW in einem Trigger.
5Kann ein Domain-Typ mehrere Constraints tragen?
Ja, beliebig viele benannte CHECK-Constraints gleichzeitig, jeweils mit eigenem Namen.
6Unterstützt MySQL Domain-Typen?
Nein, nur Namenskonventionen und generierte, konsistente CHECK-Constraints als Ersatz.
7Wie migriere ich eine Spalte auf einen Domain-Typ?
Mit ALTER TABLE ALTER COLUMN TYPE, PostgreSQL prüft automatisch bestehende Werte gegen die neuen Regeln.
8Erlaubt ein Domain-Typ ohne NOT NULL trotzdem NULL?
Ja, eine Bedingung mit NULL ergibt unbekannt und wird nicht als Verletzung gewertet.
9Wann Domain-Typ statt eigener Funktion?
Wenn die Regel für einen ganzen Datentyp gelten und direkt im Spaltentyp sichtbar sein soll.
10Bietet Oracle ein Äquivalent?
Nur eingeschränkt über PL/SQL Subtypes, primär für Variablen, nicht für Tabellenspalten.