如何以 Rails 的方式写一个 SQL NOT EXISTS query/scope?

How to write an SQL NOT EXISTS query/scope in the Rails way?

我有一个数据库范围来过滤特定 Proxyenvironment 的最新 ProxyConfig 版本。

这是与 MySQL、PostgreSQL 和 Oracle:

配合得很好的原始 SQL
class ProxyConfig < ApplicationRecord
  ...
  scope :current_versions, -> do
    where %(NOT EXISTS (
      SELECT 1 FROM proxy_configs pc
      WHERE proxy_configs.environment = environment
        AND proxy_configs.proxy_id = proxy_id
        AND proxy_configs.version < version
      ))
  end
  ...
end

你可以在我的 baby_squeel issue.

中找到一个简单的测试用例

但我发现最好不要直接使用 SQL。我花了很多时间尝试不同的方法以 Rails 的方式编写它,但无济于事。我找到了通用的 Rails 和 baby_squeel 示例,但它们总是涉及不同的表。

PS 以前的版本使用 joins 但是它非常慢并且搞乱了一些查询。例如 #count 产生了 SQL 语法错误。所以我对使用其他方法不是很开放。相反,我更愿意知道如何准确地实现这个查询。尽管我至少很想看看其他简单的解决方案。

PPS关于直接SQL可以的问题。在这种情况下,大部分是。也许所有 RDBMS 都能理解这句话。如果需要比较 text 字段,虽然这需要 Oracle 上的特殊功能。在 Postgres 上,不区分大小写的 LIKEILIKE。它可以由 Arel 自动处理。在原始 SQL 中,不同的 RDBMS 需要不同的字符串。

这实际上不是您可以单独使用 ActiveRecord 查询接口构建的查询。不过可以通过少量的 Arel 来完成:

class ProxyConfig < ApplicationRecord
  def self.current_versions
    pc = arel_table.alias("pc")
    where(
      unscoped.select(1)
         .where(pc[:environment].eq(arel_table[:environment]))
         .where(pc[:proxy_id].eq(arel_table[:proxy_id]))
         .where(pc[:version].gt(arel_table[:version]))
         .from(pc)
         .arel.exists.not
    )
  end
end

生成的 SQL 不相同,但我认为它在功能上应该是等效的。

SELECT "proxy_configs".* FROM "proxy_configs" 
WHERE NOT (
  EXISTS (
    SELECT 1 FROM "proxy_configs" "pc" 
    WHERE "pc"."environment" = "proxy_configs"."environment" 
    AND "pc"."proxy_id" = "proxy_configs"."proxy_id" 
    AND "pc"."version" > "proxy_configs"."version"
  )
)