54

I am switching a database from MySQL to Postgres SQL. A select query that worked in MySQL works in Postgres but a similar delete query does not.

I have two tables of data which list where certain back-up files are located. Existing data (ed) and new data (nd). This syntax will pick out existing data which might state where a file is located in the existing data table, matching it against equal filename and path, but no information as to where it is located in the new data:

SELECT ed.id, ed.file_name, ed.cd_name, ed.path, nd.cd_name
FROM tv_episodes AS ed
LEFT OUTER JOIN data AS nd ON
ed.file_name = nd.file_name AND 
ed.path = nd.path
WHERE ed.cd_name = 'MediaLibraryDrive' AND nd.cd_name IS NULL;

I wish to run a delete query using this syntax:

DELETE ed
FROM tv_episodes AS ed
LEFT OUTER JOIN data AS nd ON
ed.file_name = nd.file_name AND 
ed.path = nd.path
WHERE ed.cd_name = 'MediaLibraryDrive' AND nd.cd_name IS NULL;

I have tried DELETE ed and DELETE ed.* both of which render syntax error at or near "ed". Similar errors if I try without the alias of ed. If I attempt

DELETE FROM tv_episodes AS ed
LEFT  JOIN data AS nd.....

Postgres sends back syntax error at or near "LEFT".

I'm stumped and can't find much on delete queries using joins specific to psql.

kraxor
  • 649
  • 8
  • 16
dwlamb
  • 885
  • 2
  • 7
  • 14
  • 1
    possible duplicate of [PostgreSQL: how to delete rows using a join](http://stackoverflow.com/questions/16034253/postgresql-how-to-delete-rows-using-a-join) – cimmanon Jul 22 '14 at 14:30
  • 1
    Worth checking out the question linked by @cimmanon (regardless of whether it's a duplicate or not) - the `NOT EXISTS` syntax works better for me in my situation than the answers here. – GregHNZ Jun 27 '18 at 00:21

4 Answers4

106

As others have noted, you can't LEFT JOIN directly in a DELETE statement. You can, however, self join on a primary key to the target table with a USING statement, then left join against that self-joined table.

Note the self join on tv_episodes.id in the WHERE clause. This avoids the sub-query route provided above.

DELETE FROM tv_episodes
USING tv_episodes AS ed
LEFT OUTER JOIN data AS nd ON
   ed.file_name = nd.file_name AND 
   ed.path = nd.path
WHERE
   tv_episodes.id = ed.id AND
   ed.cd_name = 'MediaLibraryDrive' AND nd.cd_name IS NULL;
Freek de Bruijn
  • 3,552
  • 2
  • 22
  • 28
Joshua Burgner
  • 1,085
  • 1
  • 7
  • 4
  • 8
    thanks for this. this works well in my case with a couple tables of ~100K rows. The "total cost" given by `explain` for the subquery methods suggested in the other answers was 337448542.97 (!) and took over 9 minutes. This method had a cost of 40464.59 and took 6.7 seconds. – sbeam Jan 07 '16 at 04:05
  • Do you think you should provide the self join condition if there was just one join in using... say if it was just file_name that we are joining with? – Nishant Mar 23 '19 at 13:21
  • 31
    I missed your "note" and accidentally deleted everything from the table on production website :( it's really non-obvious especially for users coming from MySQL or SQL Server – Stalinko Apr 03 '19 at 06:53
  • 2
    This is weird example. data table should be in USING clause, and no left join at all – urmurmur Nov 06 '20 at 18:49
  • 1
    Thanks, this fixed my issue. Kind of makes a mockery of having the 'USING' syntax in the first place though, I find the "DELETE table FROM table LEFT JOIN..." syntax of, say, mysql, allot more convenient. – Giles Oct 11 '21 at 10:16
15

As bf2020 points out, postgres does not support JOINs when conducting a DELETE query. The proposed solution of a sub-query made me think of the solution. Refine the SELECT query from above and employ it as a sub-query to a DELETE query statement:

DELETE FROM tv_episodes 
WHERE id in (
    SELECT ed.id
    FROM tv_episodes AS ed
    LEFT OUTER JOIN data AS nd ON
    ed.file_name = nd.file_name AND 
    ed.path = nd.path
    WHERE ed.cd_name = 'MediaLibraryDrive' AND nd.cd_name IS NULL
);

Sub-queries can often be inefficient consuming time and CPU resources with some database systems, especially MySQL. From my experience I try to avoid using a sub-query due to that inefficiency plus that such queries are sometimes an easy way out to honing one's skill like learning JOIN syntax.

Since postgre does not permit delete queries using join, the above is the solution that works.

dwlamb
  • 885
  • 2
  • 7
  • 14
  • 4
    *Postgres does not support `JOIN`s when conducting a `DELETE` query*. Nonsense, it does, though the syntax is weird. `DELETE FROM ... USING ...`. See the manual. However, it doesn't support left-joining against the table to be deleted from, that's true. – Craig Ringer Feb 10 '14 at 00:29
  • 1
    @dwlamb - if you aren't accepting my answer you should upvote it because it put you on the right track. http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – bf2020 Feb 10 '14 at 15:18
5

Use the DELETE... USING syntax:

DELETE FROM tv_episodes USING data WHERE 
tv_episodes.file_name = data.file_name AND 
tv_episodes.path = data.path AND 
tv_episodes.cd_name = 'MediaLibraryDrive' AND 
data.cd_name IS NULL;
Meow
  • 178
  • 2
  • 7
  • In my case, I had to do the self join that Joshua Burgner proposed to avoid deleting everything. Luckily I tried it in a testing database first. – Alec Gerona Dec 03 '21 at 06:45
3

Instead of

DELETE ed
FROM tv_episodes AS ed
LEFT OUTER JOIN data AS nd ON
ed.file_name = nd.file_name AND 
ed.path = nd.path
WHERE ed.cd_name = 'MediaLibraryDrive' AND nd.cd_name IS NULL;

please try

DELETE FROM tv_episodes
WHERE cd_name = 'MediaLibraryDrive' AND 
  (tv_episodes.filename, tv_episodes.path IN
    (SELECT ed.filename, 
    ed.path 
    FROM tv_episodes AS ed 
    INNER JOIN data AS nd 
      ON ed.file_name = nd.file_name 
        AND ed.path = nd.path
    WHERE nd.cd_name IS NULL)
  )
  ;

JOIN is not valid in a DELETE query according to the postgresql documentation. You might need to concatenate the left and right parts of the IN expression.

bf2020
  • 742
  • 4
  • 7
  • There were some syntax errors in your solution (a comma before FROM and clear declaration of the JOIN on specific fields). I only note these for someone else that comes across this challenge looking for a solution. Kindly see the above revision of my dilemma above. Your advice gave me the solution. – dwlamb Feb 09 '14 at 20:56
  • 1
    For PostgreSQL a joined delete is done with `DELETE FROM ... USING ...`, but there's currently no way to express a `left outer join` against the delete target table AFAIK. – Craig Ringer Feb 10 '14 at 00:29