NOT EXISTS
SELECT m."memberID",
m.lastname
FROM MEMBERS m
WHERE NOT EXISTS (SELECT NULL
FROM MEMBERS b
WHERE b.lastname ~* '[a-zA-z]+([-][a-zA-Z]+)*'
AND b."memberID" = m."memberID");
LEFT JOIN / IS NULL
SELECT m."memberID",
m.lastname
FROM MEMBERS m
LEFT JOIN MEMBERS b ON b."memberID" = m."memberID"
AND b.lastname ~* '[a-zA-z]+([-][a-zA-Z]+)*'
WHERE b."memberID" IS NULL
Summary
Quote:
PostgreSQL treats LEFT JOIN and NOT EXISTS equally, using same execution plan for both of them (namely a Hash Anti Join for the example above).
As for NOT IN, which is semantically different since its logic is trivalent and it can return NULL, PostgreSQL tries to take this into account and limits itself to using a filter against a subplan (a hashed subplan for a hashable resultset like in example above).
Since it need to search the hash table for each missing value twice (first time to find the value, second time to find a NULL), this method is a little less efficient.
A plain subplan, which the optimizer can resort to any time it decides the list will not fit into the memory, is very inefficient and the queries that have possibility of using it should be avoided like a plague.
That’s why in PostgreSQL 8.4 one should always use LEFT JOIN / IS NULL or NOT EXISTS rather than NOT IN to find the missing values.
Addendum
But as Andrew Lazarus points out, if there are no duplicates of memberid in the MEMBERS table, the query only needs to be:
SELECT m."memberID",
m.lastname
FROM MEMBERS m
WHERE b.lastname ~* '[a-zA-z]+([-][a-zA-Z]+)*'