Rails left_outer_joins 多个条件

Rails left_outer_joins multiple conditions

我有这个 sql 查询,我正试图将其转换为 ActiveRecord 查询:

SELECT DISTINCT name FROM `languages` 
  LEFT OUTER JOIN `items` ON `items`.`language` = `languages.id` 
  AND (`items`.`version_id` = '1.0') 
  WHERE `languages`.`id` != 'en';

我想要做的是获取项目 table 中不存在的所有语言,其中 items.version_id 是例如“1.0”。因此,为了简单起见,语言 table 包含英语、法语和德语,并且这些项目只有一行,版本为“1.0”,语言为 'en',然后我应该用上面的方法取回法语和德语查询。

(我不是数据库专家,所以如果有人有比上面更好的查询,我洗耳恭听)。

我有以下型号:

# item.rb
class Item < ApplicationRecord
  belongs_to :language
  belongs_to :version
end

# language.rb
class Language < ApplicationRecord
  has_many :items
end

# version.rb
class Version < ApplicationRecord
  has_many :items
end 

我尝试了几种不同的方法,但都没有奏效,例如在控制台上:

Language.left_joins(:items).where(items: {version_id: '1.0'})
  .where.not(languages: {id: Item.distinct.pluck(:language_id)})

这会生成以下两个导致空集的查询:

SELECT DISTINCT `items`.`language_id` FROM `items`
=>   Language Exists (0.5ms)  SELECT  1 AS one FROM `languages` 
  LEFT OUTER JOIN `items` ON `items`.`language_id` = `languages`.`id` 
  WHERE `items`.`version_id` = '1.0' 
  AND (`languages`.`id` != 'en') LIMIT 1

注意 WHEREAND 的顺序是如何交换的。

所以我显然没有正确构建查询,很想知道如何以 rails 方式处理它。是否也可以将查询减少到一个数据库查询?

我是 Rails 的新手,仍在学习范围等,所以我将所有内容捆绑到一个 ActiveRecord 查询中。

您需要在查询中使用一些自定义 SQL,至少我看不到其他方法。同样使用 select 而不是 pluck 会将其转换为子查询。

Language
  .joins("LEFT JOINS items on items.language_id = languages.id 
          AND items.version_id = '1.0'")
  .where.not(languages: {id: Item.distinct.select(:language_id)})

您需要在 items.id 上添加条件以过滤结果行:

languages, items = Language.arel_table, Item.arel_table
conditions = items[:language_id].eq(languages[:id])
                                .and(items[:version_id].eq('1.0'))
j = languages.outer_join(items)
             .on(conditions).join_sources

Language.joins(j)
        .where(
          items: { id: nil }
        )
        .where.not(language: { id: 'en'})
SELECT "languages".*
FROM   "languages"
       LEFT OUTER JOIN "items"
                    ON "items"."language_id" = "languages"."id"
                       AND "items"."version_id" = '1.0'
WHERE  "items"."id" IS NULL
       AND "languages"."id" != ? 

您也可以使用 WHERE languages.id NOT IN(subquery):

Language.where.not(
  id: Language.joins(:items)
              .where(items: { version_id: '1.0' })
).where.not(id: 'en')
SELECT "languages".*
FROM   "languages"
WHERE  "languages"."id" NOT IN (SELECT "languages"."id"
                                FROM   "languages"
                                       INNER JOIN "items"
                                               ON "items"."language_id" =
                                                  "languages"."id"
                                WHERE  "items"."version_id" = ?)
       AND "languages"."id" != ? 

Or NOT EXISTS 可能有也可能没有性能优势,具体取决于您的 RDBMS:

Language.where(
  Item.select(:id)
      .where(
          # items.version_id = languages.id
          Item.arel_table[:language_id].eq(Language.arel_table[:id])
       )
       .where(items: { version_id: '1.0'})
       .arel
       .exists.not

).where.not(id: 'en')
SELECT "languages".*
FROM   "languages"
WHERE  NOT ( EXISTS (SELECT "items"."id"
                     FROM   "items"
                     WHERE  "items"."language_id" = "languages"."id"
                            AND "items"."version_id" = ?) )
       AND "languages"."id" != ?
LIMIT  ? 

Difference between EXISTS and IN in SQL?