Wer Datenbanken wechselt, mehrere Systeme parallel betreibt oder ein gemeinsames Datenmodell für unterschiedliche SQL-Engines definieren muss, stößt schnell auf Unterschiede bei Datentypen, die in der Praxis zu fehlerhaften Ergebnissen führen können: Dezimalwerte, die unerwartet gerundet werden, Zeitstempel, die Zeitzonen anders behandeln als erwartet, oder Text- und Binärfelder, deren Indexierbarkeit sich je nach System und Kollation unterscheidet. Auch scheinbar identische Typnamen wie INTEGER, DATETIME oder NUMERIC meinen nicht zwingend dasselbe, und manche Systeme setzen stärker auf Typaffinität oder implizite Konvertierungen als andere. Relevant wird das besonders bei Migrationen, Replikation, ETL-Prozessen, API-Verträgen sowie beim Einsatz von ORM-Frameworks, die je nach Dialekt unterschiedliche Standardtypen wählen. Aus Sicht der Leser steht damit eine konkrete Frage im Raum: Welche Datentypen sind zwischen MySQL, PostgreSQL, SQL Server und SQLite tatsächlich kompatibel, wo liegen Grenzen bei Wertebereich und Genauigkeit, und welche Unterschiede beeinflussen Speicherung, Indizes, Performance und das Verhalten von Anwendungen?

