consolelog.tools

PostgreSQL vs MySQL

A side-by-side reference for the syntax differences between PostgreSQL and MySQL, plus a best-effort two-way query converter. Search and filter dozens of topics - auto-increment, JSON access, upsert, pagination, dates, booleans, arrays, CTEs and more - then paste a query to translate it in either direction. Everything runs in your browser.

About this ToolHow it works, benefits & use cases

A two-in-one reference for working across PostgreSQL and MySQL: search side-by-side syntax comparisons and run a quick query conversion, all on one page. The comparison reference covers more than two dozen topics - auto-increment (SERIAL vs AUTO_INCREMENT), string concatenation (|| vs CONCAT), pagination, date/time functions and formatting, ILIKE vs LIKE, regex (~ vs REGEXP), boolean vs TINYINT(1), JSON access, upsert (ON CONFLICT vs ON DUPLICATE KEY), sequences, arrays, UUID keys, enums, casting, full-text search, window functions, recursive CTEs, generate_series, RETURNING vs LAST_INSERT_ID, and JOIN-based UPDATE/DELETE - each with PostgreSQL and MySQL examples and a note on the difference. A live search box and topic filter narrow the list as you type. The converter does a best-effort rewrite in either direction, swapping SERIAL and AUTO_INCREMENT, CONCAT and ||, LIMIT/OFFSET ordering, CURDATE and CURRENT_DATE, BOOLEAN and TINYINT(1), ILIKE to LIKE, and identifier quoting between backticks and double quotes. Everything runs in your browser.

How to Use

  1. 1To convert a query, choose a direction (PostgreSQL -> MySQL or MySQL -> PostgreSQL).
  2. 2Paste your query into the source panel - the converted query updates live.
  3. 3Copy, download, or share the converted result from the output panel.
  4. 4To browse differences, type a keyword (such as json or upsert) into the search box.
  5. 5Narrow further with the Topic filter, then read the PostgreSQL and MySQL examples side by side with an explanatory note.

Key Benefits

  • Over two dozen side-by-side syntax topics with per-topic difference notes
  • Live search and topic filtering across every comparison row
  • Covers SERIAL vs AUTO_INCREMENT, || vs CONCAT, ILIKE vs LIKE, and more
  • Two-way query conversion between PostgreSQL and MySQL
  • Rewrites common type, function, pagination, and identifier differences
  • Examples for JSON access, upsert, arrays, CTEs, UUIDs, and window functions
  • A concise "when to choose which" summary for both databases
  • Shareable URL preserves the query, direction, and active filters
  • Fully in-browser - no server round-trips

Common Use Cases

  • Looking up how a PostgreSQL feature is written in MySQL (or vice versa)
  • Migrating a schema or query between the two databases
  • Settling a team debate over upsert or pagination syntax with a citation
  • Learning the dialect differences before choosing a database
  • Quickly drafting a MySQL version of a PostgreSQL query for a cross-DB feature

Convert a query

Best-effort, rule-based rewrite - review complex queries

Output will appear here.

Syntax comparison

30 topics

