【问题标题】:Rails association with two foreign keys?Rails与两个外键关联?
【发布时间】:2014-06-20 15:55:55
【问题描述】:

我对 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 对象传递给我的视图,这让我觉得我正在以一种非常迂回的方式来做这件事。

【问题讨论】:

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


    【解决方案1】:

    您的意思是,它不应该每次传递都触发一个新的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)
    

    编辑

    您的 cmets 似乎表明性能是最关心的问题(可能是因为表很大),所以我认为您已经非常接近了。我建议只选择您需要的列(因为事件表有很多),并使用 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​​ 哈希。事件对象不会在内存中构建,节省了大量开销。

    【讨论】:

    • 这似乎是个好主意,所以我决定快速测试一下:编辑:哎呀,字符太多了。但结果是这样的:Payload Load (264023.1ms) 我不完全确定它为什么这么慢。
    • 要回答你原来的问题(“你的意思是..”)但是,我的问题更多的是如何让 WHERE 子句同时包含 event.cid = data.sid AND event.sid = data.sid 因为这似乎要快得多我的 MySQL 测试。
    • @aplewandowski 我已经更新了我的答案,希望能解决您的一些担忧。告诉我进展如何!
    • 我担心我没有用 Rails 或 ruby​​“方式”硬编码这么多 SQL。我相信您的两个示例查询可能会对我有所帮助。非常感谢您的浏览。
    • 这两个例子在我见过的大多数 Rails 代码中都非常罕见。 ActiveRecord 很棒,因为您可以将大量功能挂在模型记录对象上,但在某些情况下,如果不使用这些丰富的对象并更接近金属,它确实会有所回报。
    猜你喜欢
    • 1970-01-01
    • 2014-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多