Inhalt
- Zahlen-, Boolean- und Identifier-Typen im Direktvergleich (Wertebereich, Genauigkeit, Auto-Increment/Identity, Indexverhalten)
- Zeichen-, Binär- und JSON/UUID-Typen: Speicherung, Kollationen, Längenregeln, Indexierbarkeit und Sonderformen
- Zeichenketten: CHAR/VARCHAR/TEXT, NVARCHAR und die Rolle von Kollationen
- Längenregeln: Zeichen vs. Bytes, Trunkierung und Semantik von Leerzeichen
- Binärtypen: BINARY/VARBINARY, BYTEA/BLOB und Auswirkungen auf Indizes
- JSON: nativer Typ, Textspeicherung und Index-Strategien
- UUID/GUID: nativer Typ vs. String/Binary und Portierungsregeln
- Datum/Zeit, Intervall und Typkonvertierung: Zeitzonen, Rundungsregeln, CAST/CONVERT-Matrizen und Auswirkungen auf ORMs
Zahlen-, Boolean- und Identifier-Typen im Direktvergleich (Wertebereich, Genauigkeit, Auto-Increment/Identity, Indexverhalten)
Zahlen-, Boolean- und Identifier-Typen wirken auf den ersten Blick austauschbar, unterscheiden sich plattformübergreifend jedoch in Details, die Datenqualität, Abfragepläne und Migrationsrisiken unmittelbar beeinflussen. Besonders relevant sind feste versus variable Speicherung, die exakte Abbildung von Dezimalwerten, die Semantik von BOOLEAN sowie die Mechanik hinter Auto-Increment/Identity und deren Interaktion mit Transaktionen, Replikation und ORMs.
Ganzzahlen: Wertebereich, Speicherbedarf, Identity/Autoincrement
Bei Ganzzahlen ist die Lage relativ konsistent: MySQL, PostgreSQL und SQL Server bieten abgestufte Integer-Typen mit festem Speicherbedarf (1/2/4/8 Byte). SQLite besitzt zwar Typnamen wie INTEGER, SMALLINT oder TINYINT, erzwingt aber aufgrund des Dynamik-Typsystems nicht dieselben Wertebereiche oder Speichergrößen; entscheidend ist die „Storage Class“ INTEGER (bis zu 8 Byte), während die tatsächliche On-Disk-Repräsentation je nach Wertgröße variieren kann.
Auto-Increment/Identity ist in der Praxis die wichtigste Differenz: In MySQL ist AUTO_INCREMENT ein Spaltenattribut (typisch auf einer Integer-Spalte, häufig Primärschlüssel). In PostgreSQL wird die Identität über GENERATED ... AS IDENTITY oder historisch über serial-Typen modelliert (Sequenzen als eigenständige Objekte). SQL Server nutzt IDENTITY als Spalteneigenschaft. SQLite koppelt „rowid“-Semantik an INTEGER PRIMARY KEY; nur INTEGER PRIMARY KEY ist ein Alias auf den internen rowid und bestimmt damit das Verhalten bei automatisch vergebenen Schlüsseln.
| Kategorie | MySQL | PostgreSQL | SQL Server | SQLite |
|---|---|---|---|---|
| 8‑Bit Integer | TINYINT (1 Byte, −128..127; mit UNSIGNED 0..255) |
smallint (2 Byte; kein 1‑Byte‑Integer) |
tinyint (1 Byte, 0..255) |
Typname möglich, aber Affinity; Werte als INTEGER‑Storage bis 64‑Bit |
| 16‑Bit Integer | SMALLINT (2 Byte) |
smallint (2 Byte) |
smallint (2 Byte) |
Affinity, kein erzwungener Bereich |
| 32‑Bit Integer | INT/INTEGER (4 Byte) |
integer (4 Byte) |
int (4 Byte) |
Affinity, Storage als 1–8 Byte je nach Wert |
| 64‑Bit Integer | BIGINT (8 Byte) |
bigint (8 Byte) |
bigint (8 Byte) |
INTEGER Storage ist 64‑Bit signed (−263..263−1) |
| Auto-Increment/Identity | AUTO_INCREMENT auf Integer-Spalten (typisch Primärschlüssel) |
GENERATED BY DEFAULT/ALWAYS AS IDENTITY (Sequenz im Hintergrund); alternativ serial/bigserial |
IDENTITY(seed, increment); Reihenfolgenlücken möglich |
INTEGER PRIMARY KEY nutzt rowid; optional AUTOINCREMENT (verhindert Wiederverwendung alter Rowids, aber nicht „lückenlos“) |
Dezimal- und Gleitkommazahlen: Genauigkeit, Rundung, Vergleichssemantik
Für Geldbeträge und andere exakt zu speichernde Dezimalwerte sind DECIMAL/NUMERIC relevant. MySQL implementiert DECIMAL(p,s) als exakten Dezimaltyp mit definierter Präzision und Skala; PostgreSQL bietet numeric mit frei wählbarer Präzision (und ohne Angabe als „unbounded“, praktisch durch Ressourcen begrenzt). SQL Server verwendet decimal/numeric mit Präzision bis 38 Stellen. SQLite besitzt keinen nativen Dezimaltyp; NUMERIC-Affinity führt je nach Inhalt zu INTEGER- oder REAL-Storage oder bleibt TEXT, was bei Importen und ORMs zu stillen Typwechseln führen kann.
Bei Gleitkomma (FLOAT/REAL/DOUBLE) dominiert IEEE-754, aber die Typnamen sind nicht äquivalent: PostgreSQL real ist 32‑Bit, double precision ist 64‑Bit; SQL Server float(n) ist abhängig von n (Standard ist float(53)), während real 32‑Bit bedeutet. MySQLs FLOAT/DOUBLE entsprechen 32/64‑Bit; optionale Präzisionsangaben in Klammern (FLOAT(M,D)) sind nicht portabel und sollten bei plattformübergreifenden Schemata vermieden werden. Rundungsregeln wirken vor allem bei Casts und bei Ausdrücken mit gemischten Typen: Exakte Dezimalwerte bleiben in den Systemen mit echtem Dezimaltyp stabiler, während Float-Vergleiche aufgrund binärer Repräsentation grundsätzlich mit Toleranzen geplant werden müssen.
- Portabler Standard für Geld/Abrechnung:
DECIMAL/NUMERICmit expliziten(p,s); bei SQL Server Präzision bis 38; bei PostgreSQL ohne Angabe nur verwenden, wenn „unbounded“ bewusst akzeptiert wird. - Float nur für Messwerte/Approximation: bei Vergleichen statt
=typischerweise Bereichsprüfung (z. B. „zwischen“), weil IEEE‑754 Rundungsfehler in allen vier Systemen auftreten. - SQLite-Sonderfall:
NUMERIC-Affinity garantiert keine Dezimalarithmetik; exakte Dezimalwerte erfordern häufig Speicherung als Integer (z. B. Cents) oder als Text mit eigener Validierung.
BOOLEAN: Typ, zulässige Literale und Indexverhalten
PostgreSQL besitzt einen echten boolean-Typ mit den Literalen TRUE/FALSE (und diversen akzeptierten Eingabeformen). SQL Server modelliert Boolean typischerweise über bit; dabei sind 0, 1 und NULL zulässig, und Aggregationen/Default-Definitionen unterscheiden sich syntaktisch vom SQL-Standard. MySQL unterstützt BOOL/BOOLEAN als Synonym für TINYINT(1); damit ist die physische Speicherung ein Integer, und „wahr“ ist in Ausdrücken grundsätzlich jeder von 0 verschiedene Wert (sofern keine Check-Constraints die Domäne einschränken). SQLite kennt keinen nativen Boolean; häufig werden INTEGER (0/1) oder TEXT verwendet, und ORMs müssen konsequent mappen.
Indexseitig sind Boolean-Spalten oft selektiv schwach. In PostgreSQL und SQL Server kann der Optimizer bei extrem niedriger Selektivität Scans bevorzugen; gezielte Partial-/Filtered-Indexes sind plattformspezifisch: PostgreSQL bietet partielle Indizes mit WHERE-Klausel, SQL Server gefilterte Indizes mit WHERE. MySQL unterstützt funktionale Indizes (über Ausdrücke) und generierte Spalten; echte partielle Indizes wie in PostgreSQL gibt es nicht, häufig helfen zusammengesetzte Indizes oder (bei InnoDB) ein Index, der das Flag nicht als führende Spalte nutzt. SQLite kann (je nach Version) partielle Indizes mit WHERE und Ausdrucksindizes anlegen; in der Praxis sind auch hier zusammengesetzte Indizes oft robuster. Für migrationsfreundliche Modelle sollten boolesche Flags selten alleinstehend indiziert werden; in der Regel profitieren zusammengesetzte Indizes, bei denen das Flag nur ein zusätzlicher Prädikatsanker ist.
Identifier/Schlüssel: UUID/GUID, Sequenzen, Rowid und ORM-Fallen
Als Identifier dominieren zwei Muster: numerische Surrogatschlüssel (Identity/Autoincrement) und UUID/GUID. PostgreSQL besitzt mit uuid einen nativen Typ; SQL Server stellt uniqueidentifier bereit; MySQL unterstützt UUID nicht als eigener Typ, sondern speichert üblicherweise als CHAR(36) oder als binäre 16‑Byte-Repräsentation (BINARY(16)), wobei Applikations- oder Datenbankfunktionen die Konvertierung übernehmen. SQLite speichert UUIDs typischerweise als TEXT oder BLOB, ohne Zwang auf Format oder Länge.
Für Indexe ist die Repräsentation entscheidend: Text-UUIDs vergrößern Indexseiten und verschlechtern Cache-Lokalität; binäre UUIDs sind kompakter, verursachen aber weiterhin zufällige Insert-Positionen in B‑Trees, was zu Page Splits und Fragmentierung führen kann. Numerische, monotone Schlüssel begünstigen Insert-Locality, erzeugen aber bei verteilten Writes und Sharding zusätzliche Koordinationsprobleme. ORMs abstrahieren dies, erzeugen jedoch plattformspezifische DDL: PostgreSQL wird häufig mit GENERATED AS IDENTITY oder Sequenzen ausgestattet, SQL Server mit IDENTITY, MySQL mit AUTO_INCREMENT, SQLite mit INTEGER PRIMARY KEY. Unterschiede treten bei „get generated key“-Semantik, Transaktionssichtbarkeit und beim Verhalten nach Rollbacks auf; lückenlose Nummernfolgen sind in keinem der Systeme garantiert und sollten nicht als fachliche Eigenschaft modelliert werden.
Zeichen-, Binär- und JSON/UUID-Typen: Speicherung, Kollationen, Längenregeln, Indexierbarkeit und Sonderformen
Zeichenketten-, Binär- und semistrukturierte Typen unterscheiden sich zwischen MySQL, PostgreSQL, SQL Server und SQLite weniger durch „kann Text speichern“, sondern durch konkrete Regeln: Längenangaben (Zeichen vs. Bytes), Kollationen und Sortierlogik, implizite Konvertierungen, Indexgrenzen sowie die interne Repräsentation von JSON und UUID. Diese Details entscheiden über Speicherbedarf, Vergleichssemantik, Query-Pläne und darüber, ob ein Schema plattformübergreifend ohne Überraschungen portierbar bleibt.
Zeichenketten: CHAR/VARCHAR/TEXT, NVARCHAR und die Rolle von Kollationen
MySQL unterscheidet klassisch zwischen CHAR (feste Länge, ggf. rechts mit Leerzeichen aufgefüllt) und VARCHAR (variable Länge). Die maximale Länge von VARCHAR hängt effektiv von der Zeilenformatierung und der Zeichensatz-Bytebreite ab (z. B. utf8mb4 mit bis zu 4 Bytes pro Zeichen), während TEXT-Varianten als LOB-Typen behandelt werden und andere Grenzen sowie Off-Page-Speicherung haben können. PostgreSQL speichert char(n), varchar(n) und text intern sehr ähnlich; die Längenangabe bei varchar(n) und char(n) wirkt primär als Constraint, nicht als grundlegend anderes Storage-Format.
SQL Server trennt Unicode und Non-Unicode strikt über NVARCHAR/NCHAR (UTF-16LE, 2 Bytes pro Code Unit) vs. VARCHAR/CHAR (Codepage-abhängig). Hier ist die Kollation nicht nur Sortierreihenfolge, sondern bestimmt auch Vergleichsregeln (z. B. Groß-/Kleinschreibung, Akzent-/Kana-Breite). SQLite speichert Text als dynamisch typisierten Storage Class TEXT; deklarierte Typnamen beeinflussen Affinität und Konvertierung, aber nicht die grundsätzliche Fähigkeit, beliebig lange Strings zu halten. Kollationen in SQLite werden über eingebaute oder registrierte Kollationssequenzen gesteuert; ohne passende Kollation weichen Vergleichsergebnisse bei Internationalisierung leicht ab.
Kollationsunterschiede sind eine häufige Portierungsfalle: PostgreSQL bindet Kollation an die Spalte oder an Ausdrücke und richtet sich stark nach der zugrunde liegenden ICU/libc-Implementierung; in MySQL ist Kollation Teil von Charset/Collation-Paaren; in SQL Server sind Kollationen tief in Typen, Indizes und Vergleiche integriert. Ein identischer Stringvergleich kann daher plattformabhängig andere Sortierung oder Gleichheit liefern, insbesondere bei Mehrbyte-Zeichen, zusammengesetzten Zeichen oder sprachspezifischen Regeln.
Längenregeln: Zeichen vs. Bytes, Trunkierung und Semantik von Leerzeichen
„Länge“ bedeutet nicht überall dasselbe. In SQL Server ist die Längenangabe bei NVARCHAR(n) in Zeichen (Code Units) definiert, bei VARCHAR(n) in Bytes. In MySQL beziehen sich CHAR(n) und VARCHAR(n) auf Zeichen, während Speicher- und Zeilenlimits bytebasiert greifen. PostgreSQL definiert die Grenze bei varchar(n) als Anzahl von Zeichen und validiert bei Insert/Update; text hat keinen deklarativen Grenzwert. SQLite kennt keine harte, deklarative Spaltenlänge; Begrenzungen müssen über CHECK oder Anwendung/ORM erfolgen.
Leerzeichen-Semantik variiert: Bei SQL Server werden bei Vergleichen mit CHAR/VARCHAR trailing spaces in vielen Fällen ignoriert; bei PostgreSQL sind Strings byte-/zeichenexakt (außer bei char(n), wo rechts gepaddet und beim Vergleich die Auffüllung nicht unterscheidet). MySQL kann bei CHAR Auffüllung und bei bestimmten Kollationen/SQL-Modi abweichende Vergleichsregeln zeigen. Für eindeutige Schlüssel auf textuellen Spalten empfiehlt sich deshalb eine explizite Normalisierungsstrategie (z. B. trimmen, case-folding) als Teil des Datenmodells, nicht als stillschweigende Annahme.
- Zeichen- vs. Byte-Limits: In MySQL begrenzt die Kombination aus
VARCHAR-Länge, Zeichensatz (z. B.utf8mb4) und Row-Format die tatsächlich speicherbare Länge; in SQL Server istNVARCHAR(n)an UTF-16 Code Units gebunden, währendVARCHAR(n)codepage- und byteabhängig ist. - Trunkierung: PostgreSQL wirft bei Überschreitung von
varchar(n)typischerweise einen Fehler; SQL Server kann je nach Einstellung/Client bei Konvertierungen und Zuweisungen warnen oder fehlschlagen; SQLite akzeptiert Werte ohne automatische Kürzung, sofern keine Constraints existieren. - Trailing Spaces: SQL Server behandelt bei vielen Vergleichen und eindeutigen Indizes Strings so, als wären abschließende Leerzeichen nicht signifikant; in PostgreSQL und SQLite bleiben sie grundsätzlich signifikant (abgesehen von
char(n)-Spezifika). - Kollationsbindung: In SQL Server beeinflusst die Kollation Indexordnung und Gleichheit unmittelbar; in PostgreSQL können Indizes kollationsspezifisch sein, sodass ein Spaltenindex nicht automatisch für eine abweichende
COLLATE-Klausel gilt.
Binärtypen: BINARY/VARBINARY, BYTEA/BLOB und Auswirkungen auf Indizes
Binärdaten werden in MySQL über BINARY/VARBINARY sowie BLOB-Familien abgebildet, in SQL Server über BINARY/VARBINARY (inklusive VARBINARY(MAX)), in PostgreSQL typischerweise über bytea, in SQLite über BLOB. Für Performance und Indexierbarkeit ist entscheidend, ob der Typ als „LOB“ gilt und wie Indizes damit umgehen. MySQL benötigt bei großen Text-/BLOB-Spalten für reguläre B-Tree-Indizes häufig Präfixlängen (z. B. Index auf die ersten N Bytes/Zeichen), was die Selektivität begrenzt und Kollisionen wahrscheinlicher macht. SQL Server begrenzt Schlüsselspalten in Indizes; sehr große VARBINARY(MAX)-Werte sind nicht als regulärer Indexschlüssel geeignet, sondern erfordern alternative Modellierung (z. B. Hash, separate Suchspalten). PostgreSQL kann bytea grundsätzlich indexieren, doch große Werte führen zu breiten Indexeinträgen; häufig ist ein funktionaler Index auf einen Hash sinnvoller.
Semantisch vergleicht Binärtypen byteweise; Kollationen spielen hier keine Rolle. Trotzdem entstehen Portierungsprobleme durch implizite Casts zwischen Text und Binary (z. b. Hex-/Base64-Darstellungen), unterschiedliche Literal-Syntax und Treiberverhalten. Für UUIDs, Hashes oder verschlüsselte Payloads sollte deshalb ein klarer Binärpfad ohne implizite Textkonvertierung festgelegt werden.
| Semantik/Aspekt | MySQL | PostgreSQL | SQL Server | SQLite |
|---|---|---|---|---|
| Kurze Binärwerte (z. b. Hash/Key) | VARBINARY(n) oder BINARY(n) |
bytea |
VARBINARY(n) oder BINARY(n) |
BLOB (Affinität, keine feste Länge) |
| Große Payloads | BLOB / MEDIUMBLOB / LONGBLOB |
bytea (TOAST-Mechanismus) |
VARBINARY(MAX) |
BLOB |
| Indexierbarkeit | Bei BLOB oft nur Präfixindex; VARBINARY regulär |
bytea regulär, große Werte unpraktisch |
Indexschlüssel-Limits; (MAX) nicht als Schlüsselspalte |
Index auf BLOB möglich, Vergleich byteweise; Größe bleibt kritisch |
| Vergleich/Sortierung | Bytevergleich | Bytevergleich | Bytevergleich | Bytevergleich |
JSON: nativer Typ, Textspeicherung und Index-Strategien
JSON ist nicht überall gleichwertig „ein Typ“. PostgreSQL bietet mit jsonb einen binär repräsentierten JSON-Typ mit Normalisierung (z. b. Schlüsselreihenfolge) und operator-/indexfreundlicher Speicherung; json speichert den Originaltext und validiert Syntax, ohne die gleiche Indexeffizienz. MySQL speichert JSON in einem binären Format und validiert beim Schreiben; funktionale Indizes bzw. generierte Spalten werden oft genutzt, um einzelne JSON-Pfade für B-Tree-Indizes zu materialisieren. SQL Server hat keinen eigenständigen JSON-Datentyp; JSON wird üblicherweise als NVARCHAR gespeichert, während Funktionen wie JSON_VALUE und JSON_QUERY Verarbeitung und (bei Bedarf) Validierung unterstützen. Für Indizes etabliert sich auch hier die Persistierung extrahierter Werte (berechnete Spalten) als klassische Spalten. SQLite behandelt JSON als Text; die JSON1-Erweiterung stellt Funktionen bereit, aber ohne nativen JSON-Storage-Typ. Indexierbarkeit ergibt sich in der Praxis über Ausdrucksindizes auf JSON-Extraktionen.
Kompatibilitätsrelevant sind Unterschiede bei Zahlenrepräsentation, Whitespace, Duplikatschlüsseln und Erhalt der Originalformatierung. PostgreSQL jsonb kann semantisch gleiches, aber textuell unterschiedliches JSON identisch speichern; SQL Server und SQLite als Text bewahren Formatierungsdetails, was bei Signaturen, Hashes oder exakten Re-Serialisierungen relevant ist. Für ORMs entsteht daraus eine klare Entscheidung: entweder semantische JSON-Vergleiche (besser mit jsonb/JSON als binärem Format) oder textuelle Stabilität (Textspalte, Validierung per Constraint/Trigger/Anwendung).
UUID/GUID: nativer Typ vs. String/Binary und Portierungsregeln
PostgreSQL stellt uuid als nativen 16-Byte-Typ bereit. SQL Server bietet uniqueidentifier, ebenfalls 16 Byte, mit eigener Sortier- und Anzeigecharakteristik; die Byte-/Segmentreihenfolge in der Textdarstellung kann bei Rohbyte-Konvertierungen irritieren, weshalb einheitliche Serialisierung über Standard-Textform oder Treiberkonvertierung bevorzugt wird. MySQL besitzt keinen dedizierten UUID-Typ; verbreitet sind Speicherung als CHAR(36) (lesbar, aber größer und kollationssensitiv) oder als BINARY(16) (kompakt, indexfreundlicher). SQLite nutzt meist TEXT oder BLOB ohne strikten Typzwang.
Für Indexierbarkeit gilt: BINARY(16)/uuid/uniqueidentifier erzeugen kompaktere Indexseiten als CHAR(36) oder NVARCHAR(36). Gleichzeitig beeinflusst die Einfüge-Reihenfolge die Fragmentierung: zufällige UUIDs führen in B-Tree-Indizes häufig zu Page-Splits. Plattformübergreifend lässt sich das Problem nur über Datenmodell- und Generierungsentscheidungen adressieren (z. b. zeitbasierte UUID-Varianten oder separate Surrogate Keys), nicht durch den Datentyp allein.
Datum/Zeit, Intervall und Typkonvertierung: Zeitzonen, Rundungsregeln, CAST/CONVERT-Matrizen und Auswirkungen auf ORMs
Datum- und Zeittypen wirken auf den ersten Blick ähnlich, unterscheiden sich plattformübergreifend jedoch in Semantik (Zeitzone ja/nein), Auflösung (Sekunde bis Nanosekunde), Gültigkeitsbereich, Rundungsverhalten und Konvertierungsregeln. Diese Unterschiede bleiben selten folgenlos: Sie beeinflussen Sortierung, Indexnutzung, Vergleichslogik, Replikation, Import/Export, aber auch die Typabbildung in ORMs und damit Migrationsskripte.
Zeitzonen-Semantik: „timestamp“ ist nicht überall dasselbe
PostgreSQL trennt strikt zwischen timestamp without time zone (Zeitpunkt ohne Zeitzoneninformation; Interpretation liegt bei der Anwendung) und timestamp with time zone (timestamptz). Letzterer speichert intern einen absoluten Zeitpunkt und normalisiert Ein-/Ausgabe anhand der Session-Zeitzone. MySQL kennt TIMESTAMP mit UTC-Konvertierung bei Speicherung/Ausgabe (abhängig von time_zone) und DATETIME als zeitzonenloser Wandzeit-Typ. SQL Server unterscheidet datetimeoffset (Offset Bestandteil des Werts) von datetime2 (ohne Offset); eine automatische Normalisierung auf UTC erfolgt nicht. SQLite besitzt keine nativen Datum/Zeit-Typen; Werte liegen typischerweise als TEXT (ISO-8601), REAL (Julian day) oder INTEGER (Unix-Epoch) vor, wodurch Zeitzonenbehandlung vollständig in Anwendung und Funktionen verlagert wird.
Für fachliche „Kalenderzeit“ (z. b. Ladenöffnungszeiten) sind zeitzonenlose Typen stabiler, weil DST-Umstellungen sonst doppelte oder nicht existente lokale Zeiten erzeugen. Für Ereignisse, die weltweit eindeutig sein müssen (Log-Zeitpunkte, Zahlungen), bietet sich ein absoluter Zeitpunkt an, in PostgreSQL meist timestamptz, in SQL Server datetimeoffset oder ein explizit als UTC geführtes datetime2. In MySQL führt TIMESTAMP häufiger zu Überraschungen, wenn Session-Zeitzonen pro Verbindung variieren.
Auflösung und Rundung: Mikrosekunden, 100‑ns und Nanosekunden
PostgreSQL arbeitet bei timestamp/timestamptz mit Mikrosekundenauflösung. MySQL unterstützt Bruchteile bis zu 6 Stellen (Mikrosekunden) über DATETIME(fsp) und TIMESTAMP(fsp). SQL Server bietet mit datetime2(p) und datetimeoffset(p) eine Auflösung bis 100 Nanosekunden (Skalierung 0–7); das ältere datetime ist gröber und bildet Sekundenbruchteile in 1/300‑Sekunden-Schritten ab, was beim Roundtrip sichtbar wird. SQLite hängt von der Darstellung ab; gängige ISO-Strings transportieren oft Mikrosekunden, verlieren aber bei inkonsistenten Formaten oder numerischen Repräsentationen schnell Präzision.
Rundung tritt bei Konvertierungen zwischen Skalen und bei String-Parsing auf. PostgreSQL rundet bei vielen Umwandlungen auf die Zielauflösung (nicht stumpf abzuschneiden), MySQL rundet bei FSP-Reduktion ebenfalls, kann aber je nach SQL-Mode und Funktionspfad abweichende Effekte zeigen. SQL Server rundet bei datetime2(p) und datetimeoffset(p) beim Cast auf geringere Skala; beim Cast nach datetime greifen die 1/300‑Sekunden-Inkremente, wodurch Zeitpunkte „springen“ können. Für Vergleichsoperationen und Deduplizierung ist deshalb eine Normalisierung auf eine feste Auflösung (z. b. Mikrosekunden oder Millisekunden) häufig sinnvoll.
| Datenbank | Zeitzonenfähiger Typ | Zeitzonenloser Typ | Max. Bruchteile |
|---|---|---|---|
| MySQL | TIMESTAMP(fsp) (Konvertierung über Session-Zeitzone) |
DATETIME(fsp) |
6 (µs) |
| PostgreSQL | timestamptz (Session-Zeitzone für I/O) |
timestamp |
6 (µs) |
| SQL Server | datetimeoffset(p) (Offset im Wert) |
datetime2(p) |
7 (100 ns) |
| SQLite | kein nativer Typ (Konventionen: ISO-8601, Unix-Epoch, Julian Day) | kein nativer Typ | formatabhängig |
Intervall- und Dauerlogik: periodisch vs. absolut
Intervalltypen sind besonders inkompatibel. PostgreSQL besitzt interval als eigenen Typ, der Monate/Jahre getrennt von Tagen/Zeiten verwaltet und damit „kalenderartige“ Dauern ausdrücken kann. SQL Server hat keinen nativen Intervalltyp; Dauern werden typischerweise als Ganzzahl in einer Basiseinheit (z. b. Sekunden) oder als time (für Tageszeit, nicht für Dauer) modelliert, alternativ über datetime2-Differenzen und DATEDIFF/DATEADD. MySQL bietet ebenfalls keinen Intervall-Datentyp; INTERVAL ist dort eine Syntaxkomponente für Datumsarithmetik, nicht speicherbar als Typ. SQLite folgt dem Muster „kein Typ, nur Konvention“; Dauern werden meist als INTEGER (Sekunden/Millisekunden) gespeichert.
Kalenderdauern („+1 Monat“) sind nicht äquivalent zu festen Sekundenwerten; die resultierende Zeit hängt vom Startdatum ab. ORMs, die Duration oder TimeSpan anbieten, müssen deshalb entscheiden, ob sie eine feste Dauer (Sekunden/Nanosekunden) oder ein kalenderbasiertes Intervall abbilden. PostgreSQL interval kann beides enthalten; viele ORMs mappen es dennoch nur eingeschränkt oder serialisieren als Text, um Mehrdeutigkeiten zu vermeiden.
CAST/CONVERT-Matrizen: typische Konvertierungspfade und Fallstricke
Konvertierungen zwischen Datum/Zeit, Text und numerischen Epochenwerten sind in allen Systemen möglich, aber nicht gleichwertig. PostgreSQL ist bei CAST in der Regel strikt und unterstützt zusätzlich explizite Formatierung über to_timestamp und to_char. MySQL bietet CAST sowie Parser/Formatter wie STR_TO_DATE und DATE_FORMAT; stille Korrekturen oder „Null-Dates“ hängen stark vom SQL-Mode ab. SQL Server trennt CAST/CONVERT (mit Style-Codes) und ist bei mehrdeutigen Formaten ohne Style fehleranfällig. SQLite nutzt CAST primär für Typaffinität; Datum/Zeit-Funktionen wie datetime() und strftime() arbeiten auf Text/Numbers und interpretieren Formate nach eigenen Regeln.
- ISO-8601 als Austauschformat: Für Roundtrips zwischen Systemen ist ein unmissverständliches Format wie
YYYY-MM-DDTHH:MM:SS[.ffffff]Z(UTC) robust; bei SQL Server ist für Parsing oftCONVERT(datetime2, ..., 127)geeignet, während PostgreSQL ISO-Strings nativ gut akzeptiert. - Epoch-Konvertierung: PostgreSQL nutzt
to_timestamp(double precision)(Sekunden seit Unix-Epoch) undextract(epoch from ...); in MySQL liefernUNIX_TIMESTAMP(...)undFROM_UNIXTIME(...)Sekundenwerte, wobei Zeitzonenregeln beiTIMESTAMPhineinspielen; SQL Server arbeitet häufig mitDATEDIFF_BIG(second, '1970-01-01', ...)bzw.DATEADD(second, ..., '1970-01-01'); SQLite rechnet oft überstrftime('%s', ...)undunixepoch()(abhängig von Version/Funktionsverfügbarkeit). - Skalenreduktion und Rundung: Beim Heruntersetzen der Präzision sind explizite Normalisierungen sinnvoll, z. b. PostgreSQL
date_trunc('milliseconds', ...), MySQLCAST(ts AS DATETIME(3)), SQL ServerDATETIME2FROMPARTSoderDATEADD(millisecond, DATEDIFF(millisecond, '20000101', ...), '20000101')für deterministische Rasterung. - Ambige lokale Zeiten: Bei DST-Übergängen führen lokale Timestamps ohne Offset zu Mehrdeutigkeiten; PostgreSQL löst bei
timestamptzüber die aktuelle Zeitzonenregel auf, SQL Server benötigt beidatetimeoffseteinen expliziten Offset, MySQLDATETIMEbleibt ambig, SQLite überlässt die Interpretation der Anwendung.
ORM-Auswirkungen: Mapping, Migrationen und Abfragepläne
ORMS abstrahieren Datum/Zeit-Typen, können aber die Semantik nicht vollständig vereinheitlichen. In PostgreSQL wird ein „timezone-aware“ Datumsfeld häufig zu timestamptz gemappt, in MySQL dagegen je nach Dialekt auf DATETIME oder TIMESTAMP; damit ändern sich sowohl Korrektheit als auch Portabilität. SQL Server-Mappings nutzen oft datetime2 als Standard, weil datetime aufgrund der 1/300‑Sekunden-Granularität und des eingeschränkteren Bereichs ungewollte Effekte in Vergleichen und Unique-Constraints erzeugen kann. Für SQLite wählen ORMs meist TEXT-Serialisierung; Indexe funktionieren dann lexikografisch nur zuverlässig, wenn durchgehend ISO-8601 mit fixer Breite verwendet wird.
Typkonvertierungen in WHERE-Klauseln beeinflussen Indexnutzung stark. Funktionen auf der Spalte (z. B. „cast column to date“) verhindern häufig die Sargability; das gilt plattformübergreifend. Migrationen sollten daher Spaltentypen so wählen, dass die häufigsten Filter ohne Funktionswrap auskommen, etwa separate Spalten für Datum und Zeit, oder persistierte Normalisierungen (z. B. UTC in einer eigenen Spalte). Bei Multi-DB-Setups reduziert ein konsistenter Ansatz für Zeitzonen (typisch: UTC speichern, Ausgabe in der Anwendung lokalisieren) die Zahl impliziter Casts und die Gefahr von Datenabweichungen in Tests und Replikation.
Meroth IT-Service ist Ihr lokaler IT-Dienstleister in Frankfurt am Main für kleine Unternehmen, Selbstständige und Privatkunden
Kostenfreie Ersteinschätzung Ihres Anliegens?
Werbung
(**) UVP: Unverbindliche Preisempfehlung
Preise inkl. MwSt., zzgl. Versandkosten
