复杂的 Rails 查询 - 联合? 子选择? 我还可以使用named_scope吗?

发布于 2024-07-25 11:48:08 字数 3692 浏览 7 评论 0原文

我喜欢 Rails 的部分原因是我讨厌 SQL - 我认为它更像是一种汇编语言,应该使用 ActiveRecord 等更高级别的工具进行操作。 然而,我似乎已经达到了这种方法的极限,而且我对 SQL 的了解已经超出了我的能力范围。

我有一个包含很多子记录的复杂模型。 我还有一组 30-40 个命名范围,用于实现客户端的业务逻辑。 这些作用域有条件地链接在一起,这就是为什么我有这些 joins_ 作用域,这样连接就不会被破坏。

我有几个不能正常工作的产品,或者至少不能按照客户希望的方式工作。 这是模型结构的粗略想法,其中有一些命名范围(示例中并非全部需要)来说明我的方法并指出我的问题。 (请原谅任何语法错误)

class Man < ActiveRecord::Base
  has_many :wives

  named_scope :has_wife_named       lambda { |n| { :conditions => { :wives => {:name => n}}}}
  named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}}
  named_scope :has_yw_named_v2      lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}}
  named_scope :joins_wives         :joins => :wives

  named_scope :has_red_cat          :conditions => { :cats => {:color => 'red'}}        
  named_scope :has_cat_of_color     lambda { |c| { :conditions => { :cats => {:color => c}}}}
  named_scope :has_7yo_cat          :conditions => { :cats => {:age => 7}}
  named_scope :has_cat_of_age       lambda { |a| { :conditions => { :cats => {:age => a}}}}
  named_scope :has_cat_older_than   lambda { |a| { :conditions => ["cats.age > ?", a] }}
  named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }}
  named_scope :has_cat_fatter_than  lambda { |w| { :conditions => ["cats.weight > ?", w] } }
  named_scope :joins_wives_cats     :joins => {:wives => :cats}
end

class Wife < ActiveRecord::Base
  belongs_to :man
  has_many :cats
end

class Cat < ActiveRecord::Base
  belongs_to :wife
end
  1. 我可以找到妻子养了七岁红猫的男人

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => '不同的男人'}) 
      

    我什至可以找到妻子养有超过 20 磅且超过 6 岁的猫的男人

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => '不同的男人'}) 
      

    但这不是我想要的。 我想找到其妻子中至少有一只红猫和一只七岁猫的男人,这些猫不必是同一只猫,或者找到其妻子中至少有一只超过给定重量的猫的男人,并且一只超过特定年龄的猫。
    (在后续示例中,请假设存在适当的 joins_DISTINCT

  2. 我可以找到妻子名为 Esther 的男人

    @men = Man.has_wife_named('Esther') 
      

    我什至可以找到妻子名叫 Esther、Ruth 或 Ada 的男人(太棒了!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada']) 
      

    但我想找到妻子名叫以斯帖、露丝和艾达的男人。

  3. 哈哈,开个玩笑,其实我需要这个:我可以找到30岁以下有老婆的男人,名字叫Esther

    @men = Man.has_young_wife_named('Esther') 
      

    寻找年轻妻子名叫埃丝特、露丝或艾达的男人

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada']) 
      

    但如上所述,我想找到有年轻妻子的男人,名叫埃丝特、露丝和艾达。 幸运的是,在这种情况下,最低年龄是固定的,但最好也指定最低年龄。

  4. 有没有办法用哈希语法测试不等式,或者您总是必须恢复到 :conditions => ["", n] - 请注意 has_young_wife_namedhas_yw_named_v2 之间的区别 - 我更喜欢第一个,但范围仅适用于有限值。 如果您正在寻找一位年老的妻子,我想您可以使用a..100,但当妻子年满 101 岁时,她就会放弃搜索。 (嗯。她会做饭吗?j/k)

  5. 有没有办法在作用域内使用作用域? 如果 :has_red_cat 可以以某种方式使用 :has_cat_of_color ,或者如果有某种方法可以在其父记录中使用子记录的范围,我会很高兴,这样我就可以把将与猫相关的范围放入 Wife 模型中。

我真的不想在不使用 named_scope 的情况下直接在 SQL 中执行此操作,除非有其他更好的东西 - 对插件的建议和不被高度赞赏的内容,或者指导我需要的 SQL 类型学习。 一位朋友建议 UNION 或子搜索在这里可以工作,但在 Rails 的上下文中似乎没有太多讨论这些。 我还不知道关于视图的任何事情 - 它们有用吗? 有没有一种让 Rails 满意的方法来制作它们?

谢谢你!

当我要去圣艾夫斯时
我遇到了一个有七个妻子的男人
每个妻子都有七个麻袋
每个麻袋里有七只猫
每只猫有七个套件
套件、猫、麻袋、妻子
有多少人要去圣艾夫斯?

Part of why I love Rails is that I hate SQL - I think it's more like an assembly language that should be manipulated with higher level tools such as ActiveRecord. I seem to have hit the limits of this approach, however, and I'm out of my depth with the SQL.

I have a complex model with lots of sub-records. I also have a set 30-40 named_scopes that implement the business logic from the client. These scopes get chained together conditionally, which is why I have those joins_ scopes so the joins don't get clobbered.

I've got a couple of them that don't work right, or at least not how the client wants them to work. Here's a rough idea of the model structure, with a few named scopes (not all needed for the example) that illustrate my approach and indicate my problems. (please forgive any syntax errors)

class Man < ActiveRecord::Base
  has_many :wives

  named_scope :has_wife_named       lambda { |n| { :conditions => { :wives => {:name => n}}}}
  named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}}
  named_scope :has_yw_named_v2      lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}}
  named_scope :joins_wives         :joins => :wives

  named_scope :has_red_cat          :conditions => { :cats => {:color => 'red'}}        
  named_scope :has_cat_of_color     lambda { |c| { :conditions => { :cats => {:color => c}}}}
  named_scope :has_7yo_cat          :conditions => { :cats => {:age => 7}}
  named_scope :has_cat_of_age       lambda { |a| { :conditions => { :cats => {:age => a}}}}
  named_scope :has_cat_older_than   lambda { |a| { :conditions => ["cats.age > ?", a] }}
  named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }}
  named_scope :has_cat_fatter_than  lambda { |w| { :conditions => ["cats.weight > ?", w] } }
  named_scope :joins_wives_cats     :joins => {:wives => :cats}
