Mongoid:按嵌套子值排序

时间:2011-09-21 13:52:35

标签: ruby-on-rails ruby mongodb mongoid

class Box
  embeds_many :things
  after_init :add_default_things

  def add_default_things
    self.things.build(title: "Socks", value: 2)
    self.things.build(title: "Ring", value: 1)
  end
end

class Thing
  field :title
  field :value, type: Integer
end

所有的盒子都有一些默认的东西:袜子和戒指。每个人都可以将这个或另一个东西添加到一个盒子里。所以现在我需要按袜子的数量订购所有的盒子:

Box.order_by(:thing_with_title_socks.value.desc) # ???

2 个答案:

答案 0 :(得分:1)

这对单个查询很难(或不可能)。我玩了一会儿。首先,让我发布我的整个测试脚本:

require 'mongoid'

Mongoid.configure do |config|
  config.master = Mongo::Connection.new.db("test")
end

class Box
  include Mongoid::Document
  embeds_many :things
  field :name
end

class Thing
  include Mongoid::Document
  field :title
  field :value, type: Integer
end

b = Box.new
b.name = "non sock box"
b.things.push Thing.create!(title: "Ring", value:1)
b.save!

b1 = Box.new
b1.name = "sock box"
b1.things.push Thing.create!(title:"Socks", value:50)
b1.save!

b2 = Box.new
b2.name = "huge sock box"
b2.things.push Thing.create!(title:"Socks", value:1000)
b2.things.push Thing.create!(title:"Ring", value:1100)
b2.save!

b3 = Box.new
b3.name = "huge ring box"
b3.things.push Thing.create!(title:"Socks", value:100)
b3.things.push Thing.create!(title:"Ring", value:2600)
b3.save!

b4 = Box.new
b4.name = "ring first sock box"
b4.things.push Thing.create!(title: "Ring", value: 1200)
b4.things.push Thing.create!(title: "Socks", value: 5)
b4.save!

所以你这样做有几个方法。首先,您可以找到符合您标准的所有Thing对象,然后对Things做一些事情。例如,获取Box ID。

Thing.desc(:value).where(:title => "Socks").to_a

这会将问题推向应用层,这在一些mongo查询中很常见。但这不是你要求的。所以我提交了这个令人讨厌的查询。这只适用于在嵌入式文档中始终首先使用Sock的东西。

Box.all(conditions: { "things.title" => "Socks" }, 
  sort:[["things.0.value", :desc]]).to_a

这真的很烦人,因为你会注意到在我的示例数据中,我有一个“巨大的戒指盒”,它有袜子,但也有最大的值属性,所以实际上这首先返回。如果您对事物进行了重新排序,则此查询实际上会起作用。我不知道如何用事物来排序。值等于“foo”。我打赌你不能。

>> Box.all(conditions: { "things.title" => "Socks" }, 
sort:[["things.0.value", :desc]]).collect(&:name)
=> ["ring first sock box", "huge sock box", "huge ring box", "sock box"]

看,这是错的。应该是第一个巨大的袜子盒。但这并不是因为.0.value在(b4)样本数据中获得了最大的2600 Ring值。

你可以使用一个班轮,但它实际上(至少)有两个查询:

Thing.desc(:value).where(:title => "Socks").collect(&:id).each {|thing| 
  puts Box.where("things._id" => thing).first.name }

但是,再次,这真的是在红宝石中解除,而不是让mongo这样做。有很多这样的连接问题。你必须做两个查询。

另一种选择可能是对数据进行去规范化,只将事物存储为嵌入的属性数组或仅仅是属性。如果箱子可以包含任何东西,您甚至不需要提前定义属性。这对MongoDB来说很酷。

class Chest
  include Mongoid::Document
  field :name
end

c = Chest.create!(:name => "sock chest", :socks => 50, :ring => 1)
c1 = Chest.create!(:name => "non sock chest", :ring => 10)
c2 = Chest.create!(:name => "huge sock chest", :ring => 5, :socks => 100)
c3 = Chest.create!(:name => "huge ring chest", :ring => 100, :socks => 25)

Chest.where(:socks.exists => true).desc(:socks).collect(&:name)
=> ["huge sock chest", "sock chest", "huge ring chest"]

答案 1 :(得分:1)

我不擅长ruby,所以我将尝试用java术语来解释它。问题是你如何设计数据。例如,你有n个Box类,每个类都有一个项目列表:

public class Box {
  public List<Item> items;
}

public class Item {
  public String name;
  public int value;
}

要对此类中的所有框进行排序,以获取特定项目值,您需要循环显示所有项目中的所有项目。所以我会改变我的代码来更有效地做到这一点:

public class Box {
  public Map<Integer, Item> items;
}

public class Item {
  public int id;
  public String name;
  public int value;
}

这样我会检查项目是否存在于框中;访问具有O(1)复杂度的items值,我不会错误地在我的盒子中放入2个不同的袜子包,这会导致错误(哪一个会排序?)。

您当前的数据结构如下:

{
  things : [ {"name" : "socks", "value" : 2} , {"name" : "ring", "value" : 5} ]
}

但如果要对“已知”对象(如戒指或袜子)进行排序/直接访问(在查询中使用),那么您的数据应如下所示:

{
  things : { "socks": 2, "ring" : 5 }
}

如果您有额外的信息附加到这些项目,他们也可以看起来像:

{
  things : { "S01" : { "name" : "super socks", "value" : 1 }, "S02" : { "name" : "different socks", "value" : 2} }
}
通过这种方式,您可以直接访问框中的项目。

希望这会有所帮助。

编辑:我认为这很明显,但只是为了清楚说明:除非您知道确切位置,否则无法有效查询“已列出”的子数据。 (你总是可以使用map / reduce)