【问题标题】:Delete using left outer join in Postgres在 Postgres 中使用左外连接删除
【发布时间】:2014-02-09 17:56:00
【问题描述】:

我正在将数据库从 MySQL 切换到 Postgres SQL。在 MySQL 中有效的选择查询在 Postgres 中有效,但类似的删除查询无效。

我有两个数据表,其中列出了某些备份文件的位置。现有数据(ed)和新数据(nd)。此语法将挑选出可能说明文件在现有数据表中的位置的现有数据,将其与相同的文件名和路径进行匹配,但没有关于它在新数据中的位置的信息:

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;

我希望使用以下语法运行删除查询:

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;

我尝试过DELETE edDELETE ed.*,它们都渲染syntax error at or near "ed"。如果我尝试不使用 ed 的别名,则会出现类似错误。如果我尝试

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

Postgres 发回syntax error at or near "LEFT"

我很困惑,在使用特定于 psql 的连接的删除查询上找不到太多内容。

【问题讨论】:

标签: postgresql


【解决方案1】:

正如其他人所指出的,您不能直接在 DELETE 语句中使用 LEFT JOIN。但是,您可以使用 USING 语句对目标表的主键进行自联接,然后针对该自联接表进行左联接。

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;

请注意 WHERE 子句中 tv_episodes.id 上的自连接。这样就避免了上面提供的子查询路由。

【讨论】:

  • 感谢您。在我的情况下,这适用于大约 100K 行的几个表。 explain 为其他答案中建议的子查询方法给出的“总成本”为 337448542.97(!),耗时 9 分钟。此方法的成本为 40464.59,耗时 6.7 秒。
  • 如果只有一个连接在使用中,您认为您是否应该提供自连接条件...比如我们要连接的只是 file_name 吗?
  • 我错过了你的“笔记”,不小心从生产网站的表格中删除了所有内容:(对于来自 MySQL 或 SQL Server 的用户来说,这真的很不明显
  • 这是一个奇怪的例子。数据表应该在 USING 子句中,并且完全没有左连接
  • 谢谢,这解决了我的问题。不过,首先使用“USING”语法有点讽刺,但我发现“从表左连接中删除表...”的语法,比如 mysql,分配更方便。
【解决方案2】:

正如 bf2020 指出的那样,postgres 在执行 DELETE 查询时不支持 JOIN。子查询的提议解决方案让我想到了解决方案。从上面优化 SELECT 查询并将其用作 DELETE 查询语句的子查询:

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
);

对于某些数据库系统,尤其是 MySQL,子查询通常会消耗时间和 CPU 资源效率低下。根据我的经验,我尽量避免使用子查询,因为这种查询效率低下,而且此类查询有时是磨练技能(如学习 JOIN 语法)的简单方法。

由于 postgre 不允许使用 join 删除查询,以上是可行的解决方案。

【讨论】:

  • Postgres 在进行DELETE 查询时不支持JOINs。废话,确实如此,尽管语法很奇怪。 DELETE FROM ... USING ...。请参阅手册。但是,它不支持对要删除的表进行左连接,这是真的。
  • @dwlamb - 如果你不接受我的回答,你应该支持它,因为它让你走上正轨。 meta.stackexchange.com/questions/5234/…
【解决方案3】:

使用DELETE... USING 语法:

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;

【讨论】:

  • 就我而言,我必须执行 Joshua Burgner 建议的自我加入以避免删除所有内容。幸运的是,我首先在测试数据库中进行了尝试。
【解决方案4】:

代替

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;

请尝试

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)
  )
  ;

根据 postgresql 文档,JOINDELETE 查询中无效。您可能需要连接 IN 表达式的左右部分。

【讨论】:

  • 您的解决方案中存在一些语法错误(FROM 之前的逗号和明确声明特定字段的 JOIN)。我只为遇到此挑战并寻找解决方案的其他人注意这些。请参阅上面对我的困境的上述修订。您的建议给了我解决方案。
  • 对于 PostgreSQL,连接删除是使用 DELETE FROM ... USING ... 完成的,但目前无法针对删除目标表 AFAIK 表达 left outer join
猜你喜欢
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
  • 2015-03-06
  • 2014-03-26
  • 2019-05-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多