Auto Increment
Auto-incrementing primary key
PostgreSQL
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100)
);
MySQL
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100)
);
PostgreSQL uses SERIAL, MySQL uses AUTO_INCREMENT
String Concatenation
Concatenate strings
PostgreSQL
SELECT first_name || ' ' || last_name AS full_name
FROM users;
MySQL
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
PostgreSQL uses || operator, MySQL uses CONCAT()
Limit with Offset
Pagination query
PostgreSQL
SELECT * FROM users
LIMIT 10 OFFSET 20;
MySQL
SELECT * FROM users
LIMIT 20, 10;
Different syntax for OFFSET. Both support LIMIT x OFFSET y
Date/Time Functions
Get current timestamp
PostgreSQL
SELECT NOW();
SELECT CURRENT_TIMESTAMP;
SELECT CURRENT_DATE;
MySQL
SELECT NOW();
SELECT CURRENT_TIMESTAMP();
SELECT CURDATE();
Similar but with subtle differences in function names
String Pattern Matching
Case-insensitive LIKE
PostgreSQL
SELECT * FROM users
WHERE name ILIKE '%john%';
MySQL
SELECT * FROM users
WHERE name LIKE '%john%';
MySQL LIKE is case-insensitive by default. PostgreSQL has ILIKE
Regular Expressions
Regex pattern matching
PostgreSQL
SELECT * FROM users
WHERE email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$';
MySQL
SELECT * FROM users
WHERE email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$';
PostgreSQL uses ~, MySQL uses REGEXP
Boolean Type
Boolean data type
PostgreSQL
CREATE TABLE settings (
  id SERIAL PRIMARY KEY,
  is_active BOOLEAN DEFAULT TRUE
);
MySQL
CREATE TABLE settings (
  id INT AUTO_INCREMENT PRIMARY KEY,
  is_active TINYINT(1) DEFAULT 1
);
PostgreSQL has native BOOLEAN, MySQL uses TINYINT(1)
JSON Operations
Query JSON data
PostgreSQL
SELECT data->>'name' AS name
FROM users
WHERE data->>'age' > '25';
MySQL
SELECT JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) AS name
FROM users
WHERE JSON_EXTRACT(data, '$.age') > 25;
PostgreSQL has better JSON operators
Upsert (Insert or Update)
Insert or update if exists
PostgreSQL
INSERT INTO users (id, name, email)
VALUES (1, 'John', '[email protected]')
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email;
MySQL
INSERT INTO users (id, name, email)
VALUES (1, 'John', '[email protected]')
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  email = VALUES(email);
Different keywords: ON CONFLICT vs ON DUPLICATE KEY
Sequence/Identity
Generate sequence values
PostgreSQL
CREATE SEQUENCE user_id_seq;
SELECT nextval('user_id_seq');
MySQL
-- No direct sequence support
-- Use AUTO_INCREMENT or application logic
PostgreSQL has sequences, MySQL does not
Array Type
Array data type
PostgreSQL
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  tags TEXT[]
);

SELECT * FROM products
WHERE 'electronics' = ANY(tags);
MySQL
-- No native array type
-- Use JSON or separate table
CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tags JSON
);
PostgreSQL has native arrays, MySQL does not
Full Text Search
Full-text search
PostgreSQL
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('database & performance');
MySQL
SELECT * FROM articles
WHERE MATCH(content) AGAINST('database performance' IN NATURAL LANGUAGE MODE);
Different full-text search implementations
Case Expression
CASE statement
PostgreSQL
SELECT
  CASE
    WHEN age < 18 THEN 'Minor'
    WHEN age >= 18 AND age < 65 THEN 'Adult'
    ELSE 'Senior'
  END AS age_group
FROM users;
MySQL
SELECT
  CASE
    WHEN age < 18 THEN 'Minor'
    WHEN age >= 18 AND age < 65 THEN 'Adult'
    ELSE 'Senior'
  END AS age_group
FROM users;
Same syntax for CASE expressions
Window Functions
ROW_NUMBER() with partition
PostgreSQL
SELECT
  name,
  department,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank
FROM employees;
MySQL
SELECT
  name,
  department,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank
FROM employees;
MySQL 8.0+ supports window functions like PostgreSQL
Common Table Expressions (CTE)
WITH clause for recursive queries
PostgreSQL
WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id
  FROM employees
  WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, e.manager_id
  FROM employees e
  INNER JOIN subordinates s ON s.id = e.manager_id
)
SELECT * FROM subordinates;
MySQL
WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id
  FROM employees
  WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, e.manager_id
  FROM employees e
  INNER JOIN subordinates s ON s.id = e.manager_id
)
SELECT * FROM subordinates;
Both support recursive CTEs (MySQL 8.0+)
String Length
Get length of string
PostgreSQL
SELECT LENGTH('hello') AS len;
SELECT CHAR_LENGTH('hello') AS len;
MySQL
SELECT LENGTH('hello') AS len;
SELECT CHAR_LENGTH('hello') AS len;
LENGTH() counts bytes, CHAR_LENGTH() counts characters
Generate Series
Generate sequence of numbers
PostgreSQL
SELECT * FROM generate_series(1, 10);
SELECT * FROM generate_series(
  '2024-01-01'::DATE,
  '2024-01-31'::DATE,
  '1 day'::INTERVAL
);
MySQL
-- No direct equivalent
-- Use recursive CTE or temporary table
WITH RECURSIVE numbers AS (
  SELECT 1 AS n
  UNION ALL
  SELECT n + 1 FROM numbers WHERE n < 10
)
SELECT * FROM numbers;
PostgreSQL has built-in generate_series()
Return Inserted ID
Get last inserted ID
PostgreSQL
INSERT INTO users (name, email)
VALUES ('John', '[email protected]')
RETURNING id;
MySQL
INSERT INTO users (name, email)
VALUES ('John', '[email protected]');
SELECT LAST_INSERT_ID();
PostgreSQL uses RETURNING, MySQL uses LAST_INSERT_ID()
Update with Join
Update using JOIN
PostgreSQL
UPDATE orders o
SET status = 'shipped'
FROM customers c
WHERE o.customer_id = c.id
  AND c.country = 'USA';
