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
注意 WHERE
和 AND
的顺序是如何交换的。
所以我显然没有正确构建查询,很想知道如何以 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?。
我有这个 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
注意 WHERE
和 AND
的顺序是如何交换的。
所以我显然没有正确构建查询,很想知道如何以 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?。