Table of contents

  1. Avoid worst MySQL quirks
    1. Everyone else's SQL
  2. Functions
  3. Types
  4. Upsert
  5. Triggers

For trivial CRUD applications it might be enough to use a database abstraction layer, but anything more complicated needs conversion.

Avoid worst MySQL quirks

First ensure that the application works in MySQL's “ANSI” mode. Start every connection with:

SET SESSION sql_mode='ANSI';
SET NAMES utf8;

It's like DOCTYPE for MySQL. Disables all worst quirks and makes it do best effort to support standard SQL syntax. This allows basic queries to be compatible with both MySQL and PostgreSQL, but you'll have to remove MySQL-isms from the queries first:

Everyone else's SQL

Functions

If you want to keep MySQL portability or just be done with porting quickly, try mysqlcompat — a reimplementation of MySQL-specific functions in PostgreSQL.

I don't recommend importing all of them at once (MySQL has some crazy stuff, and mysqlcompat will shadow PostgreSQL's built-in functions/operators), but picked selectively are a huge time-saver.

Types

Helpful mapping of MySQL to PostgreSQL types.

Upsert

Kill me now. Unfortunately PostgreSQL doesn't have REPLACE INTO, INSERT IGNORE or ON DUPLICATE KEY UPDATE.

It pretends there's no problem, because in v9 it has MERGE (all hail SQL 2003 standard!), but that's only a partial solution, and its syntax is awful. Still, if you can, use MERGE, because none its alternatives are any better.

REPLACE INTO can be emulated with DELETE and INSERT in a transaction (just be careful not to delete too much).

For INSERT IGNORE use temporary table, insert all data into it, and then copy with INSERT … SELECT statement that excludes duplicates.

If you always INSERT IGNORE on a table, then try this:

CREATE OR REPLACE RULE insert_ignore AS ON INSERT TO table
WHERE NEW.id = OLD.id
DO INSTEAD NOTHING;

Here's one way to emulate ON DUPLICATE KEY UPDATE in Pg/PLSQL (in a trigger or a function):

UPDATE foo SET bar=NEW.bar WHERE baz=NEW.baz
IF NOT FOUND THEN
  BEGIN
    INSERT INTO foo(bar,baz) VALUES (NEW.bar,NEW.baz);
    EXCEPTION WHEN unique_violation THEN
      -- Oops, race condition. You should have locked the table.
    END;
  END IF;
END;

The same thing can be emulated in application logic by checking number of rows that UPDATE query touched or even using UPDATE … RETURNING id. It sucks, but so does PostgreSQL's upsert support.

Triggers

This:

DELIMITER ;;
CREATE TRIGGER foo BEFORE INSERT ON table FOR EACH ROW
SET NEW.foo = 'bar'
;;
DELIMITER ;

changes to this:

CREATE LANGUAGE plpgsql; -- needed only once
CREATE OR REPLACE FUNCTION foo() RETURNS trigger AS
$BODY$
    BEGIN
        NEW.foo := 'bar';
        return NEW;
    END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER foo BEFORE INSERT ON table FOR EACH ROW EXECUTE PROCEDURE foo();