MySQL
UPDATE orders o
JOIN customers c ON o.customer_id = c.id
SET o.status = 'shipped'
WHERE c.country = 'USA';
Different JOIN syntax in UPDATE statements
Delete with Join
Delete using JOIN
PostgreSQL
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.id
  AND c.country = 'USA';
MySQL
DELETE o FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.country = 'USA';
PostgreSQL uses USING, MySQL uses direct JOIN
Variable-length String
Unbounded text column
PostgreSQL
CREATE TABLE notes (
  id SERIAL PRIMARY KEY,
  body TEXT
);
MySQL
CREATE TABLE notes (
  id INT AUTO_INCREMENT PRIMARY KEY,
  body TEXT
);
Both support TEXT; PostgreSQL TEXT has no length penalty vs VARCHAR.
UUID Primary Key
UUID-based primary key with default
PostgreSQL
CREATE TABLE accounts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT
);
MySQL
CREATE TABLE accounts (
  id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
  name TEXT
);
PostgreSQL has a native UUID type; MySQL stores UUIDs as CHAR(36) or BINARY(16).
String Substring
Extract part of a string
PostgreSQL
SELECT SUBSTRING(name FROM 1 FOR 3) FROM users;
MySQL
SELECT SUBSTRING(name, 1, 3) FROM users;
PostgreSQL accepts SUBSTRING(x FROM a FOR b); both also accept SUBSTRING(x, a, b).
Cast Syntax
Type casting
PostgreSQL
SELECT '42'::INTEGER AS n;
SELECT CAST('42' AS INTEGER) AS n;
MySQL
SELECT CAST('42' AS SIGNED) AS n;
PostgreSQL has the :: cast shorthand; MySQL uses SIGNED/UNSIGNED for integer casts.
Date Formatting
Format a date as text
PostgreSQL
SELECT TO_CHAR(created_at, 'YYYY-MM-DD') FROM orders;
MySQL
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders;
PostgreSQL uses TO_CHAR with patterns; MySQL uses DATE_FORMAT with % specifiers.
Date Arithmetic
Add an interval to a date
PostgreSQL
SELECT NOW() + INTERVAL '7 days';
MySQL
SELECT NOW() + INTERVAL 7 DAY;
PostgreSQL quotes the interval string; MySQL uses an unquoted INTERVAL n UNIT form.
Default Timestamp
Auto-set created timestamp
PostgreSQL
created_at TIMESTAMPTZ DEFAULT NOW()
MySQL
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
PostgreSQL has TIMESTAMPTZ (timezone-aware); MySQL TIMESTAMP is UTC-stored.
Enum Type
Enumerated column
PostgreSQL
CREATE TYPE mood AS ENUM ('happy', 'sad');
CREATE TABLE t (m mood);
MySQL
CREATE TABLE t (
  m ENUM('happy', 'sad')
);
PostgreSQL enums are standalone types; MySQL declares ENUM inline on the column.
Show Tables
List tables in the database
PostgreSQL
\dt
-- or
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
MySQL
SHOW TABLES;
MySQL has SHOW TABLES; in psql use \dt or query the catalog.
Insert Multiple Rows
Bulk insert values
PostgreSQL
INSERT INTO tags (name) VALUES
  ('a'), ('b'), ('c');
MySQL
INSERT INTO tags (name) VALUES
  ('a'), ('b'), ('c');
Identical multi-row VALUES syntax in both databases.

When to choose which

PostgreSQL

Reach for it when correctness, rich types, and complex queries matter.

  • Native JSONB, arrays, ranges, and custom/enumerated types
  • Powerful window functions, CTEs, and full SQL standard coverage
  • Strict typing and robust constraints catch data bugs early
  • Extensions like PostGIS, pgvector, and full-text search built in

MySQL

Reach for it for simple read-heavy workloads and ubiquitous hosting.

  • Extremely widely deployed; cheap, well-understood managed hosting
  • Simple replication and a large ecosystem of tooling
  • Fast for read-mostly, key-lookup style workloads
  • Familiar to most LAMP-stack and shared-hosting environments

Was this tool helpful?

Share Your Experience

Help others discover this tool!

Related tools