Der Großteil der PostgreSQL- vs. MySQL-Syntax, die Sie täglich schreiben, ist identisch. SELECT, JOIN, WHERE, GROUP BY und einfache INSERT funktionieren in beiden gleich. Das Problem sind die anderen 20 Prozent, die dialektspezifischen Ecken, in denen eine Abfrage, die unter MySQL sauber läuft, unter Postgres einen Syntaxfehler wirft (und umgekehrt). Diese Anleitung kartiert diese Ecken mit nebeneinanderstehenden Beispielen, damit Sie Abfragen ohne Trial-and-Error portieren können.
Wenn Sie eine Datenbank migrieren, eine Abfrage schreiben, die auf beiden Engines laufen muss, oder einfach verstehen wollen, warum ein Stack Overflow-Snippet in Ihrer Konsole nicht funktioniert, liegt die Antwort fast immer an einer Handvoll bekannter Unterschiede. UPSERT, Booleans, Mengenoperatoren, Auto-Inkrement und String-Handling verursachen die überwältigende Mehrheit der Portierungsschmerzen.
PostgreSQL vs MySQL Syntax: Die 20%, die wirklich kaputt gehen#
Business-Vergleiche dieser beiden Datenbanken konzentrieren sich auf Leistung, Lizenzierung und Replikation. Nützlich für die Auswahl eines Stacks, nutzlos, wenn Sie eine kaputte Abfrage vor sich haben. Was Sie brauchen, ist die Syntax-Tabelle.
Hier ist die Kurzversion, wo sich die Dialekte unterscheiden. Der Rest dieses Leitfadens erweitert jede Zeile mit echten Beispielen.
| Funktion | PostgreSQL | MySQL |
|---|---|---|
| UPSERT | INSERT ... ON CONFLICT ... DO UPDATE | INSERT ... ON DUPLICATE KEY UPDATE |
| Auto-Inkrement | SERIAL / GENERATED ... AS IDENTITY | AUTO_INCREMENT |
| Boolean | nativ BOOLEAN (true/false) | TINYINT(1) Alias, speichert 0/1 |
| Mengenoperatoren | INTERSECT, EXCEPT unterstützt | INTERSECT/EXCEPT nur ab 8.0.31+ |
| String-Verkettung | || Operator | CONCAT() (|| bedeutet ODER) |
| Groß-/Kleinschreibung | Bezeichner werden in Kleinbuchstaben umgewandelt | hängt vom Betriebssystem und der Tabellenkonfiguration ab |
| Anführungszeichen | doppelte Anführungszeichen für Bezeichner | Backticks für Bezeichner |
| LIMIT/OFFSET | LIMIT n OFFSET m | LIMIT m, n oder LIMIT n OFFSET m |
Faustregel: Wenn eine Abfrage nach einer Migration fehlschlägt, überprüfen Sie zuerst UPSERT, Boolean-Vergleiche und Bezeichner-Anführungszeichen. Diese drei verursachen die meisten Probleme.
UPSERT: ON CONFLICT vs ON DUPLICATE KEY UPDATE#
Dies ist die größte Stolperfalle beim Portieren von Abfragen und hat keine saubere automatische Übersetzung. Beide Datenbank-Engines ermöglichen das Einfügen einer Zeile und deren Aktualisierung, falls ein Schlüssel bereits existiert, aber die Syntax ist völlig unterschiedlich.
MySQL verwendet ON DUPLICATE KEY UPDATE, das bei jeder Kollision eines eindeutigen oder Primärschlüssels ausgelöst wird:
INSERT INTO users (id, email, login_count)
VALUES (1, 'a@example.com', 1)
ON DUPLICATE KEY UPDATE login_count = login_count + 1;
PostgreSQL verwendet ON CONFLICT und zwingt Sie, das Konfliktziel (die Spalte oder Einschränkung, bei der Sie eine Kollision erwarten) zu benennen:
INSERT INTO users (id, email, login_count)
VALUES (1, 'a@example.com', 1)
ON CONFLICT (id) DO UPDATE
SET login_count = users.login_count + 1;
Zwei Dinge bereiten hier Probleme. Erstens erfordert Postgres das Konfliktziel, während MySQL es aus dem verletzten eindeutigen Schlüssel ableitet. Zweitens unterscheidet sich die Referenzierung der eingehenden Zeile. MySQL stellt die neuen Werte über VALUES(col) (ältere Syntax) oder einen Zeilen-Alias bereit, während Postgres sie über die spezielle EXCLUDED-Pseudotabelle bereitstellt:
-- PostgreSQL Referenzierung des eingehenden Werts
ON CONFLICT (id) DO UPDATE
SET login_count = EXCLUDED.login_count;
Wenn Sie sich nur einen Unterschied aus diesem Artikel merken, dann diesen. UPSERT ist der Bereich, in dem die meisten Abfragen stillschweigend bei der Übersetzung scheitern.
Auto-Increment: SERIAL und IDENTITY vs AUTO_INCREMENT#
Auto-Increment-Primärschlüssel gibt es in beiden Datenbank-Engines, sie werden jedoch unterschiedlich deklariert.
MySQL hängt AUTO_INCREMENT an die Spalte an:
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
total DECIMAL(10,2)
);
PostgreSQL bietet zwei Ansätze. Der ältere, weit verbreitete ist SERIAL, eine Kurzform, die im Hintergrund eine Sequenz erstellt:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
total NUMERIC(10,2)
);
Der moderne, SQL-Standard-konforme Ansatz (empfohlen ab Postgres 10) ist GENERATED ... AS IDENTITY:
CREATE TABLE orders (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
total NUMERIC(10,2)
);
Ein subtiler Unterschied: MySQLs DECIMAL und Postgres' NUMERIC sind funktional derselbe beliebig genaue Datentyp, und beide akzeptieren beide Schlüsselwörter. Aber NUMERIC ist der SQL-Standard-Name, den Sie in der Postgres-Dokumentation sehen werden. Seien Sie also nicht überrascht, wenn die Portierung in die andere Richtung erfolgt.
Booleans: Native BOOLEAN vs TINYINT(1)#
PostgreSQL hat einen echten BOOLEAN-Typ, der true und false (sowie NULL) speichert. Sie können direkt vergleichen:
SELECT * FROM users WHERE is_active = true;
SELECT * FROM users WHERE is_active; -- auch gültig in Postgres
MySQL hat keinen nativen Boolean. BOOLEAN und BOOL sind Aliase für TINYINT(1), das Ganzzahlen speichert. true wird zu 1 und false zu 0. Das funktioniert meistens, aber es gibt zwei Fallstricke:
- Vergleiche mit den Literalen
true/falsefunktionieren, da MySQL sie als1/0behandelt, aber Ihre gespeicherten Daten sind Ganzzahlen, daher gibt einSELECT1und0zurück, nichttrueundfalse. - Jeder Wert ungleich 0 ist in MySQLs Ganzzahlwelt wahrheitsgemäß, daher können
TINYINT(1)-Spalten technisch2,5oder-1enthalten, wenn Ihre Anwendung unvorsichtig ist.
Wenn Sie eine Postgres-Boolean-Spalte nach MySQL portieren, erwarten Sie, dass die Daten als 0/1 erscheinen, und passen Sie Anwendungscode oder ORM-Mapping an, das die Zeichenfolgen true/false erwartet hat.
Mengenoperatoren: INTERSECT und EXCEPT#
PostgreSQL unterstützt die standardmäßigen Mengenoperatoren vollständig: UNION, UNION ALL, INTERSECT und EXCEPT. Alle verhalten sich wie im SQL-Standard beschrieben.
SELECT id FROM table_a
INTERSECT
SELECT id FROM table_b;
MySQL war hier jahrelang der Nachzügler. UNION und UNION ALL funktionierten immer, aber INTERSECT und EXCEPT kamen erst mit MySQL 8.0.31. Wenn Sie eine ältere MySQL-Version anvisieren, können Sie diese Operatoren gar nicht verwenden und müssen die Logik umschreiben.
Der klassische MySQL-Workaround für INTERSECT ist ein innerer Join oder eine IN-Unterabfrage:
-- INTERSECT umgeschrieben für älteres MySQL
SELECT DISTINCT a.id
FROM table_a a
JOIN table_b b ON a.id = b.id;
Und für EXCEPT ein LEFT JOIN ... IS NULL oder ein NOT IN / NOT EXISTS-Muster:
-- EXCEPT umgeschrieben für älteres MySQL
SELECT a.id
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
WHERE b.id IS NULL;
Achten Sie auf die NULL-Behandlung.
NOT INmit einer Unterabfrage, die einen NULL-Wert zurückgibt, kann unerwartet zu einem leeren Ergebnissatz führen.NOT EXISTSund dasLEFT JOIN ... IS NULL-Muster sind sicherer, wenn NULL-Werte möglich sind.
Strings, Anführungszeichen und Verkettung#
Diese Kategorie verursacht die verwirrendsten Fehler, weil dieselben Zeichen unterschiedliche Bedeutungen haben.
Identifier-Quoting#
PostgreSQL verwendet doppelte Anführungszeichen für Bezeichner (Tabellen- und Spaltennamen) und einfache Anführungszeichen für Zeichenfolgenliterale:
SELECT "userName" FROM "Users" WHERE name = 'Alice';
MySQL verwendet standardmäßig Backticks für Bezeichner:
SELECT `userName` FROM `Users` WHERE name = 'Alice';
MySQL kann so konfiguriert werden, dass es doppelt Anführungszeichen für Bezeichner akzeptiert, indem der Modus ANSI_QUOTES aktiviert wird. Standardmäßig verhalten sich doppelte Anführungszeichen um eine Zeichenfolge jedoch wie ein Zeichenfolgenliteral, was zu verwirrenden Fehlern führt, wenn Sie Postgres-SQL in eine MySQL-Konsole kopieren.
Zeichenfolgenverkettung#
PostgreSQL verwendet den Standardoperator || zum Verketten von Zeichenfolgen:
SELECT first_name || ' ' || last_name AS full_name FROM users;
In MySQL bedeutet || standardmäßig logisches ODER, nicht Verkettung. Sie müssen die Funktion CONCAT() verwenden:
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
Dies ist ein stiller Fehler, der nur darauf wartet, passiert zu werden. Eine aus Postgres kopierte ||-Verkettung führt in MySQL nicht zu einem Fehler; sie wird stattdessen leise als boolesches ODER ausgewertet und 0 oder 1 zurückgeben, was weitaus schwieriger zu debuggen ist als ein Syntaxfehler.
Groß-/Kleinschreibung#
PostgreSQL wandelt nicht in Anführungszeichen gesetzte Bezeichner in Kleinbuchstaben um, sodass MyTable und mytable dasselbe Objekt sind, es sei denn, Sie setzen sie in Anführungszeichen. Die Groß-/Kleinschreibung von Bezeichnern in MySQL hängt vom Betriebssystem und der Einstellung lower_case_table_names ab, was bedeutet, dass eine Abfrage auf dem Mac eines Entwicklers funktionieren und auf einem Linux-Produktionsserver fehlschlagen kann. Wenn Sie unsicher sind, wählen Sie eine konsistente Benennungskonvention mit Kleinbuchstaben und Unterstrichen, und Sie umgehen das gesamte Problem.
LIMIT, OFFSET und Paginierung#
Beide Engines unterstützen LIMIT n OFFSET m, die portable Form, die bevorzugt werden sollte:
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20;
MySQL akzeptiert auch eine Kurzform mit Komma, die PostgreSQL nicht versteht:
-- Nur MySQL: 20 überspringen, 10 nehmen
SELECT * FROM products ORDER BY id LIMIT 20, 10;
Beachten Sie, dass die Reihenfolge in der Komma-Form umgekehrt ist (zuerst Offset, dann Anzahl), was eine häufige Quelle für Seitenfehler ist. Bleiben Sie bei der expliziten LIMIT ... OFFSET ...-Syntax, dann sind Ihre Paginierungsabfragen in beide Richtungen sauber portierbar.
Abfragen portieren ohne Rätselraten#
Wenn Sie eine Abfrage zwischen diesen Dialekten verschieben, gehen Sie die risikoreichen Features der Reihe nach durch, anstatt sie blind auszuführen und Fehlermeldungen zu lesen. Eine praktische Checkliste:
- Ersetzen Sie die UPSERT-Syntax (
ON DUPLICATE KEY UPDATEdurchON CONFLICToder umgekehrt) und korrigieren Sie den Zeilenverweis (VALUES()zuEXCLUDED). - Tauschen Sie
||-Zeichenverkettung gegenCONCAT()aus, wenn Sie auf MySQL abzielen. - Konvertieren Sie die Anführungszeichen für Bezeichner (Backticks zu doppelten Anführungszeichen oder umgekehrt).
- Überprüfen Sie boolesche Spalten und erwarten Sie
0/1-Daten in MySQL. - Ersetzen Sie
INTERSECT/EXCEPTdurch Joins, wenn Sie auf MySQL vor 8.0.31 abzielen. - Normalisieren Sie Auto-Increment-Deklarationen (
SERIAL/IDENTITYversusAUTO_INCREMENT).
Das Lesen einer dichten Abfrage, um diese Stellen zu finden, ist viel einfacher, sobald sie formatiert ist. Eine Wand aus einzeiligem SQL verbirgt die ON CONFLICT-Klausel und das vereinzelte ||. Unser Multi-Dialekt-SQL-Formatter formatiert und rückt Abfragen für PostgreSQL und MySQL ein, sodass Sie die Struktur klar erkennen, bevor Sie mit der Übersetzung beginnen. Für einen tieferen Einblick, wie sich die Formatierung zwischen verschiedenen Engines unterscheidet, lesen Sie unseren Leitfaden zum Formatieren einer SQL-Abfrage für Lesbarkeit über Dialekte hinweg.
Wenn Ihre Arbeit auch das Senden von Abfrageergebnissen an eine API oder Konfiguration umfasst, gilt das gleiche Lesbarkeitsprinzip für Ihre Payloads. Unser JSON-Formatter bereinigt die Datenschicht, und die Anleitung zum Formatieren von JSON in JavaScript behandelt die JSON.stringify-Muster, die mit Datenbankausgaben zusammenarbeiten.
Das Fazit zu PostgreSQL vs. MySQL Syntax#
Die syntaktischen Unterschiede zwischen PostgreSQL und MySQL sind konzentriert, nicht verstreut. Standard SELECT, JOIN und Aggregationen sind portierbar; die Bruchstellen konzentrieren sich auf UPSERT, Booleans, Mengenoperatoren, String-Verkettung, Identifier-Quoting und Auto-Increment. Lernen Sie diese sechs und Sie können die überwältigende Mehrheit realer Abfragen ohne Überraschungen portieren.
Behalten Sie dieses mentale Tabellenmodell: Wenn nach einer Migration etwas fehlschlägt, liegt die Ursache fast sicher in einer der oben genannten Dialektecken, nicht in Ihrer Kernlogik. Formatieren Sie die Abfrage zuerst, scannen Sie nach den risikoreichen Klauseln, übersetzen Sie sie bewusst, und Sie verwandeln ein frustrierendes Ratespiel in eine schnelle, mechanische Korrektur.
Häufig gestellte Fragen#
Was ist der größte Syntaxunterschied zwischen PostgreSQL und MySQL?
UPSERT ist der größte praktische Unterschied. PostgreSQL verwendet INSERT ... ON CONFLICT ... DO UPDATE mit einem expliziten Konflikt-Ziel und der EXCLUDED-Pseudotabelle, während MySQL INSERT ... ON DUPLICATE KEY UPDATE verwendet und den Schlüssel ableitet. Es gibt keine saubere automatische Übersetzung, daher ist dies die Klausel, die beim Portieren einer Abfrage am wahrscheinlichsten bricht.
Warum schlägt meine PostgreSQL-String-Verkettung in MySQL fehl?
PostgreSQL verwendet den ||-Operator für die String-Verkettung, aber in MySQL bedeutet || standardmäßig logisches ODER. Das Kopieren einer Postgres-Abfrage in MySQL führt nicht zu einem Syntaxfehler, sondern gibt stattdessen leise 0 oder 1 anstelle eines verbundenen Strings zurück. Verwenden Sie in MySQL die Funktion CONCAT(), um das gleiche Ergebnis zu erzielen.
Hat MySQL einen echten BOOLEAN-Typ wie PostgreSQL?
Nein. PostgreSQL hat einen nativen BOOLEAN-Typ, der true/false speichert. In MySQL sind BOOLEAN und BOOL Aliase für TINYINT(1), das ganze Zahlen speichert, sodass true zu 1 und false zu 0 wird. Wenn Sie boolesche Spalten portieren, erwarten Sie, dass die Daten als 0/1 erscheinen, und aktualisieren Sie Code, der die String-Werte erwartet hat.
Kann ich INTERSECT und EXCEPT in MySQL verwenden?
Nur in MySQL 8.0.31 und höher. PostgreSQL unterstützt INTERSECT und EXCEPT seit langem. In älteren MySQL-Versionen müssen Sie sie umschreiben, indem Sie einen inneren Join oder eine IN-Unterabfrage für INTERSECT und ein LEFT JOIN ... IS NULL- oder NOT EXISTS-Muster für EXCEPT verwenden, wobei Sie auf die NULL-Behandlung achten müssen.
Ist das meiste SQL zwischen PostgreSQL und MySQL portierbar?
Ja. Ungefähr 80 Prozent des alltäglichen SQL, einschließlich SELECT, JOIN, WHERE, GROUP BY und einfachem INSERT, sind identisch oder nahe genug, um auf beiden Datenbanken zu laufen. Die Unterschiede konzentrieren sich auf UPSERT, Booleans, Mengenoperatoren, Bezeichner-Zitierung, String-Verkettung und Auto-Inkrement, weshalb eine gezielte Checkliste besser ist, als Fehlermeldungen einzeln zu lesen.
Wie erkenne ich am schnellsten Dialektunterschiede in einer Abfrage?
Formatieren Sie die Abfrage zuerst, damit die Struktur sichtbar ist, und scannen Sie dann nach den risikoreichen Klauseln. Ein Formatierer, der beide Dialekte unterstützt, wie der Molixa SQL-Formatierer, rückt das SQL ein und richtet es aus, sodass die ON CONFLICT-, ||- und Zitierungsunterschiede hervorstechen, anstatt sich in einer einzigen dichten Zeile zu verstecken.



