为什么我的嵌套关联加载?

时间:2016-06-20 02:06:11

标签: ruby-on-rails eager-loading

我有以下型号:主线 - >发布 - >覆盖。我试图在一个急切加载的查询中获取主线的所有叠加层。

(这是一个最初在SQL Server上使用Entity Framework启动的数据库,所以我有一个额外的复杂功能,即密钥不是Rails标准的。)

mainline.rb

class Mainline < ActiveRecord::Base
  self.primary_key = 'MainlineID'
  has_many :releases, foreign_key: 'Mainline_MainlineID'
  has_many :overlays, through: :releases
end

release.rb

class Release < ActiveRecord::Base
  self.primary_key = 'ReleaseID'
  belongs_to :mainline, foreign_key: 'Mainline_MainlineID'
  has_many :overlays, foreign_key: 'Release_ReleaseID'
end

overlay.rb

class Overlay < ActiveRecord::Base
  self.primary_key = 'OverlayID'
  belongs_to :release, foreign_key: 'Release_ReleaseID'
end

我可以在Rails控制台中执行以下操作:

m = Mainline.where(MainlineID: 2025).includes(releases: :overlays).where.not(overlays: { Version: nil }).first

这给了我以下SQL,这是正确的。我可以在SQL Server Management Studio中运行它并获得正确的结果。

  SQL (40.8ms)  EXEC sp_executesql N'SELECT DISTINCT TOP (1) [mainlines].[MainlineID] FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [mainlines].[MainlineID] = 2025 AND ([overlays].[Version] IS NOT NULL) ORDER BY [mainlines].[MainlineID] ASC'
  SQL (138.0ms)  EXEC sp_executesql N'SELECT [mainlines].[MainlineID] AS t0_r0, [mainlines].[Program_ProgramID] AS t0_r1, [mainlines].[Designation] AS t0_r2, [mainlines].[Lifecycle] AS t0_r3, [releases].[ReleaseID] AS t1_r0, [releases].[Mainline_MainlineID] AS t1_r1, [releases].[SoftwareVersion] AS t1_r2, [releases].[ModuleName] AS t1_r3, [releases].[FirstProductConfigFileVersion] AS t1_r4, [releases].[ProductIdString] AS t1_r5, [releases].[ModulePartNumber] AS t1_r6, [releases].[InterfaceLevel] AS t1_r7, [releases].[CreationDate] AS t1_r8, [releases].[StartBootLoaderVersion] AS t1_r9, [releases].[EndBootLoaderVersion] AS t1_r10, [releases].[ByteOrder] AS t1_r11, [releases].[IndexTableAddress] AS t1_r12, [releases].[Description] AS t1_r13, [releases].[FileNameBase] AS t1_r14, [releases].[ImportedDate] AS t1_r15, [overlays].[OverlayID] AS t2_r0, [overlays].[Release_ReleaseID] AS t2_r1, [overlays].[Version] AS t2_r2, [overlays].[OverlayDate] AS t2_r3, [overlays].[Proposal_ProposalID] AS t2_r4, [overlays].[Lifecycle] AS t2_r5, [overlays].[Description] AS t2_r6, [overlays].[Comments] AS t2_r7, [overlays].[ImportedDate] AS t2_r8, [overlays].[Proposer] AS t2_r9 FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [mainlines].[MainlineID] = 2025 AND ([overlays].[Version] IS NOT NULL) AND [mainlines].[MainlineID] IN (2025) ORDER BY [mainlines].[MainlineID] ASC

我的问题是我可以在不再次访问数据库的情况下遍历版本...

2.2.3 :137 > m.releases
 => #<ActiveRecord::Associations::CollectionProxy [#<Release ReleaseID: 126, Mainline_MainlineID: 2025, SoftwareVersion: "40190004_0", ModuleName: "CM23xx", FirstProductConfigFileVersion: "2003.1.1.0", ProductIdString: "BHQ", ModulePartNumber: "9999999", InterfaceLevel: "4.6.0.0", CreationDate: "2015-06-16 00:0

但不是叠加......

