使用块中的where函数逐步过滤ActiveRecord

时间:2019-04-25 14:36:40

标签: ruby-on-rails

我尝试实现对激光数据库的免费请求。例如:给所有能量> 20J,持续时间<150fs和...

的镜头

我的方法是先找到所有能量> 20J的镜头,然后将持续时间<150fs的条件应用于这些镜头。依此类推,直到处理完所有搜索参数为止。

ss.ssps.each do |ssp|
    selectedShots = 
      selectedShots.where("instancevalues.name = '#{ssp.instancevalue_name}'")
                   .where("instancevalues.data_numeric #{ssp.operator} #{ssp.value}")
end

这对于一个搜索参数可以正常工作。 SQL语句如下:

SELECT  `shots`.* FROM `shots` WHERE (instancevalues.name = 'Energy') 
                                 AND (instancevalues.data_numeric > 20.0) 

使用两个搜索参数,我希望Rails首先处理第一个参数并创建上述镜头的子集。之后,应用第二个参数。

相反,Rails生成一个以下形式的SQL语句

SELECT  `shots`.* FROM `shots` WHERE 
           (instancevalues.name = 'Energy') 
       AND (instancevalues.data_numeric > 20.0) 
       AND (instancevalues.name = 'Duration') 
       AND (instancevalues.data_numeric < 150.0) 

结果当然是空的。 如何在Rails中实现逐步过滤?

更新:

我试图解释问题的实质,所以我没有使用模型的细节。但是也许这种简化阻碍了其他解决方案。

我有模特:

class Shot < ActiveRecord::Base
  belongs_to :experement
  has_many :instancevaluesets
  has_many :instances, :through => :instancevaluesets
  has_many :instancevalues, :through => :instancevaluesets
class Instancevalueset < ActiveRecord::Base
  belongs_to :shot
  belongs_to :instance
  has_many :instancevalues
class Instancevalue < ActiveRecord::Base
 #  instancevalueset_id :integer(38)     not null
 #  name                :string(256)     not null
 #  data_numeric        :decimal(, )
  belongs_to :instancevalueset

每次激光发射后,将在db中创建一个新镜头。 模型Instancevalueset模型将镜头连接到设备(实例)。拍摄后,物理设备会在此处为自己创建一个条目。设备使用instancevaluesset_id als FK将测量结果写入instancevalues中。我的任务是找到可以满足应用于intancevalues(测量结果)的搜索参数的镜头。

我的原始代码是:

  ss.ssps.each do |ssp|
          selectedShots = **selectedShots.joins(:instancevalues)**
            .where("instancevalues.name = '#{ssp.instancevalue_name}'")
            .where("instancevalues.data_numeric #{ssp.operator} #{ssp.value}")
      end

假设一组镜头[a,b,c,d,e,f]。镜头[a,c,e]的能量> 20J,所以经过1 ssp之后,我想获得[a,c,e]。现在,我想从该组中滤除短脉冲。射程e为长T = 200fs。因此,在第二次迭代中,我想将ssp应用于[a,c,e]并获得[a,c]。

@Mark的想法很好,但是它返回的查询数组适用于数据库而不是先前的查询。

 selectedShots = ss.ssps.map do |ssp|
          selectedShots.joins(:instancevalues)
            .where("instancevalues.name = '#{ssp.instancevalue_name}'")
            .where("instancevalues.data_numeric #{ssp.operator} #{ssp.value}")
      end 

如果可以对数组执行AND合并,则可以解决我的问题。但是如果我测试:

  selectedShots.last.merge(selectionShots.first)

我再次得到:

 ....WHERE (instancevalues.name = 'Energy') 
       AND (instancevalues.data_numeric > 20.0) 
       AND (instancevalues.name = 'Duration') 
       AND (instancevalues.data_numeric < 150.0) 

2 个答案:

答案 0 :(得分:0)

我认为这可能有效:

selectedShots = ss.ssps.map do |ssp|
  selectedShots.where("instancevalues.name = '#{ssp.instancevalue_name}'")
               .where("instancevalues.data_numeric #{ssp.operator} #{ssp.value}")
end

这将为集合ssps中的每个ssp创建一个具有单独条目的数组,并将分别执行每个查询。

答案 1 :(得分:0)

遵循@Mark的建议:

#2D array, one row contain ids of one query
      selectedShotsIDs = ss.ssps.map do |ssp|
          selectedShots.joins(:instancevalues)
            .where("instancevalues.name = '#{ssp.instancevalue_name}'")
            .where("instancevalues.data_numeric #{ssp.operator} #{ssp.value}").select(:id).map{|shot| shot.id}
      end

# Filter IDs that occur in all queries using array intersections
      filteredIDs = selectedShotsIDs.first
      selectedShotsIDs.each do |ss|
        filteredIDs = filteredIDs & ss
      end

      # get the shots
      selectedShots = Shot.where(id: filteredIDs )

可能效率低下,但有效!