【问题标题】:Rails left_outer_joins multiple conditionsRails left_outer_joins 多个条件
【发布时间】:2021-07-17 20:03:48
【问题描述】:

我有这个 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';

我想要做的是获取项目表中不存在的所有语言,例如items.version_id 是“1.0”。因此,为了简单起见,可以说语言表包含英语、法语和德语,并且项目只有一行,版本为“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 查询中。

【问题讨论】:

    标签: ruby-on-rails join activerecord


    【解决方案1】:

    您需要在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" != ? 
    

    或者 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?

    【讨论】:

      【解决方案2】:

      您需要在查询中使用一些自定义 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)})
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-18
        • 2016-12-02
        • 2014-09-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多