2.2.3 :138 > m.overlays
  Overlay Load (78.8ms)  EXEC sp_executesql N'SELECT [overlays].* FROM [overlays] INNER JOIN [releases] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [releases].[Mainline_MainlineID] = @0', N'@0 int', @0 = 2025  [["Mainline_MainlineID", 2025]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Overlay OverlayID: 3747, Release_ReleaseID: 126, Version: 86, OverlayDate: "2015-07-30 00:00:00", Proposal_ProposalID: nil, Lifecycle: "Imported", Description: nil, Comments: nil, ImportedDate: "2015-08-24 13:59:44", Proposer: nil>, #<Overl

它生成了另一个查询,这次没有我的条件,Version不是NULL。为什么没有Rails认识到我已经有我想要的叠加包含我选择的主线?

更新:刚开始发布后,我尝试了以下内容:

m = Mainline.includes(:releases, :overlays).where.not(overlays: { Version: nil }).find(2025)

请注意.includes()中的更改。这给了我想在控制台中看到的内容:

2.2.3 :066 > m.overlays
 => #<ActiveRecord::Associations::CollectionProxy [#<Overlay OverlayID: 3747, Release_ReleaseID: 126, Version: 86, OverlayDate: "2015-07-30 00:00:00", Proposal_ProposalID: nil, Lifecycle: "Imported", Description: nil, Comments: nil, ImportedDate: "2015-08-24 13:59:44", Proposer: n

这会产生很多不同的SQL:

  SQL (657.4ms)  EXEC sp_executesql N'SELECT [mainlines].[MainlineID] AS t0_r0, [mainlines].[Program_ProgramID] AS t0_r1, [mainlines].[Designation] AS t0_r2, [mainlines].[Lifecycle] AS t0_r3, [releases].[ReleaseID] AS t1_r0, [releases].[Mainline_MainlineID] AS t1_r1, [releases].[SoftwareVersion] AS t1_r2, [releases].[ModuleName] AS t1_r3, [releases].[FirstProductConfigFileVersion] AS t1_r4, [releases].[ProductIdString] AS t1_r5, [releases].[ModulePartNumber] AS t1_r6, [releases].[InterfaceLevel] AS t1_r7, [releases].[CreationDate] AS t1_r8, [releases].[StartBootLoaderVersion] AS t1_r9, [releases].[EndBootLoaderVersion] AS t1_r10, [releases].[ByteOrder] AS t1_r11, [releases].[IndexTableAddress] AS t1_r12, [releases].[Description] AS t1_r13, [releases].[FileNameBase] AS t1_r14, [releases].[ImportedDate] AS t1_r15, [overlays].[OverlayID] AS t2_r0, [overlays].[Release_ReleaseID] AS t2_r1, [overlays].[Version] AS t2_r2, [overlays].[OverlayDate] AS t2_r3, [overlays].[Proposal_ProposalID] AS t2_r4, [overlays].[Lifecycle] AS t2_r5, [overlays].[Description] AS t2_r6, [overlays].[Comments] AS t2_r7, [overlays].[ImportedDate] AS t2_r8, [overlays].[Proposer] AS t2_r9 FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [releases] [releases_mainlines_join] ON [releases_mainlines_join].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases_mainlines_join].[ReleaseID] WHERE ([overlays].[Version] IS NOT NULL) AND [mainlines].[MainlineID] = @0 AND [mainlines].[MainlineID] IN (2025)', N'@0 int', @0 = 2025  [["MainlineID", 2025]]

所以现在我有两个问题。首先,我不明白为什么.includes()语法有效,当我读到关于这个主题的所有东西让我觉得它应该是(release :: overlays),而不是以逗号分隔。

其次,更重要的是,我的目标是增加2个预加载的关联,并且它们变得更加毛茸茸。如果我无法理解第一个例子,我就无法让其他人加载。我最终需要了解以下内容:

Mainline -> Release -> Overlay -> Calibration <- Parameter

如何在正确的散列/数组排列中使用.includes()加载所有这些数据?从我所读过的内容来看,我希望.includes(发布:[overlay:[校准::参数]]),但我尝试过的任何事情都让我感到非常接近。只是尝试添加校准级别,让我得到整个数据库的交叉产品。

而这只是我需要从单个查询中获取的核心数据集。我还需要结合用户和所有权以及类别等。我的核心&#34;主线&#34;有大约17K线,涉及约8种不同的模型。我绝对无法承受任何类型的N + 1查询与此应用程序。我必须一气呵成。 (我已经成功将其设置为find_by_sql(),这正是我所需要的数据,但我希望能够实现这一目标&#34; Rails方式,&#34;这样我就可以按预期的方式遍历关系。)

我尝试过使用.joins(),因为这确实是INNER JOINS,这是我真正需要的,但它似乎并没有预先加载数据。我甚至尝试过将.joins()和.includes()结合起来,因为我看到有些人这样做,但现在我只是在猜测。我不明白为什么我在第一遍中尝试的东西不起作用,而且在我这样做之前,我只是在浪费时间。

1 个答案:

答案 0 :(得分:0)

我现在看到我误解了如何引用急切加载的数据。尽管使用has_many through:进行了正确设置,但您无法跳过第二个关联并直接访问加载的数据中的第三个关联。我将永远无法引用@mainline.overlays的成员而不会引发其他查询。我必须将它们引用为@mainline.releases[0].overlays[0]

令人欣慰的消息是,我对包含的语法并不感到沮丧。我只是错误地使用了结果。但是,基于这种访问我的数据的方式,我已经改变了查询以使我真正感兴趣的(校准)顶级查询,如下所示:

cals = Calibration
  .joins(overlay: [release: :mainline])
  .where(mainlines: { MainlineID: params[:id] })
  .where.not(overlays: { Version: nil })
  .includes(:parameter, overlay: [release: :mainline])

然后我可以遍历cals并访问参数数据,这是我的第一步/关注。

我现在还看到.joins()在根据相关值限制数据集方面的作用。与我的.find_by_sql()方法相比,我当前(有限)的结果集需要3秒才能完成,而./Pods/FirebaseCrash/batch-upload XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX 方法只需要大约一半,并且已经包含了更多我尚未包含的模型,所以我不会# 39;不知道这是否会起作用,但至少我明白现在正在发生什么。

(当然,这两种方法都存在这样的问题:一旦从浏览器传递了大约20K行数据,浏览器需要10秒钟来生成表格,但那是另一种颜色的马。)