Rails与两个外键关联?

时间:2014-06-20 15:55:55

标签: mysql sql ruby-on-rails rails-activerecord has-one

我对Rails很新,我通过搜索找不到类似的情况。我有两个现有表eventdata

event有很多列:

+-------------------+----------------------+------+-----+---------+-------+
| Field             | Type                 | Null | Key | Default | Extra |
+-------------------+----------------------+------+-----+---------+-------+
| sid               | int(10) unsigned     | NO   | MUL | NULL    |       |
| cid               | int(10) unsigned     | NO   |     | NULL    |       |
| signature         | varchar(255)         | NO   | MUL | NULL    |       |
| src_ip            | int(10) unsigned     | YES  | MUL | NULL    |       |
| dst_ip            | int(10) unsigned     | YES  | MUL | NULL    |       |
| ...               | ...                  | ...  | ... | ...     |       |
| etc               |                      |      |     |         |       |
+-------------------+----------------------+------+-----+---------+-------+

data只有三个:

+--------------+------------------+------+-----+---------+-------+
| Field        | Type             | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+-------+
| sid          | int(10) unsigned | NO   | MUL | NULL    |       |
| cid          | int(10) unsigned | NO   |     | NULL    |       |
| data_payload | text             | YES  |     | NULL    |       |
+--------------+------------------+------+-----+---------+-------+

我的模特:

class Event < ActiveRecord::Base
  has_one :payload, :foreign_key => 'cid', :primary_key => 'cid'
  self.table_name = "event";
  [more stuff]
end

class Payload < ActiveRecord::Base
  belongs_to :event
  self.table_name = "data";
end

使用has_one关系,我可以执行如下查询:

Event.limit(5).offset(90000).each do |e|

然后循环并为每次迭代调用e.payload,这将执行如下查询:

SELECT  `data`.* FROM `data`  WHERE `data`.`cid` = 90001 LIMIT 1

但这显然不是正确的方法。我觉得我在这里输入它就是在捣蛋。

我认为我的理想查询类似于:

SELECT `event`.`cid`, `event`.`sid`, `event`.`signature`, `data`.`data_payload` 
FROM `event`, `data` 
WHERE `event`.`sid` = `data`.`sid` AND 
      `event`.`cid` = `data`.`cid` AND
      `event`.`signature` LIKE 'example_signature'

有没有办法在一个查询中使用正确的Rails关联完成所有操作?只考虑我必须通过将最终的ActiveRecord / hash对象传递给我的视图的箍,让我觉得我正在以一种非常迂回的方式做这件事。

1 个答案:

答案 0 :(得分:0)

您的意思是,它不应该在每次传递时触发新的SELECT查询吗?

您可以使用includes()指令对此进行优化,如下所示:

events = Event.includes(:payload).where('signature LIKE ?', 'example_signature')
events.each do |event|
  puts e.payload.data_payload
end

这将在2个查询中加载所有数据,一个用于所有事件,另一个用于将外键返回到事件的所有有效负载:

Event Load (0.2ms)  SELECT "event".* FROM "event"  WHERE (signature LIKE 'example_signature')
Payload Load (0.2ms)  SELECT "data".* FROM "data"  WHERE "data"."cid" IN (2,4,5,6,7)

修改

您的评论似乎表明性能是最受关注的(可能因为表格很大),所以我认为您已经非常接近了。我建议只选择你需要的列(因为事件表中有很多列),并使用和SQL JOIN直接添加其他表的内容(因为有一对一的映射关系)事件到有效载荷)

这样就可以了:

Event.
  select('event.cid, event.sid, event.signature, data.cid, data.sid, data.data_payload').
  joins(:payload).
  where('signature LIKE ?', 'example_signature').each do |event|
  puts event.data_payload
end
# SELECT event.cid, event.sid, event.signature, data.cid, data.sid, data.data_payload FROM "event" INNER JOIN "data" ON "data"."cid" = "event"."cid" WHERE (signature LIKE 'example_signature')

这也将保留您的Event对象,因此您可以在其上调用其他方法,但如果它们不依赖于您在select()子句中排除的属性,则

如果您不需要Event对象中的任何其他方法,并且只想尽快获得结果,那么您可以使用以下命令运行原始查询:

sanitized_str = ActiveRecord::Base.connection.quote('example_signature')
result = ActiveRecord::Base.connection.execute(
  "SELECT event.cid, event.sid, event.signature, data.cid, data.sid, data.data_payload
   FROM event
   INNER JOIN data ON data.cid = event.cid
   WHERE (signature LIKE #{sanitized_str});"
)
# => [{"cid"=>2, "sid"=>1, "signature"=>"example_signature", "data_payload"=>"Hello world!", 0=>2, 1=>1, 2=>"example_signature", 3=>2, 4=>1, 5=>"Hello world!"}]

这将返回一个记录数组,每条记录都是一个普通的ruby哈希值。事件对象不会在内存中构建,从而节省了大量开销。