end

class Wife < ActiveRecord::Base
  belongs_to :man
  has_many :cats
end

class Cat < ActiveRecord::Base
  belongs_to :wife
end
  1. I can find men whose wives have cats that are red AND seven years old

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    And I can even find men whose wives have cats that are over 20 pounds and over 6 years old

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    But that's not what I want. I want to find the men whose wives have amongst them at least one red cat and one seven year old cat, which need not be the same cat, or to find the men whose wives have amongst them at least one cat above a given weight and one cat older than a given age.
    (in subsequent examples, please assume the presence of the appropriate joins_ and DISTINCT)

  2. I can find men with wives named Esther

    @men = Man.has_wife_named('Esther')
    

    I can even find men with wives named Esther, Ruth OR Ada (sweet!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada'])
    

    but I want to find men with wives named Esther AND Ruth AND Ada.

  3. Ha ha, only joking, actually, I need this: I can find men with wives under 30 named Esther

    @men = Man.has_young_wife_named('Esther')
    

    find men with young wives named Esther, Ruth or Ada

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada'])
    

    but as above I want to find men with young wives named Esther AND Ruth AND Ada. Fortunately, the minimum is fixed in this case, but it would be nice to specify a minimum age as well.

  4. is there a way to test for an inequality with a hash syntax, or do you always have to revert to :conditions => ["", n] - note the difference between has_young_wife_named and has_yw_named_v2 - I like the first better, but the range only works for finite values. If you're looking for an old wife, I guess you could use a..100 but then when a wife turns 101 years old she drops off the search. (hmm. can she cook? j/k)

  5. is there a way to use a scope within a scope? I'd love it if :has_red_cat could use :has_cat_of_color somehow, or if there was some way to use the scope from a child record in its parent, so I could put the cat related scopes into the Wife model.

I really don't want to do this in straight SQL without using named_scope, unless there's something else actually nicer - suggestions for plugins and whatnot greatly appreciated, or direction into the sort of SQL I'll need to learn. A friend suggested that UNIONs or sub-searches would work here, but those don't seem to be discussed much in the context of Rails. I don't yet know anything about views - would they be useful? Is there a rails-happy way to make them?

Thank you!

As I was going to St Ives
I met a man with seven wives
Each wife had seven sacks
Each sack had seven cats
Each cat had seven kits
Kits, cats, sacks, wives
How many were going to St Ives?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

哭了丶谁疼 2024-08-01 11:48:08

好吧,我已经通过像这样的 named_scope 取得了很好的结果:

named_scope :has_cat_older_than   lambda { |a| { :conditions => ["men.id in ( select man_id from wives where wives.id in ( select wife_id from cats where age > ? ) )", a] } }

现在

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in ( select man_id from wives where name = ? and age < 30)", n] } }

我可以成功地完成

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti')

并获得我所期望的结果。 这些范围不需要使用连接,并且它们似乎与其他样式的连接配合得很好。

哇!

有人评论这是否是一种有效的方法,或者是否有一种更“rails-y”的方法。 将另一个模型的范围包含为 sql 子查询片段的某种方法可能会很有用......

Well, I've had great results with named_scopes like these:

named_scope :has_cat_older_than   lambda { |a| { :conditions => ["men.id in ( select man_id from wives where wives.id in ( select wife_id from cats where age > ? ) )", a] } }

and

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in ( select man_id from wives where name = ? and age < 30)", n] } }

I can now successfully do

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti')

and get what I'm expecting. These scopes don't require the use of the joins, and they seem to play well with the other styled joins.

w00t!

Commentary elicited on whether this is an efficient way to do this, or if there is a more 'rails-y' way. Some way to include a scope from another model as an sql subquery fragment could be useful...

幻想少年梦 2024-08-01 11:48:08

我使用construct_finder_sql 来完成一个named_scope 在另一个named_scope 中的子选择。 它可能并不适合所有人,但使用它可以让我们干燥一些用于报告的命名范围。

Man.has_cat_older_than(6).send(:construct_finder_sql,{})

在您的脚本/控制台中尝试一下。

I've used construct_finder_sql to accomplish the subselect of one named_scope within another. It may not be for everyone, but using it allow us to DRY up a couple of named_scopes we used for reports.

Man.has_cat_older_than(6).send(:construct_finder_sql,{})

Try that in your script/console.

や莫失莫忘 2024-08-01 11:48:08

您使用了最原生的 Rails 解决方案。
直接 SQL 将具有相同的性能,因此没有理由使用它。

You used the most native solution for Rails.
Straight SQL will have the same performance so there is no reason to use it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文