Rails - 以查询变量为范围的复杂 sql 查询
Rails - Complicated sql query with query variables as scope
我需要 Rails 范围内的帮助。
我有以下 postgresql 查询:
select
name,
coalesce(esp.description, eng.description) Description
from games g
left join
(select game_id, description from details where locale = 'en-GB') eng
on g.id = eng.game_id
left join
(select game_id, description from details where locale = 'es-ES')
esp on g.id = esp.game_id
它的作用基本上是获取游戏的名称(来自 table 'games')和描述(来自另一个名为 'details' 的 table),如果没有西班牙语说明,需要英语说明。
我把两个table都留在这里:
Games
id
name
Details
locale
description
game_id
你可以用两个预制游戏测试我之前提到的查询(一个有西班牙语翻译,另一个没有):
https://dbfiddle.uk/?rdbms=postgres_13&fiddle=381eb932e859dcfa9105d5d79a2c9c63
我希望在检索 rails 中的游戏时获得与示波器相同的结果。
当我尝试时:
scope :filter_by_description,
-> {
order("description, coalesce(currnt.locale, fallbck.locale) Locale FROM custom_apps c
LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'de-DE') currnt on c.id = currnt.custom_app_id
LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'en-GB') fallbck on c.id = fallbck.custom_app_id")
}
我刚收到以下错误:
ActiveRecord::UnknownAttributeReference at /games
Query method called with non-attribute argument(s): "order("description, coalesce(currnt.locale, fallbck.locale) Locale FROM custom_apps c LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'de-DE') currnt on c.id = currnt.custom_app_id LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'en-GB') fallbck on c.id = fallbck.custom_app_id")"
一种方法是通过两个横向子查询:
class Game < ApplicationRecord
has_many :details
# Gets games with a localized description
# @param [String] preferred - the preferred locale for the description
# @param [String] fallback - the fallback locale for the description
# @return [ActiveRecord::Relation]
def self.with_localized_description(preferred: 'es-ES', fallback: 'en-GB')
# This is the subquery we want to run for each row on the games table
subquery = Detail.select(:description)
.where(Detail.arel_table[:game_id].eq(arel_table[:id]))
.limit(1)
select(
arel_table[Arel.star],
# Feel free to use whatever alias you want
'COALESCE(preferred_locale.description, fallback_locale.description) AS description'
)
.outer_lateral_joins(subquery.where(locale: preferred), name: 'preferred_locale')
.outer_lateral_joins(subquery.where(locale: fallback), name: 'fallback_locale')
end
# @param [Arel::SelectCore] - subquery
# @param [String] name - the alias used for this table
# @return [ActiveRecord::Relation]
def self.outer_lateral_joins(subquery, name: '')
# Rails doesn't have a decent way to write LEFT OUTER JOIN LATERAL - this is kind of hacky but works
joins("LEFT OUTER JOIN LATERAL (#{subquery.to_sql}) #{name} ON true")
end
end
最简单的描述方式是它就像一个 SQL foreach 循环。每个横向连接都告诉 Postgres 从游戏 table.
的每一行的详细信息 table 中获取一行
由于这是一个横向连接,您可以在组、顺序或您想要的任何子句中使用 preferred_locale
和 fallback_locale
,就像处理游戏中的专栏一样 table. Postgres 还允许您在 order 子句中使用 selected 列。
例如:
Game.with_localized_description.order('fallback_locale.description')
Game.with_localized_description.order(:description)
ActiveRecord 还使 select 中的任何别名列可用作记录的属性:
irb(main):047:0> mario = Game.with_localized_description.find_by(name: 'Mario Cart')
Game Load (0.5ms) SELECT "games".*, COALESCE(preferred_locale.description, fallback_locale.description) AS description FROM "games" LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'es-ES' LIMIT 1) preferred_locale ON true LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'en-GB' LIMIT 1) fallback_locale ON true WHERE "games"."name" = LIMIT [["name", "Mario Cart"], ["LIMIT", 1]]
=>
#<Game:0x00007fec80133700
...
irb(main):048:0> mario.description
=> "Un buen juego de carreras."
irb(main):049:0> Game.with_localized_description.order(:description)
Game Load (0.9ms) SELECT "games".*, COALESCE(preferred_locale.description, fallback_locale.description) AS description FROM "games" LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'es-ES' LIMIT 1) preferred_locale ON true LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'en-GB' LIMIT 1) fallback_locale ON true ORDER BY "description" ASC
=>
[#<Game:0x000056162a59a3d0
id: 1,
name: "Mario Cart",
created_at: Wed, 23 Mar 2022 12:11:41.658592000 UTC +00:00,
updated_at: Wed, 23 Mar 2022 12:11:41.658592000 UTC +00:00>]
此方法未写为“作用域”,因为作用域方法只是用于编写 class 方法的语法糖。将此代码干扰到 lambda 中只会破坏代码的可读性。
我需要 Rails 范围内的帮助。
我有以下 postgresql 查询:
select
name,
coalesce(esp.description, eng.description) Description
from games g
left join
(select game_id, description from details where locale = 'en-GB') eng
on g.id = eng.game_id
left join
(select game_id, description from details where locale = 'es-ES')
esp on g.id = esp.game_id
它的作用基本上是获取游戏的名称(来自 table 'games')和描述(来自另一个名为 'details' 的 table),如果没有西班牙语说明,需要英语说明。
我把两个table都留在这里:
Games |
---|
id |
name |
Details |
---|
locale |
description |
game_id |
你可以用两个预制游戏测试我之前提到的查询(一个有西班牙语翻译,另一个没有): https://dbfiddle.uk/?rdbms=postgres_13&fiddle=381eb932e859dcfa9105d5d79a2c9c63
我希望在检索 rails 中的游戏时获得与示波器相同的结果。
当我尝试时:
scope :filter_by_description,
-> {
order("description, coalesce(currnt.locale, fallbck.locale) Locale FROM custom_apps c
LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'de-DE') currnt on c.id = currnt.custom_app_id
LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'en-GB') fallbck on c.id = fallbck.custom_app_id")
}
我刚收到以下错误:
ActiveRecord::UnknownAttributeReference at /games
Query method called with non-attribute argument(s): "order("description, coalesce(currnt.locale, fallbck.locale) Locale FROM custom_apps c LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'de-DE') currnt on c.id = currnt.custom_app_id LEFT JOIN (SELECT * FROM custom_app_details WHERE locale = 'en-GB') fallbck on c.id = fallbck.custom_app_id")"
一种方法是通过两个横向子查询:
class Game < ApplicationRecord
has_many :details
# Gets games with a localized description
# @param [String] preferred - the preferred locale for the description
# @param [String] fallback - the fallback locale for the description
# @return [ActiveRecord::Relation]
def self.with_localized_description(preferred: 'es-ES', fallback: 'en-GB')
# This is the subquery we want to run for each row on the games table
subquery = Detail.select(:description)
.where(Detail.arel_table[:game_id].eq(arel_table[:id]))
.limit(1)
select(
arel_table[Arel.star],
# Feel free to use whatever alias you want
'COALESCE(preferred_locale.description, fallback_locale.description) AS description'
)
.outer_lateral_joins(subquery.where(locale: preferred), name: 'preferred_locale')
.outer_lateral_joins(subquery.where(locale: fallback), name: 'fallback_locale')
end
# @param [Arel::SelectCore] - subquery
# @param [String] name - the alias used for this table
# @return [ActiveRecord::Relation]
def self.outer_lateral_joins(subquery, name: '')
# Rails doesn't have a decent way to write LEFT OUTER JOIN LATERAL - this is kind of hacky but works
joins("LEFT OUTER JOIN LATERAL (#{subquery.to_sql}) #{name} ON true")
end
end
最简单的描述方式是它就像一个 SQL foreach 循环。每个横向连接都告诉 Postgres 从游戏 table.
的每一行的详细信息 table 中获取一行由于这是一个横向连接,您可以在组、顺序或您想要的任何子句中使用 preferred_locale
和 fallback_locale
,就像处理游戏中的专栏一样 table. Postgres 还允许您在 order 子句中使用 selected 列。
例如:
Game.with_localized_description.order('fallback_locale.description')
Game.with_localized_description.order(:description)
ActiveRecord 还使 select 中的任何别名列可用作记录的属性:
irb(main):047:0> mario = Game.with_localized_description.find_by(name: 'Mario Cart')
Game Load (0.5ms) SELECT "games".*, COALESCE(preferred_locale.description, fallback_locale.description) AS description FROM "games" LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'es-ES' LIMIT 1) preferred_locale ON true LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'en-GB' LIMIT 1) fallback_locale ON true WHERE "games"."name" = LIMIT [["name", "Mario Cart"], ["LIMIT", 1]]
=>
#<Game:0x00007fec80133700
...
irb(main):048:0> mario.description
=> "Un buen juego de carreras."
irb(main):049:0> Game.with_localized_description.order(:description)
Game Load (0.9ms) SELECT "games".*, COALESCE(preferred_locale.description, fallback_locale.description) AS description FROM "games" LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'es-ES' LIMIT 1) preferred_locale ON true LEFT OUTER JOIN LATERAL (SELECT * FROM "details" WHERE "details"."game_id" = "games"."id" AND "details"."locale" = 'en-GB' LIMIT 1) fallback_locale ON true ORDER BY "description" ASC
=>
[#<Game:0x000056162a59a3d0
id: 1,
name: "Mario Cart",
created_at: Wed, 23 Mar 2022 12:11:41.658592000 UTC +00:00,
updated_at: Wed, 23 Mar 2022 12:11:41.658592000 UTC +00:00>]
此方法未写为“作用域”,因为作用域方法只是用于编写 class 方法的语法糖。将此代码干扰到 lambda 中只会破坏代码的可读性。