我现在一直在使用Ecto,我一直发现让我的疑问可以是非常有帮助的。由此,我的意思是创建在查询中取出的函数并返回查询,以便我可以将这些查询函数的倍数在一起。
最近,我遇到了一个问题,我以前没见过,带我走了一些时间来完成。该问题是我的查询的多个部分加入不同的表(甚至通过同一个表!)。如果您想跳过那个,只需跳转到博客文章的结尾,编写多个连接。
TL; DR使用AS:选项在加入和检查时查看是否已查询已具有has_named_binding的连接?/ 2允许您撰写需要执行的多个查询
对于其他人来说,我将通过在这篇文章中的一个查询中建立可协调的疑问。为此,让我们看看一些汽车。
我将在为我为这个名为Ectocars的博客文章而制作的项目上运行我的疑问。按照Repo Readme的入门部分的步骤进行操作,如果您要遵循!
此图像是帮助您了解我们将要查询的数据库域。有不同的传输(自动,手动)和发动机(120hp,250hp)。规格具有传输和发动机。一辆汽车有一个规范,一种颜色,VIN号码(我们不需要这个,但它只是一个唯一的标识符)和一个名称。
好吧,让我们用一个很好的简单查询开始这个。让我们找到所有的蓝色汽车。为此,我们真的只需要ecto.query.where / 3。我喜欢使用表达式语法而不是关键字语法进行漂亮的管道能力,所以我们将看看向前移动的示例。现在我说我想找到所有的蓝色车,但是让这函数一点可重复使用,以便它可以采用任何颜色。为此,我们可以有这样的东西:
现在要尝试一下,让我们跳进入我们的IEX会话,让我们通过运行异常ex.car.with_color(“blue”)|> ectocars.repo.all():
IEX(1)> ectocars.car.with_color("蓝色")|> Ectocars.repo.all()18:07:08.057 [Debug]查询OK源="汽车" DB = 0.6ms解码= 1.1ms队列= 1.1ms选择C0。" ID",C0。"颜色",C0。" VIN_NUMBER",C0。&C0。" 34;规格_id"来自"汽车"作为C0哪里(C0."颜色" = $ 1)["蓝色"] [%ectocars.car {__meta__:#ecto.schema.metadata<:loaded,"汽车& #34;>,颜色:"蓝色" ID:1,规范:#ecto.association.notloaded<关联:规范未加载和gt; specification_id:3,vin_number:" somall" 34; },%ectocars.car {__meta__:#eco.schema.metadata<:加载,"汽车">>颜色:"蓝色",ID:3,规格:#ecto。协会.Notloaded<关联:规范未加载,gt; specification_id:1,vin_number:" my_dream_car" },%ectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车">颜色:"蓝色",ID:4,规格:#ecto。协会.Notloaded<关联:规范未加载,规范_ID:2,Vin_number:" cant_drive_this" }]
这看起来很棒,但我真的只想要一个理想的汽车给我,而不是三辆。继续,如果您愿意,请使用其他颜色尝试查询!
现在让我们让我们的查询更加复杂。我真的无法驾驶一辆手动车(我最后一次尝试用一辆带着一辆带有死亡电池的汽车和那个那么多的那家伙,不想出去跳到这么多次......),所以这可能是最好的如果我寻找自动车。
为实现这一目标,我们将通过我们的规格表加入我们的传输表:
你会在这里注意到我们实际上必须加入两次来实现这一目标。一旦通过规范,然后再次传输。我们再次通过接受作为参数的传输类型然后在末尾的WHERE子句中使用该查询功能可重新使用此查询功能。
一件事要肯定会意识到我们使用的是要加入的第三个参数的字母列表只是绑定的变量。例如,在第二个连接中,使用[C,S]没有什么特别的。那些字母对我们有意义,因为它们是先前函数调用中使用的相同字母。如果我们在第二个连接到另一个字母中将s更改为时,它根本不会更改查询。 (这将更加重要!)
IEX(3)> ectocars.car.with_transmission("自动")|> ectocars.repo.all()16:33:16.127 [debug]查询ok源="汽车" db = 3.3ms解码= 1.4ms队列= 2.8ms选择c0。" id",c0。"颜色",c0。" vin_number",c0。" 34;规格_id"来自"汽车"作为C0左外加入"规格"作为C0上的S1。" specification_id" = S1。" ID"左外加入"传输"在S1上的T2。"传输 - " = T2。" ID"在哪里(T2。"类型" = $ 1)["自动"] [%ectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车&#34 ;>,颜色:"蓝色",ID:3,规范:#ecto.association.notloaded<关联:规范没有加载,规范,specification_id:1,vin_number:" my_dream_car" },%ectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车">>颜色:"红色" id:2,规格:#ecto。协会.Notloaded<关联:规范未加载,规范,规格_id:1,Vin_number:"错误_color" },%ectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车">颜色:"蓝色",ID:1,规格:#ecto。协会.Notloaded<关联:规范未加载>,specification_id:3,vin_number:" som_small" }]
完美的!只是我们在寻找的东西,所有的自动汽车。虽然,太多的汽车可供选择。
现在,如果我真的在寻找具有蓝色的自动变速器的所有汽车。好吧,而不是写一个全新的查询,如果我们可以重复使用两个现有的那些,这将是很棒的。
为此,我们将不得不重构函数一点,不仅仅是掌握类型/颜色的参数,还可以将现有查询添加到:
通过使第一个参数可选,我们可以选择在Query中传递我们在建设过程中,或者我们将默认默认为汽车表询问。我们现在可以将这些仿佛刚刚构建一个长的Ecto查询:Ectocars.car |> ectocars.car.with_color(“蓝色”)|> Ectocars.car.with_transmission(“自动”)|> ectocars.repo.all():
IEX(5)> Ectocars.car |> ectocars.car.with_color("蓝色")|> ectocars.car.with_transmission("自动")|> ectocars.repo.all()16:43:18.287 [debug]查询ok source ="汽车" DB = 4.0ms Queue = 9.5ms选择C0。" ID",C0。"颜色",C0。" Vin_number",C0。" specification_id" 34;来自"汽车"作为C0左外加入"规格"作为C0上的S1。" specification_id" = S1。" ID"左外加入"传输"在S1上的T2。"传输 - " = T2。" ID"在哪里(C0。"颜色" = $ 1)和(t2。"类型" $ 2; $ 2)["蓝色&#34 ;,"自动"] [ %Eectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车">颜色:"蓝色" id:1,规格:#ecto.association。未载荷<关联:规范未加载,规范,规格_id:3,Vin_number:" too_small" },%ectocars.car {__meta__:#eco.schema.metadata<:加载,"汽车">>颜色:"蓝色",ID:3,规格:#ecto。协会.Notloaded<关联:规范未加载,gt; specification_id:1,vin_number:" my_dream_car" }]
我们终于得到它了!一个可协议的查询,找到我正在寻找的汽车。但是汽车引擎呢?也许我想要一辆拥有超过200马力的汽车。
因此,随着我们之前所做的操作,让我们为引擎创建一个查询函数:
IEX(10)> Ectocars.car |> ectocars.car.with_color("蓝色")|> ectocars.car.with_transmission("自动")|> Ectocars.car.with_engine_horse_power(200)|> ectocars.repo.all()**(ecto.queryError)lib / ecto_cars / cars / car.ex:37:Field`hife_power`在schema fectocars中不存在的地方。查询中的转发:从C0中的C0。 CAR,LEFT_JOIN:S1在ECTOCARS.Pecification,ON:C0.Specification_ID == S1.ID,left_join:T2在Ectocars.transmission中,on:s1.transmission_id == t2.id,left_join:s3在fectocars.pecification中,ON: c0.specification_id == s3.id,left_join:e4在ectocars.engine中,on:s1.engine_id == e4.id,其中:c0.color == ^"蓝色",其中:t2.type == ^"自动",其中:t2.horse_power> ^ 200,选择:c0(elixir)lib / enum.ex:1925:枚举。" -reduce / 3列表^ foldl / 2-0 - " / 3(elixir)lib / enum.ex :1418:枚举。" -map_reduce / 3-lists ^ mapfoldl / 2-0 - " / 3(elixir)lib / enum.ex:1418:枚举。" -map_reduce / 3-列表^ mapfoldl / 2-0 - " / 3
让我们看看我们是否可以分解在此发生的事情。查看错误,这就是传输模式不存在Horse_Power。足够公平,但我以为我们在这里查询发动机架构,对吧?好吧,如果我们通过查询浏览查询,我们可以看到更多发生的事情。首先,我们加入规范(S1),然后转到传输(T2),然后再次调整(S3)(这似乎没有很大......),然后到发动机(E4)。现在在马力的Where条款中,我们正在看T2而不是E4!
要更好地理解这一点,让我们解开所有这些查询的作品,并在其功能之外将它们一起写在一起:
也许它更容易看到这里。由于默认方式绑定与加入的关系,所引用的表与该列表中的位置完全是相关的。所以在第8行时,我们打算在引擎表上查询时,我们真的在此查询中查询第三表...传输!要获得此项工作,我们必须执行以下操作:
首先,我删除了双重加入来规范,因为真的,谁需要那个。我还将加入和查询更新为引擎,以便绑定列表了解与传输的现有绑定。在加方面,这将在下方工作,这真的杀死了我们的可组合性。正如它所致,此查询现在依赖于已完成的先前查询(指定),并且在调用此之前将相同精确的表格连接到查询。这里必须更好的方式,以便我们可以保持生活在我们的超级酷炫的世界中......
命名绑定!必须完全依靠我们加入不同表的顺序是香蕉。要使用命名绑定,请在调用Join时通过AS选项:加入(查询,:左,[C],S规范,AS::规范,ON:C.Specification_ID == S.ID)。然后,我们可以通过使用关键规格稍后绑定我们的规格变量:::加入(查询,:左,[c,规格:s],t在传输中,如下::传输,on:s.transmission_id == T.ID)。现在让我们继续前进并重新编写我们的函数来使用命名绑定:
我不会将输出显示出来的每个单独测试,但我保证他们的工作;)但是当我们再次尝试链接时,有趣的事情就会发生......
IEX(16)> Ectocars.car |> ectocars.car.with_color("蓝色")|> ectocars.car.with_transmission("自动")|> Ectocars.car.with_engine_horse_power(200)|> ectocars.repo.all()**(ecto.query.compileError)别名`:规格`已存在(EECTO_CARS)lib / ecto_cars / cars / car.ex:35:Eectocars.car.with_engine_horse_power / 2
好的,一个新的错误!它看起来我们无法重新绑定相同的键(有意义......)。一个简单的解决方案是用_engine_horse_power函数中的连接到规范,但随后我们再次被困在一个地方只有在调用连接到规范的函数之后只能调用此功能。
我认为这里的最佳选择是要检查并查看查询是否已加入特定表,并且如果它没有,只会重新加入。我们可以用has_named_binding整理完成此功能?此功能允许我们执行此操作,检查并查看查询是否已具有该绑定。通过制作较小的辅助功能,我们应该处于良好状态。让我们看看重构看起来的样子是什么:
Join_Specifications Helper功能负责检查连接并在需要时将其添加到查询。我们现在可以终于编写一个不依赖于任何特定顺序或依赖于一个功能的可协调查询,始终需要在第二个可以调用之前调用!
IEX(17)> Ectocars.car |> ectocars.car.with_color("蓝色")|> ectocars.car.with_transmission("自动")|> Ectocars.car.with_engine_horse_power(200)|> ectocars.repo.all()17:31:25.303 [debug]查询ok source ="汽车" DB = 2.6ms队列= 3.8ms选择C0。" ID",c0。"颜色",c0。" Vin_number",c0。" specification_id&# 34;来自"汽车"作为C0左外加入"规格"作为C0上的S1。" specification_id" = S1。" ID"左外加入"传输"在S1上的T2。"传输 - " = T2。" ID"左外加入"发动机"作为S1上的E3。" Engine_ID" = E3。" ID"在哪里(C0。"颜色" = $ 1)和(t2。"类型" = $ 2)和(e3."马_power"> $ 3)[&#34 ;蓝色&#34 ;,"自动",200] [%ectocars.car {__meta__:#ecto.schema.metadata<:加载,"汽车">,颜色:&# 34;蓝色",ID:3,规范:#ecto.association.notloaded<关联:规范没有加载,规范,规格_id:1,Vin_number:" my_dream_car" }]
我们终于得到它了!在可协调的疑问,我发现了我完美的汽车。