为什么我的嵌套关联没有加载?
Why isn't my nested association loading?
我有以下模型:Mainline -> Release -> Overlay。我正在尝试在一个急切加载的查询中获取主线的所有叠加层。
(这是一个最初在 SQL 服务器上使用 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() 语法有效,当我读到的关于这个主题的所有内容让我认为它真的应该是 (releases: :overlays),而不是逗号分隔的。
其次,更重要的是,我的目标是让 2 个以上的关联预加载,并且它们变得更毛茸茸。如果我无法理解第一个示例,就无法加载其他示例。我最终需要执行以下操作:
Mainline -> Release -> Overlay -> Calibration <- Parameter
我如何以正确的 hash/array 安排使用 .includes() 加载所有这些数据?从我读过的内容来看,我希望 .includes(release: [overlay: [calibration: :parameter]]),但我没有尝试过让我更接近。只是尝试添加校准级别让我得到整个数据库的叉积。
这只是我需要从单个查询中获取的核心数据集。我还需要结合用户、所有权和类别等。我的核心 "mainline" 有大约 17K 行,涉及大约 8 个不同的模型。对于此应用程序,我绝对负担不起任何类型的 N+1 查询。我必须一口气搞定。 (我已经成功地将它设置为 find_by_sql(),它准确地获取了我需要的数据,但我想让它继续运行 "the Rails way,",以便我可以按预期遍历关系。)
我试过使用 .joins(),因为它可以实现 INNER JOINS,这是我真正需要的,但它似乎没有预加载数据。我什至尝试过结合 .joins() 和 .includes() ,正如我看到有些人所做的那样,但现在我只是在猜测。我不明白为什么我在第一遍中尝试的方法不起作用,而且在我这样做之前,我只是在浪费时间。
我现在明白我误解了如何引用预先加载的数据。尽管使用 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
并访问参数数据,这是我的第一个 step/concern。
我现在还看到 .joins()
在根据关联值限制我的数据集方面的作用。与我的 .find_by_sql()
方法相比,我当前(有限的)结果集以这种方式需要 3 秒,后者只需要大约一半,并且已经包含了更多我尚未包含的模型,所以我不不知道这是否会很好地工作,但至少我明白现在发生了什么。
(当然,这两种方法都有一个问题,一旦 ~20K 行数据从浏览器中移出,浏览器需要 10 秒才能生成 table,但这是另一种颜色的马。 )
我有以下模型:Mainline -> Release -> Overlay。我正在尝试在一个急切加载的查询中获取主线的所有叠加层。
(这是一个最初在 SQL 服务器上使用 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() 语法有效,当我读到的关于这个主题的所有内容让我认为它真的应该是 (releases: :overlays),而不是逗号分隔的。
其次,更重要的是,我的目标是让 2 个以上的关联预加载,并且它们变得更毛茸茸。如果我无法理解第一个示例,就无法加载其他示例。我最终需要执行以下操作:
Mainline -> Release -> Overlay -> Calibration <- Parameter
我如何以正确的 hash/array 安排使用 .includes() 加载所有这些数据?从我读过的内容来看,我希望 .includes(release: [overlay: [calibration: :parameter]]),但我没有尝试过让我更接近。只是尝试添加校准级别让我得到整个数据库的叉积。
这只是我需要从单个查询中获取的核心数据集。我还需要结合用户、所有权和类别等。我的核心 "mainline" 有大约 17K 行,涉及大约 8 个不同的模型。对于此应用程序,我绝对负担不起任何类型的 N+1 查询。我必须一口气搞定。 (我已经成功地将它设置为 find_by_sql(),它准确地获取了我需要的数据,但我想让它继续运行 "the Rails way,",以便我可以按预期遍历关系。)
我试过使用 .joins(),因为它可以实现 INNER JOINS,这是我真正需要的,但它似乎没有预加载数据。我什至尝试过结合 .joins() 和 .includes() ,正如我看到有些人所做的那样,但现在我只是在猜测。我不明白为什么我在第一遍中尝试的方法不起作用,而且在我这样做之前,我只是在浪费时间。
我现在明白我误解了如何引用预先加载的数据。尽管使用 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
并访问参数数据,这是我的第一个 step/concern。
我现在还看到 .joins()
在根据关联值限制我的数据集方面的作用。与我的 .find_by_sql()
方法相比,我当前(有限的)结果集以这种方式需要 3 秒,后者只需要大约一半,并且已经包含了更多我尚未包含的模型,所以我不不知道这是否会很好地工作,但至少我明白现在发生了什么。
(当然,这两种方法都有一个问题,一旦 ~20K 行数据从浏览器中移出,浏览器需要 10 秒才能生成 table,但这是另一种颜色的马。 )